• 1. Linux内存管理简介向宇 yu.xiangy@alibaba-inc.com
  • 2. 内容提要关于虚拟内存 地址映射 -- 虚拟内存到物理内存的转换 虚拟内存管理 –- 地址映射的源管理 建立地址映射 物理内存管理 –- 地址映射的目的管理 libc的内存管理
  • 3. 关于虚拟内存一个简单的程序: void main() { int i = (int)time(NULL); while (getchar()) printf("addr=%p, val=%d\r\n", &i, i); } 同时运行多个实例(进程)后观察,各个实例输出的addr都相同,而val却不同。说明在不同进程中,同一内存可能有着不同的值; 系统中的各个进程生活在各自的虚拟内存空间中,互不影响;
  • 4. 虚拟内存与物理内存同样的地址值,出现在不同的虚拟内存空间中,很可能就对应到不同的物理内存: 一块虚拟内存可能单独对应到一块物理内存、也可能与其他虚拟内存同时对应同一块物理内存,也可能没有对应的物理内存; 内存块的最小单位是“页”,X86下页的大小是4K;
  • 5. 地址映射实现从虚拟内存到物理内存的转换过程,由CPU的辅助部件mmu来完成; CPU执行的指令中给出的内存地址都是虚拟地址。CPU在执行指令的过程中需要访问内存时,由mmu将虚拟地址转换成物理地址; 地址映射的规则由内核以页表的形式提供,页表描述了虚拟地址XXX应该转换成物理地址YYY; 一张页表确定了一种转换规则,也就确定了一个虚拟内存空间。多个虚拟内存空间需要多张页表来支持。同一CPU在同一时刻只能使用一张页表;
  • 6. 地址映射(2)页表是存放在内存中的,则内核来编辑; 将页表的首地址设置到某个CPU寄存器(如X86下的CR3),mmu就会使用该页表做地址映射; CR3中存放的是页表首地址的物理地址; 页表的表项的index跟虚拟地址相关联(如虚拟地址M关联第N个表项),表项的value就是对应的物理地址值; 一般只有内存空间的一小部分是需要做映射的,为了节省空间,页表会分成多级(如X86下是两级)。但是级数越多,映射的消耗就越大;
  • 7. 地址映射(3) 每次内存访问都需要先访问页表,将页表放在高速缓存中犹为重要。这个高速缓存叫做TLB; 页表的切换将导致TLB失效,开销很大;
  • 8. 虚拟内存管理为了构造页表,内核需要管理好虚拟内存,要知道在某个虚拟内存空间中,哪些内存是已分配的,是需要映射的; 在linux内核中,每个进程对应一个task结构,它指向一个mm结构,mm就代表虚拟内存空间; mm结构中维护了一个vma(虚拟内存区)的集合,用户程序对内存的分配与回收都是作用在这些vma之上的; 这些vma代表着进程空间的各个区域,比如堆、栈、代码区、数据区、各种文件映射区、等等。可以通过/proc/$pid/maps查看到;
  • 9. 虚拟内存区绝大多数的vma都是映射到某个文件上的(由用户程序通过mmap系统调用创建、在执行程序或动态链接时自动创建); 也可以通过使用mmap系统调用映射一个NULL文件的方式来分配内存; 堆是其中一个vma,它一端固定、一端可伸缩。通过系统调用brk来调整其大小; 栈也是其中一个vma,它一端固定、一端可伸(不能缩)。这个vma比较特殊,没有类似brk的系统调用让这个vma伸展,而是在访问越界时自动伸展的;
  • 10. 建立地址映射用户程序分配虚拟内存之后,既没有为之建立页表项,又没有分配物理内存,仅仅是操作了vma; 访问没有建立对应页表项的虚拟地址时,CPU将触发一次访存异常,自动执行事先由内核安排好的异常处理程序; 异常处理程序会判断访存异常是由于越界/越权访问引起的,还是由于上述内存分配后未建立映射的情况引起的; 对于前者,内核将给进程一个段错误; 对于后者,内核会建立对应的页表项,然后从异常处理程序中返回。而后CPU会重新执行引起异常的这条指令;
  • 11. 物理内存管理内核在异常处理程序中建立页表项时,必须为其指定一块物理内存(页表项关联到该物理内存); 内核需要对物理内存进行管理。对于每一个物理内存页,有一个page结构与之对应。这些page结构存放在一个数组中,数组下标(page结构的地址)就对应了内存页的物理地址; 对于未被使用的物理页,其对应的page结构会被放入空闲页面列表,等待分配; 分配物理页,就是在空闲页面列表中寻找并取走一个或一组(连续的)合适的page结构。得到的page结构可换算成物理地址,填入页表项; 释放物理页就是将page结构归还空闲页面列表;
  • 12. 伙伴系统空闲页面列表是通过伙伴系统来实现的。把内存块(连续的page结构组)按大小分组管理;
  • 13. libc的内存管理我们通常都是使用libc提供的库函数malloc和free来进行内存的分配与回收; Libc通过一定的算法(比如伙伴系统)管理着堆(vma)的空间; libc对内存的分配有两种途径: 对于小内存分配,尽量从堆中分配空闲的空间,若空间不够则通过brk系统调用调整堆的大小; 对于大内存分配,mmap一个新的vma;
  • 14. libc的内存管理(2)关于libc的内存释放: 对于mmap分配的空间,munmap释放即可; 对于从堆中分配的空间,先要放回堆的空闲空间集合中。如果堆的空闲空间过大,则试图通过brk系统调用减小堆的空间; 两种分配方式的优劣: mmap每次内存分配都需要系统调用;并且增加了vma的数量,vma过多会使查询效率下降; 堆分配需要libc做很多的管理工作;堆空间是很难释放的,因为只能调整这个vma的边界。边界非空闲,则不能调整;
  • 15. 延伸阅读下面一篇文章对上述知识有更详尽的描述: 《linux内存管理浅析》 若希望对linux内核内存管理有更透彻的理解,建议阅读《深入理解linux内核》第三版的相关章节
  • 16. 谢谢~向宇 yu.xiangy@alibaba-inc.com