Linux内核内存分配

1.原理说明  Linux内核中采 用了一种同时适用于32位和64位系统的内 存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系 统中,用到了四级页表,如图2-1所示。四级页表分别为:  * 页全局目录(Page Global Directory)  * 页上级目录(Page Upper Directory)  * 页中间目录(Page Middle Directory)  * 页表(Page Table)  页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址,每一个页表项指 向一个页框。Linux中采用4KB大小的 页框作为标准的内存分配单元。  多级分页目录结构  1.1.伙伴系统算法  在实际应用中,经常需要分配一组连续的页框,而频繁地申请和释放不同大小的连续页框,必然导致在已分配页框的内存块中分散了许多小块的 空闲页框。这样,即使这些页框是空闲的,其他需要分配连续页框的应用也很难得到满足。  为了避免出现这种情况,Linux内核中引入了伙伴系统算法(buddy system)。把所有的空闲页框分组为11个 块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。最大可以申请1024个连 续页框,对应4MB大小的连续内存。每个页框块的第一个页框的物理地址是该块大小的整数倍。  假设要申请一个256个页框的块,先从256个页框的链表中查找空闲块,如果没有,就去512个 页框的链表中找,找到了则将页框块分为2个256个 页框的块,一个分配给应用,另外一个移到256个页框的链表中。如果512个页框的链表中仍没有空闲块,继续向1024个页 框的链表查找,如果仍然没有,则返回错误。  页框块在释放时,会主动将两个连续的页框块合并为一个较大的页框块。  1.2.slab分 配器  slab分配器源于 Solaris 2.4 的 分配算法,工作于物理内存页框分配器之上,管理特定大小对象的缓存,进行快速而高效的内存分配。  slab分配器为每种使用的内核对象建立单独的缓冲区。Linux 内核已经采用了伙伴系统管理物理内存页框,因此 slab分配器直接工作于伙伴系 统之上。每种缓冲区由多个 slab 组成,每个 slab就是一组连续的物理内存页框,被划分成了固定数目的对象。根据对象大小的不同,缺省情况下一个 slab 最多可以由 1024个页框构成。出于对齐 等其它方面的要求,slab 中分配给对象的内存可能大于用户要求的对象实际大小,这会造成一定的 内存浪费。  2.常用内存分配函数  2.1.__get_free_pages  unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)  __get_free_pages函数是最原始的内存分配方式,直接从伙伴系统中获取原始页框,返回值为第一个页框的起始地址。__get_free_pages在实现上只是封装了alloc_pages函 数,从代码分析,alloc_pages函数会分配长度为1<<order的 连续页框块。order参数的最大值由include/linux/Mmzone.h文 件中的MAX_ORDER宏决定,在默认的2.6.18内 核版本中,该宏定义为10。也就是说在理论上__get_free_pages函 数一次最多能申请1<<10 * 4KB也就是4MB的 连续物理内存。但是在实际应用中,很可能因为不存在这么大量的连续空闲页框而导致分配失败。在测试中,order为10时分配成功,order为11则返回错误。  2.2.kmem_cache_alloc  struct kmem_cache *kmem_cache_create(const char *name, size_t size,  size_t align, unsigned long flags,  void (*ctor)(void*, struct kmem_cache *, unsigned long),  void (*dtor)(void*, struct kmem_cache *, unsigned long))  void *kmem_cache_alloc(struct kmem_cache *c, gfp_t flags)  kmem_cache_create/ kmem_cache_alloc是基于slab分配器的一种内存分配方式,适用于反复分配释放同一大小内存块的场合。首先用kmem_cache_create创建一个高速缓存区域,然后用kmem_cache_alloc从 该高速缓存区域中获取新的内存块。 kmem_cache_alloc一次能分配的最大内存由mm/slab.c文件中的MAX_OBJ_ORDER宏 定义,在默认的2.6.18内核版本中,该宏定义为5, 于是一次最多能申请1<<5 * 4KB也就是128KB的 连续物理内存。分析内核源码发现,kmem_cache_create函数的size参数大于128KB时会调用BUG()。测试结果验证了分析结果,用kmem_cache_create分 配超过128KB的内存时使内核崩溃。  2.3.kmalloc  void *kmalloc(size_t size, gfp_t flags)  kmalloc是内核中最常用的一种内存分配方式,它通过调用kmem_cache_alloc函 数来实现。kmalloc一次最多能申请的内存大小由include/linux/Kmalloc_size.h的 内容来决定,在默认的2.6.18内核版本中,kmalloc一 次最多能申请大小为131702B也就是128KB字 节的连续物理内存。测试结果表明,如果试图用kmalloc函数分配大于128KB的内存,编译不能通过。  2.4.vmalloc  void *vmalloc(unsigned long size)  前面几种内存分配方式都是物理连续的,能保证较低的平均访问时间。但是在某些场合中,对内存区的请求不是很频繁,较高的内存访问时间也 可以接受,这是就可以分配一段线性连续,物理不连续的地址,带来的好处是一次可以分配较大块的内存。图3-1表 示的是vmalloc分配的内存使用的地址范围。vmalloc对 一次能分配的内存大小没有明确限制。出于性能考虑,应谨慎使用vmalloc函数。在测试过程中, 最大能一次分配1GB的空间。  Linux内核部分内存分布  2.5.dma_alloc_coherent  void *dma_alloc_coherent(struct device *dev, size_t size,  ma_addr_t *dma_handle, gfp_t gfp)  DMA是一种硬件机制,允许外围设备和主存之间直接传输IO数据,而不需要CPU的参与,使用DMA机制能大幅提高与设备通信的 吞吐量。DMA操作中,涉及到CPU高速缓 存和对应的内存数据一致性的问题,必须保证两者的数据一致,在x86_64体系结构中,硬件已经很 好的解决了这个问题, dma_alloc_coherent和__get_free_pages函数实现差别不大,前者实际是调用__alloc_pages函 数来分配内存,因此一次分配内存的大小限制和后者一样。__get_free_pages分配的内 存同样可以用于DMA操作。测试结果证明,dma_alloc_coherent函 数一次能分配的最大内存也为4M。  2.6.ioremap  void * ioremap (unsigned long offset, unsigned long size)  ioremap是一种更直接的内存“分配”方式,使用时直接指定物理起始地址和需要分配内存的大小,然后将该段 物理地址映射到内核地址空间。ioremap用到的物理地址空间都是事先确定的,和上面的几种内存 分配方式并不太一样,并不是分配一段新的物理内存。ioremap多用于设备驱动,可以让CPU直接访问外部设备的IO空间。ioremap能映射的内存由原有的物理内存空间决定,所以没有进行测试。  2.7.Boot Memory  如果要分配大量的连续物理内存,上述的分配函数都不能满足,就只能用比较特殊的方式,在Linux内 核引导阶段来预留部分内存。  2.7.1.在内核引导时分配内存  void* alloc_bootmem(unsigned long size)  可以在Linux内核引导过程中绕过伙伴系统来分配大块内存。使用方法是在Linux内核引导时,调用mem_init函数之前 用alloc_bootmem函数申请指定大小的内存。如果需要在其他地方调用这块内存,可以将alloc_bootmem返回的内存首地址通过EXPORT_SYMBOL导 出,然后就可以使用这块内存了。这种内存分配方式的缺点是,申请内存的代码必须在链接到内核中的代码里才能使用,因此必须重新编译内核,而且内存管理系统 看不到这部分内存,需要用户自行管理。测试结果表明,重新编译内核后重启,能够访问引导时分配的内存块。  2.7.2.通过内核引导参数预留顶部内存  在Linux内核引导时,传入参数“mem=size”保留顶部的内存区间。比如系统有256MB内 存,参数“mem=248M”会预留顶部的8MB内存,进入系统后可以调用ioremap(0xF800000,0x800000)来申请这段内存。  3.几种分配函数的比较 分配原理 最大内存 其他 __get_free_pages 直接对页框进行操作 4MB 适用于分配较大量的连续物理内存 kmem_cache_alloc 基于slab机制实现 128KB 适合需要频繁申请释放相同大小内存块时使用 kmalloc 基于kmem_cache_alloc实现 128KB 最常见的分配方式,需要小于页框大小的内存时可以使用 vmalloc 建立非连续物理内存到虚拟地址的映射 物理不连续,适合需要大内存,但是对地址连续性没有要求的场合 dma_alloc_coherent 基于__alloc_pages实现 4MB 适用于DMA操 作 ioremap 实现已知物理地址到虚拟地址的映射 适用于物理地址已知的场合,如设备驱动 alloc_bootmem 在启动kernel时,预留一段内存,内核看不见 小于物理内存大小,内存管理要求较高

