前几篇文章讨论的进程和进程调度相关的知识。 [TOC] ## 内存管理 进程调度主要是逻辑和算法的知识,而内存管理相对复杂,涉及到硬件和软件,从微机原理到应用程序到内核。比如,硬件上的cache,CPU如何去寻址内存,页表, DMA,IOMMU。 软件上,要知道底层怎么分配内存,怎么管理内存,应用程序怎么申请内存。 常见的误解包括: 1. 对free命令 cache和buffer的理解。 2. 应用程序申请10M内存,申请成功其实并没有分配。内存其实是边写边拿。代码段有10M,并不是真的内存里占了10M。 内存管理学习难,一是网上的资料不准确,二是学习时执行代码,具有欺骗性。看到的东西不一定真实,要想理解必须陷入Linux本身。 学习时,不要过快陷入太多细节,而要先对整个流程整个框架理解。 先理清楚脉络和主干,从硬件到最底层内存的分配算法,-->到内核的内存分配算法,-->应用程序与内核的交互,-->到内存如何做磁盘的缓存, --> 内存如何和磁盘替换。 再动手实践demo。 ## 硬件原理 和 分页管理 本文主要让大家理解内存管理最底层的buddy算法,内存为什么要分成多个Zone? * CPU寻址内存,虚拟地址、物理地址 * MMU 以及RWX权限、kernel和user模式权限 * 内存的zone: DMA、Normal和HIGHMEM * Linux内存管理Buddy算法 * 连续内存分配器(CMA) ### 内存分页 ![分页机制](https://box.kancloud.cn/4eb88ca2f1b06e2c89519ca21b42c834_2104x1348.jpeg) CPU 一旦开启MMU,MMU是个硬件。CPU就只知道虚拟地址了。如果地址是32位,0x12345670 。 假设MMU的管理是把每一页的内存分成4K,那么其中的670是页内偏移,作为d;0x12345 是页号,作为p。通过虚拟地址去查对应的物理地址,用0x12345去查一张页表,页表(Page table)本身在内存。 硬件里有寄存器,记录页表的基地址,每次进程切换时,寄存器就会更新一次,因为每个进程的页表不同。 CPU一旦访问虚拟地址,通过页表查到页表项,页表项记录对应的物理地址。 总结:一旦开启MMU,CPU只能看到虚拟地址,MMU才能看到物理地址。 虚拟地址是指针,物理地址是个整数。内存中的一切均通过虚拟地址来访问。 typedef u64 phys_addr_t; 去内存里读取页表会比较慢,CPU里有个高速单元tlb,它是页表的高速缓存。CPU就不需要在内存里读页表,直接在tlb中读取,从虚拟地址到物理地址的映射。如果tlb中读取不到,才回到内存里读取页表映射,并且在tlb中命中。 虚拟地址:0x12345 670 --> 1M 物理地址:1M+670 MMU去访问这个物理地址。 内存的映射以页为单位。 * * * * * ### 页表(Page table)记录的页权限 cpu虚拟地址,mmu根据cpu请求的虚拟地址,访问页表,查得物理地址。 每个MMU中的页表项,除了有虚拟地址到物理地址的映射之外,还可以标注这个页的 RWX权限和 kernel和user模式权限(用户空间,内核空间读取地址的权限),它们是内存管理两个的非常重要的权限。 一是,这一页地址的RWX权限 ,标记这4k地址的权限。一般用来做保护。 Pagefault,是CPU提供的功能。两种情况会出现Pagefault,一是,CPU通过虚拟地址没有查到对应的物理地址。二是,MMU没有访问物理地址的权限。 MPU,memory protection unit. 二是,MMU的页表项中,还可以标注这一页的地址:可以在内核态访问,还是只能在用户态访问。用户一般映射到0~3G,只有当CPU陷入到内核模式,才可以访问3G以上地址。 程序在用户态运行,处于CPU非特权模式,不能访问特权模式才能访问的内存。内核运行在CPU的特权模式,从用户态陷入到内核态,发送 软中断指令,CPU进行切环,x86从3环切到0环,到一个固定的地址去执行。软件就从非特权模式,跳到特权模式去执行。 MMU,能把某一段地址指定为只有特权模式才能够访问,会把内核空间3G以上的页表项里的每一行,指定为只有CPU 0环才能访问。应用程序没有陷入到内核态,是无法访问内核态的东西。 * * * * * intel的漏洞meltdown,就是让用户可以在用户态读到内核态的东西。 meltdown 攻击原理: 基于时间的 旁路攻击 side-channel 李小璐买汉堡的故事 --> 安全的基于时间的旁路攻击技巧。 比如试探用户名,密码。比如一个软件比较傻,每次第一个字母就不对,就不对比第2个字母了。那我每次26个字母实验换一次,看哪个字母反弹地最慢,就证明是这个字母的密码。 密码是abc, 我敲了d,那么第一个字母就不对,软件这个时候如果快速的返回出错,我知道首字母不是d,我可以实验出来首字母是a,然后接着一个个字母实,就可以把密码试探出来了。类似地原理。。 * * * * * 下面的这个例子,演示 page table记录的RWX权限的作用 ![const常量](https://box.kancloud.cn/e07154a59139042462824bc735b6da58_1550x1060.jpeg) 页表的权限,RWX权限,和 用户空间,内核空间读取的权限。 * * * * * ### 内存分Zone 下面解释内存为什么分Zone? DMA zone. ![内存分zone](https://box.kancloud.cn/b2d86b4467b8c1426d59fe1c89980e2b_1444x1048.jpeg) 内存的分Zone,全都是物理地址的概念。内存条,被分为三个Zone。 分DMA Zone的原因,是DMA引擎的缺陷。DMA引擎 可以直接访问内存空间的地址,但不一定能够访问到所有的内存,访问内存时会存在一定的限制。 当CPU 和DMA同时访问内存时,硬件上会有仲裁器,选择优先级高的去访问内存。 为什么要切DMA zone? DMA Zone的大小,是由硬件决定的。访问不到更高的内存。 什么叫做 normal zone? highmem zone? highmem和lowmem 都是指的内存条,在虚拟地址空间,只能称为highmem,lowmem映射区。 如上图,内存虚拟地址空间0~4G,3~4G是内核空间的虚拟地址,0-3G 是用户空间的虚拟地址。 内核空间,访问任何一片内存都要虚拟地址。Linux为了简化内存访问,开机就把lowmem的物理地址一一映射到虚拟地址。highmem 地址包括了 normal + DMA。 ![lowmem](https://box.kancloud.cn/5478fb217a832b55233fd1e9f3740501_740x540.jpeg) lowmem是开机就直接映射好的内存,CPU访问这片内存,也是通过3G以上的虚拟地址。这段地址的虚拟地址和物理地址是直接线性映射,通过linux的两个api (phys_to_virt / virt_to_phys)在虚拟和物理之间进行映射, highmem 不能直接用。 内核空间一般不使用highmem,内核一般使用kmalloc在lowmem申请内存,使用 kmmap在highmem 申请内存。lowmem 映射了,并不代表被内核使用掉了,只是不需要重建页表。内核使用lowmem内存,同样是要申请。 应用程序一样可以申请 lowmem 和highmem。 总结: 内存分highmem zone的原因,地址空间整体不够。 DMA zone产生的原因,硬件DMA引擎的访问缺陷。 ![DMA](https://box.kancloud.cn/a47f97aae87b27e674070a7ea1a37ed7_1464x1090.jpeg) * * * * * ### 硬件层的内存管理- buddy算法 每个zone都会使用buddy算法,把所有的空闲页面变成2的n次方进行管理。 /proc/buddyinfo 通过/proc/buddyinfo,可以看出空闲内存的情况 ![/proc/buddyinfo](https://box.kancloud.cn/70ad77befc24f4401601aa2fc5a3260a_946x494.png) CPU寻址内存的方法:通过MMU提供的虚拟地址到物理地址的映射访问。 ### 如何处理内存碎片 ![内存碎片](https://box.kancloud.cn/1dba77dbd793d0aa0a0e3508382993d5_924x628.png) X86 linux 内核有一个线程 compaction, 会进行内存碎片整理,会尽量移出大内存。 CMA:continuous memory allocation 内核把虚拟地址 指向新的物理地址,让应用程序毫无知觉情况,把64M内存腾出来给DMA。当用DMA的api申请内存,会走到CMA。在dts中指定哪块区域做CMA。 Documentation/devicetree/bindings reserved-memory/reserved-memory.txt dma_alloc_coherent ### CMA, iommu, CMA主要是给需要连续内存的DMA用的。但是为了避免DMA不用的时候浪费,才在DMA不用的时候给可移动的页面用。不能移动的页面,不能从CMA里面拿。所以主要是APP和文件的page cache的内存,才可以在CMA区域拿。 这样当DMA想拿CMA区域的时候,要么移走,要么抛弃。总之,必须保证DMA需要这片CMA区域的时候,之前占着CMA的统统滚蛋。 不具备滚蛋能力的内存,不能从CMA区域申请。你申请也滚蛋不了,待会DMA上来用的时候,DMA就完蛋了。 要搞清楚CMA的真正房东是那些需要连续内存的DMA,其他的人都只是租客。DMA要住的时候,租客必须走。哪个房东会把房子租给一辈子都不准备走的人?内核绝大多数情况下的内存申请,都是无法走的。应用走起来很容易,改下页面就行了。 CMA和不可移动之间,没有任何交集。CMA唯一的好处是,房东不住的时候,免得房子空置。