分类: LINUX
2013-12-10 14:39:31
原文地址:Linux内存管理FAQ 作者:zyd_cu
1. linux的内存管理的单位?
linux操作系统使用分页内存管理的机制,将内存空间划分成多个页面(页框,大小通常为4K),内核使用struct page结构的页描述符来保存一个页框的状态信息,所有的页描述符存放在全局的mem_map的数组中,每个page结构体大小为32B,故mem_map所占的空间小于整个内存空间的1%(页面大小按4K计算)。
2. 如何标识一个页面是否空闲?
页描述符中包含一个_count的字段,用于页的引用计数。页框不空闲时,其包含的数据可能用户空间进程的数据、某个软件高速缓存的数据、动态分配的内核数据结构、设备驱动程序缓冲的数据、内核模块的代码等。
3. linux是否支持NUMA?
linux-2.6支持非一致性的内存访问(NUMA)模型(CPU对不同内存单元的访问时间可能不一样)。在这种模型中,系统的物理内存被划分为几个节点,在一个单独的节点内,任意给定CPU访问页面所需要的时间是相同的。
4. 为什么要对内存进行分区管理?
linux将真个内存区域划分为三个部分,分别为DMA区,NORMAL区,HIGHMEM区(高端内存)。上面的划分主要基于:
1. ISA总线的使用24条地址线,其能寻址的空间为16M(224),即其只能访问RAM的前16M空间。
2. 在32位的计算机中(内核使用3G-4G地址空间),CPU不能直接访问所有的物理内存。linux将896MB以上的内存区域称为高端内存,把16M~896M的内存则为普通的内存区域,通过把0~896M的内存线性的映射到3G~3G+896M的线性地址上,内核可以直接进行访问。而内核通过永久内核映射、临时内核映射即非连续内存分配等方式访问高端内存。
5. 如何描述4中的三个区?
管理区描述符struct zone用于描述各个管理区,其主要包含管理区名字,管理区中活动页链表,非活动页链表,管理区中空闲页数,管理区中保留页数,回收页框的上下界,管理区的伙伴块数组,指向该区第一个页的指针,页数等信息。
另外,每个页描述符(struct page)中都包含有到内存节点和到节点内存管理区的链接,其信息被编码在flags字段中。
6. 各个管理区中为什么要保留一些页框?
通常,当请求内存时,如果有足够的空闲空间,请求会被立刻满足;否则必须回收一些内存,并且将发出的请求的内核控制路径阻塞,直到有内存被释放。但有些内核控制路径不能被阻塞(如处理中断或是在执行临界区内的代码时,应采用GFP_ATOMIC内存分配标志),原子请求从来不被阻塞,如果没有足够的空闲页,则分配失败。
内核为尽量减少原子内存分配失败的情形,为原子内存分配保留了一个页框池,只有在内存不足时才使用,保留页框数的大小保存在管理区描述符的pages_min字段中。
7. 内核提供了哪些页面分配接口?
alloc_pages(gfp_mask, order); 分配2order个页面,返回第一个页面的描述符。
alloc_page(gfp_mask, order); 实际上是alloc_pages(gfp_mask, 0);
__free_pages(page, order); 释放page开始的2order个页面
__free_page(page);
get_free_pages(gfp_mask, order); 分配2order个页面,返回第一个分配页的线性地址
get_free_page(gfp_mask);
free_pages(addr, order); 释放page开始的2order个页面
free_page(addr);
get_zero_page(gfp_mask); 返回填满0的页框
get_dma_pages(gfp_mask, order); 指定从DMA去申请页框
8. 高端内存页框如何映射?
永久内核映射允许内核建立高端页框到内核地址空间的长期映射。它们使用主内核页表中的一个专门的页表,其地址存放在pkmap_page_table中变量中。页表中包含LAST_PKMAP个条目,通常为512或1024。同时,为了记住高端内存页框与永久内核映射包含的线性地址之间的联系,内核使用page_addr_htable的散列表,该表包含一个page_address_map的数据结构,其包含一个指向页描述符的指针和分配给该页框的线性地址。
为了实现临时内核映射,每个CPU都有自己的包含13个窗口的集合,用enum km_type数据结构表示,该数据结构中定义的每个符号,如KM_BOUNCE、KM_READ,标识了窗口的线性地址,内核必须保证确保同一窗口永不会被两个不同的控制路径同时使用。
关于高端内存映射更详细的信息,请参考【1】【2】【3】【4】。
9. 什么是伙伴系统算法?
为了尽量避免内存外碎片, linux采用伙伴系统算法来管理空闲页框,其把所有的空闲页框分为11组(使用11个链表组织), 分别包含大小为1-1024个页框的连续RAM块。
问题7中的接口均建立在伙伴系统算法之上,根据order参数,内核从第order个链表开始查找,找到第一个满足条件的RAM块。如需要申请256个页框时,首先查找大小为256的链表,如果有空闲块,则返回第一个页的地址;如果没有空闲块,则从大小为512的链表中查找,如果找到,则将该块剩余的256个页框加入到大小为256的RAM块链表中;如果仍然没有找到,则从大小为1024的RAM块链表中查找。
如果在大小为1024的RAM块中有空闲的块,则将剩下的768个页框分为一个512和一个256个页框的块,分别加入到相应大小的链表中。当释放页时,则将页归还到对应的链表中,并试图合并相邻的块以构成更大的块。
对于划分的方法,我有点小小的看法,内核中是将后面的768个页面分成512 + 256 的形式,我觉得应该分为256 + 512的形式,这样能更加的利用内存。我的分析如下:
假设系统有1024个页的内存,一个256个页的请求,得到了前面的256个页,257-768这512个页被加入到512个页面的块链表中,后面的256个页面则被加入到256个页面的块链表中,接下来一个256个页的请求将得到后面的256个页面;此时即使两次请求的页面都被释放,则会产生二个256个页面的块和一个512个页面的块。但如果采用我的划分方法,则可合并成一个1024个页面的块。
10. 什么是slab分配器?
伙伴系统算法采用页框作为基本内存区,这适合于对大块内存的请求,但内核中经常需要用到小的数据结构(如inode,dentry等)如果为了存放很少的字节而分配给它一整个页框,则内存浪费太大,正确的方法是引入一种新的数据结构来描述在同一页框中如何分配小内存区。一种典型的解决方法是提供按几何分布的内存区大小,即内存区大小取决于2的幂次而不取决于所存放数据的大小,保证了不管请求大小是多少,保证内存碎片少于50%,为此,内核建立了13个按几何分布的空闲内存区链表,大小从32B到131072B,kmalloc的内存分配就是建立在该机制上的。
Linux 所使用的 slab 分配器是围绕对象缓存进行的。在内核中,会为有限的对象集(例如文件描述符和其他常见结构)分配大量内存,对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。因此不应该将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。
slab的实现原理及接口更详细的信息可参考【1】【5】【6】
11. 如何分配非连续的内存区?
vmalloc接口给为内核分配一个非连续的内存区,vmalloc是基于页框的分配接口,每个非连续的内存区由一个struct vm_struct的描述符表示(包含非连续内存去包含的页数,页描述符数组等信息),vmalloc本质上通过alloc_pages来申请多个单个页,并把这些页组成填充到vm_struct结构中以构成非连续的内存区,最后修改内核页表项,以表明分配给非连续内存区的每个页框对应着一个线性地址,这个线性地址包含在vmalloc产生的非连续线性地址区间中。
内存管理是内核中至关重要的子系统,涉及到的内容繁多,本来想好好的把这一块综述一下,无奈能力有限,而且网上资料也是铺天盖地,我也不想再敲重复的内容,权当是自己的一个小总结吧!
参考资料:
【1】 深入理解linnux内核第三版,中国电力出版社
【2】 http://blog.csdn.net/yunsongice/archive/2010/01/26/5258589.aspx
【3】 %CE%DE%D0%C4%CF%F2%BA%F3/blog/item/e8c086d34f0711d9562c8444.html
【4】 %CE%DE%D0%C4%CF%F2%BA%F3/blog/item/68c63e13fecd5374cb80c444.html
【5】 https://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/
【6】 _lelechong_/blog/item/facef7a5c6093e854710649c.html