zoukankan      html  css  js  c++  java
  • 郝健: Linux内存管理学习笔记-第1节课【转】

    本文转载自:https://blog.csdn.net/juS3Ve/article/details/80035751

    摘要

    MMU与分页机制

    内存区域(内存分ZONE)

    LinuxBuddy分配算法

    CMA(连续内存分配器)

    0.    课前阅读

    宋宝华:CPU是如何访问到内存的?--MMU最基本原理

    http://mp.weixin.qq.com/s/SdsT6Is0VG84WlzcAkNCJA

    宋宝华: 用代码切身实践体会meltdown漏洞

    http://mp.weixin.qq.com/s/lJJU3LCepJgNq5AxyFFM8Q

    投机之殇——一幅图读懂MELTDOWN漏洞

    http://mp.weixin.qq.com/s?__biz=MzA3NTk5MDIzNw%3D%3D&mid=2647665589&idx=1&sn=3c29a6c3fe477cbd3b2efed983b1dd5e&scene=45#wechat_redirect

    1. MMU与分页机制

    640?wx_fmt=png

    MMU(内存管理单元)是一个硬件,辅助操作系统进行内存管理,提供虚拟地址和物理地址的映射、内存访问权限保护和Cache缓存控制等硬件支持。CPU一旦开启MMU,CPU就只能看到虚拟地址(程序员也只能看到虚拟地址),只有MMU能看到物理地址。

    MMU的主要功能如下:

    1)提供虚拟地址和物理地址的映射

    例如,CPU访问一个32位的虚拟地址0x12345 670,假设MMU的管理把每一页的内存分成4K(在32位系统中通常采用的PageSize为4K),上图中p(页号)即为12345,d(页内偏移地址)即为670。首先用p去查页表(页表本身也在内存),找到对应的页表项,页表项里会填写这一页虚拟地址所对应的物理地址。

    注:基址寄存器存页表的基地址,每次进程切换时,寄存器的值会改变,因为每一个进程的页表都不一样。

    2)内存访问权限保护

    每个页表项中除了有虚拟地址所对应的物理地址外,还有这一页地址的RWX权限。比如,代码段只有R+X,用一个指针去写代码段,就会发生page fault(两种情况都会page fault:i. 虚拟地址没有找到对应的物理地址;ii. 虚拟地址有对应的物理地址,但权限不对)。EG. 写const变量page fault

    640?wx_fmt=png

    另外一个重要的地方是,在MMU的页表项中还可以标注这一页的另一个并行的权限,即这个虚拟地址可以在用户态与内核态访问还是只能在内核态访问。例如,IA32下,内核空间地址一般映射到3GB~4GB,在页表项中就把3  G以上的页表设置为只有当CPU陷入到内核模式才能访问。这样就限制了用户态程序对内核数据的访问。 EG. Meldown旁路攻击,突破硬件限制。

    附注:

    i.          “虚拟地址是个指针,物理地址是个整数(32位或者64位整数,不是指针)“

    可参考内核代码对物理地址的定义:include/linux/types.h

    640?wx_fmt=png

           ii.          由于页表访问速度很慢,引出MMU的核心部件TLB(Translation Looksize Buffer),TLB是MMU的核心部件,它缓存少量的虚拟地址与物理地址的转换关系,是转换表的Cache,俗称“快表”。

    当TLB中没有缓冲对应的地址转换关系时,需要通过对内存中转换表(大多数处理器的转换表为多级页表)的访问来获得虚拟地址和物理地址的对应关系,引出MMU的另一核心部件TTW(TranslationTable walk)。TTW成功后,结果应写入TLB中。

    iii.          MPU:内存保护单元,只能做权限管理,不能做虚实映射。

    2.   内存区域(内存分ZONE)

    640?wx_fmt=png

    1)ZONE_DMA

    640?wx_fmt=png

    DMA相对于内存与CPU相对于内存一样,可以直接访问内存。由于某些DMA引擎可能有缺陷,并不一定能访问到所有内存(如,x86 ISA总线上的DMA引擎在访问内存时,地址线只能发到16M以下,其硬件根本访问不了16M以上的内存),因此才分会ZONE_DMA。ZONE_DMA分多大由硬件决定,例如,某些体系结构在内存的任何地址上执行DMA都没有问题,在这些体系结构上,ZONG_DMA为空。

    ZONE_DMA的内存不是专用于DMA的,而是有缺陷的DMA要申请内存时,从这个区域申请,如某个驱动模块调用void*dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_tgfp);函数申请一个ZONE_DMA内存区域时,需要将gfp参数写为GFP_DMA。

    2)ZONE_HIGHMEM,ZONG_NORMAL

    IA32下,0~3G是用户空间的虚拟地址,3G~4G是内核空间的虚拟地址。Linux为了使内核访问内存简单化,一开机就把一段物理地址直接线性映射到3G以上的虚拟地址。注意,物理内存可能大于1G,是无法全部线性映射到虚拟3G~4G的虚拟地址空间的,所以,Linux直接在物理内存上做界限(32位x86系统这个界限为896MB),低于这个界限的才做一一映射,低于这个界限的称为low memory,高于这个界限的称为high memory。low memory包含了ZONG_NORMAL+ZONE_DMA。注意:low memory虽然开机即线性映射,但并不意味着low memory已经被内核用掉,内核要使用内存时与应用程序一样是需要申请的。

    low memory的物理地址和虚拟地址是一个直接的线性映射,可以使用内核API: phys_to_virt和virt_to_phys在物理地址和虚拟地址之间直接映射,而high memory则不能用这两个API。

    内核空间一般不使用high memory,kmalloc申请的内存一般都在low memory。内核空间使用high memory时调用kmap进行映射。x86会把highmemory映射到虚拟地址3G以上,而ARM会映射到3G-2M~3G。如下两图所示:

    640?wx_fmt=png

    640?wx_fmt=png

    综上,high memory产生的原因是不可能在虚拟地址映射区将所有的物理地址都做一一映射;ZONE_DMA产生的原因是DMA引擎硬件缺陷导致。x86-32上分区总结如下:

    640?wx_fmt=png

    注意,区的划分没有任何物理意义,只不过是内核为了管理页而采取的一种逻辑上的分组。

    3.    Linux Buddy分配算法

    640?wx_fmt=png

    DMA、常规、高端内存这3个区域都采用buddy算法进行管理,把空闲的页以2的n次方为单位进行管理,因此Linux最底层的内存申请都是以2n 为单位的。Buddy算法最主要的的特点任何时候区域里的空闲内存都能以2的n次方进行拆分或合并。

    例如,假设ZONE_NORMAL有16页内存(24),此时有人申请一页内存,Buddy算法会把剩下的15页拆分成8+4+2+1,放到不同的链表中去。此时再申请4页,直接给4页,若再申请4页,则从8页中给4页,正好剩下4页。Buddy算法的精髓在于任何正整数都可以拆分成2的n次方之和。

    通过/proc/buddyinfo可以看到内存空闲的一些情况: 

    640?wx_fmt=png

    4.    CMA(连续内存分配器)

    Buddy算法会导致内存碎片化,引出CMA技术。

    640?wx_fmt=png

    应用程序的虚拟地址映射到哪个物理地址,物理地址连续与否都没有关系。但DMA引擎中没有MMU,有时候是需要连续物理内存的。比如,一个camera中有一个DMA,需要用DMA将拍摄的一张图片从camera搬移到内存,这时DMA申请连续16M内存进行搬移,但此时即使物理内存空闲100M(但不连续),DMA也申请不到的。

    为了解决camera例子的问题,Linux可以一开机就将16M内存预留给camera,即使不用也占着浪费掉,而CMA技术就是为了避免内存浪费,使16M内存不预留,应用程序可以用,一旦camera要用,则CMA把这16M内存挤出来给camera。

    由于应用程序的虚拟地址映射到哪个物理地址,连续与否都无所谓,甚至将虚拟地址move到别的物理地址都没关系,只要虚拟地址不变应用程序都不会察觉,所以一般应用程序申请内存时可以附带movable标记,Linux就可以把带movable标记的应用程序的内存申请放到CMA区域中。当camera DMA申请内存时,就“漫山遍野“申请很多4K(一页)内存,把movable的应用程序的物理地址搬移到这些内存中去,注意搬移内存会修改应用程序的页表项,虽然虚拟地址不变,但到物理地址的映射已经改变。这样之前的16M内存就被挤出来给camera的DMA了。

    注意CMA是与DMA的API结合起来使用的,当使用DMA的API来申请内存时,才会触发CMA机制。

    可以在dts中指定哪一段内存做CMA,既可以指定一个默认全局的CMA池,也可以给某一个特定的设备指定一个CMA池。具体实现方法详见内核文档Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt

    640?wx_fmt=png

  • 相关阅读:
    c++的deque和queue和stack
    c++ vector和set的区别
    c++ set的用法
    c++map的用法
    c++总的map和set有什么区别,如何实现的
    1208. Get Equal Substrings Within Budget
    1089. Duplicate Zeros
    1202. Smallest String With Swaps
    1122. Relative Sort Array
    1144. Decrease Elements To Make Array Zigzag
  • 原文地址:https://www.cnblogs.com/zzb-Dream-90Time/p/8995091.html
Copyright © 2011-2022 走看看