===============================

kmalloc, vmalloc分配的内存结构 对于提供了MMU(存储管理器,辅助操作系统进行内存管理,提供虚实地址转换等硬件支持)的处理器而言,Linux提供了复杂的存储管理系统,使得进程所能访问的内存达到4GB。  进程的4GB内存空间被人为的分为两个部分–用户空间与内核空间。用户空间地址分布从0到3GB(PAGE_OFFSET,在0x86中它等于0xC0000000),3GB到4GB为内核空间。  内核空间中,从3G到vmalloc_start这段地址是物理内存映射区域(该区域中包含了内核镜像、物理页框表mem_map等等),比如我们使用的VMware虚拟系统内存是160M,那么3G~3G+160M这片内存就应该映射物理内存。在物理内存映射区之后,就是vmalloc区域。对于160M的系统而言,vmalloc_start位置应在3G+160M附近(在物理内存映射区与vmalloc_start期间还存在一个8M的gap来防止跃界),vmalloc_end的位置接近4G(最后位置系统会保留一片128k大小的区域用于专用页面映射)     kmalloc和get_free_page申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系,virt_to_phys()可以实现内核虚拟地址转化为物理地址: #define __pa(x) ((unsigned long)(x)-PAGE_OFFSET) extern inline unsigned long virt_to_phys(volatile void * address) {  return __pa(address); }上面转换过程是将虚拟地址减去3G(PAGE_OFFSET=0XC000000)。与之对应的函数为phys_to_virt(),将内核物理地址转化为虚拟地址: #define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET)) extern inline void * phys_to_virt(unsigned long address) {  return __va(address); }virt_to_phys()和phys_to_virt()都定义在include/asm-i386/io.h中。而vmalloc申请的内存则位于vmalloc_start~vmalloc_end之间,与物理地址没有简单的转换关系,虽然在逻辑上它们也是连续的,但是在物理上它们不要求连续。我们用下面的程序来演示kmalloc、get_free_page和vmalloc的区别:#include #include #include MODULE_LICENSE("GPL");unsigned char *pagemem;unsigned char *kmallocmem;unsigned char *vmallocmem;int __init mem_module_init(void){ //最好每次内存申请都检查申请是否成功 //下面这段仅仅作为演示的代码没有检查 pagemem = (unsigned char*)get_free_page(0); printk("pagemem addr=%x", pagemem); kmallocmem = (unsigned char*)kmalloc(100, 0); printk("kmallocmem addr=%x", kmallocmem); vmallocmem = (unsigned char*)vmalloc(1000000); printk("vmallocmem addr=%x", vmallocmem); return 0;}void __exit mem_module_exit(void){ free_page(pagemem); kfree(kmallocmem); vfree(vmallocmem);}module_init(mem_module_init);module_exit(mem_module_exit);  我们的系统上有160MB的内存空间,运行一次上述程序,发现pagemem的地址在0xc7997000(约3G+121M)、kmallocmem地址在0xc9bc1380(约3G+155M)、vmallocmem的地址在0xcabeb000(约3G+171M)处,符合前文所述的内存布局。 文件:v_k_malloc.tar.bz2大小:6KB下载:下载 vmalloc和kmalloc区别 kmalloc对应于kfree,可以分配连续的物理内存;vmalloc对应于vfree,分配连续的虚拟内存,但是物理上不一定连续。vmalloc分配内存的时候逻辑地址是连续的,但物理地址一般是不连续的,适用于那种一下需要分配大量内存的情况,如insert模块的时候。这种分配方式性能不入kmalloc。kmalloc分配内存是基于slab,因此slab的一些特性包括着色,对齐等都具备,性能较好。物理地址和逻辑地址都是连续的最主要的区别是分配大小的问题。比如你需要28个字节,那一定用KMALLOC,如果用VMALLOC,分配不多次机器就罢工了。 nPAGE_OFFSET为3GB,high_memory为保存物理地址最高值的变量,VMALLOC_START为非连续区的起始地址 在物理地址的末尾与第一个内存区之间插入了一个8MB的区间,这是一个安全区,目的是为了“捕获”对非连续区的非法访问。出于同样的理由,在其他非连续的内存区之间也插入了4K大小的安全区。每个非连续内存区的大小都是4096的倍数。 n vmalloc()与 kmalloc()都可用于分配内存 ü kmalloc()分配的内存处于3GB~high_memory之间,这段内核空间与物理内存的映射一一对应ü vmalloc()分配的内存在VMALLOC_START~4GB之间,这段非连续内存区映射到物理内存也可能是非连续的n vmalloc() 分配的物理地址无需连续,而kmalloc() 确保页在物理上是连续的 n 尽管仅仅在某些情况下才需要物理上连续的内存块,但是,很多内核代码都调用kmalloc(),而不是用vmalloc()获得内存。n 这主要是出于性能的考虑。vmalloc()函数为了把物理上不连续的页面转换为虚拟地址空间上连续的页,必须专门建立页表项。还有,通过vmalloc()获得的页必须一个一个的进行映射(因为它们物理上不是连续的),这就会导致比直接内存映射大得多的缓冲区刷新。n 因为这些原因,vmalloc()仅在绝对必要时才会使用——典型的就是为了获得大块内存时,例如,当模块被动态插入到内核中时,就把模块装载到由vmalloc()分配的内存上。

大把大把的时光从指缝间遛走,

Linux内核内存分配

相关文章:

你感兴趣的文章:

标签云: