浅析linux内核内存管理之kmalloc

本文深入探讨了Linux内核中的SLAB高速缓存机制及其组成部分——普通高速缓存和专用高速缓存,并详细解析了kmalloc函数的工作原理,包括内存分配与释放的过程。此外,还介绍了kmalloc在不同内存大小下的行为以及与vmalloc的区别。

在SLAB的高速缓存中有普通高速缓存和专用高速缓存,平时用kmem_cache_create创建的是专用高速缓存,比如存放task_struct,mm_struct的高速缓存。普通高速缓存主要供kmalloc使用。第一个高速缓存叫kmem_cache,存放在cache_cache变量中,这个cache专门用于为其他cache分配描述符。

[cpp]  view plain copy
  1. static kmem_cache_t cache_cache = {  
  2.     .lists      = LIST3_INIT(cache_cache.lists),  
  3.     .batchcount = 1,  
  4.     .limit      = BOOT_CPUCACHE_ENTRIES,  
  5.     .objsize    = sizeof(kmem_cache_t),  
  6.     .flags      = SLAB_NO_REAP,  
  7.     .spinlock   = SPIN_LOCK_UNLOCKED,  
  8.     .name       = "kmem_cache",  
  9. #if DEBUG  
  10.     .reallen    = sizeof(kmem_cache_t),  
  11. #endif  
  12. };  
内存区大小的范围一般包括13个几何分布的内存区。一个叫做malloc_sizes的表(其元素类型为cache_sizes)分别指向26个高速缓存描述符,与其相关的内存区大小为32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072。

[cpp]  view plain copy
  1. struct cache_sizes malloc_sizes[] = {  
  2. #define CACHE(x) { .cs_size = (x) },  
  3. #include <linux/kmalloc_sizes.h>  
  4.     { 0, }  
  5. #undef CACHE  
  6. };  
下面是kmalloc()的实现,kernel 2.6.11:

[cpp]  view plain copy
  1. static inline void *kmalloc(size_t size, int flags)  
  2. {  
  3.         if (__builtin_constant_p(size)) {  
  4.                 int i = 0;  
  5. #define CACHE(x) \  
  6.                 if (size <= x) \  
  7.                         goto found; \  
  8.                 else \  
  9.                         i++;  
  10. #include "kmalloc_sizes.h"  
  11. #undef CACHE  
  12.                 {  
  13.                         extern void __you_cannot_kmalloc_that_much(void);  
  14.                         __you_cannot_kmalloc_that_much();  
  15.                 }  
  16. found:  
  17.                 return kmem_cache_alloc((flags & GFP_DMA) ?  
  18.                         malloc_sizes[i].cs_dmacachep :  
  19.                         malloc_sizes[i].cs_cachep, flags);  
  20.         }  
  21.         return __kmalloc(size, flags);  
  22. }  
注意这里与malloc_sizes都包含了kmalloc_sizes.h头文件,在下边列出:
[cpp]  view plain copy
  1. #if (PAGE_SIZE == 4096)  
  2.     CACHE(32)  
  3. #endif  
  4.     CACHE(64)  
  5. #if L1_CACHE_BYTES < 64  
  6.     CACHE(96)  
  7. #endif  
  8.     CACHE(128)  
  9. #if L1_CACHE_BYTES < 128  
  10.     CACHE(192)  
  11. #endif  
  12.     CACHE(256)  
  13.     CACHE(512)  
  14.     CACHE(1024)  
  15.     CACHE(2048)  
  16.     CACHE(4096)  
  17.     CACHE(8192)  
  18.     CACHE(16384)  
  19.     CACHE(32768)  
  20.     CACHE(65536)  
  21.     CACHE(131072)  
  22. #ifndef CONFIG_MMU  
  23.     CACHE(262144)  
  24.     CACHE(524288)  
  25.     CACHE(1048576)  
  26. #ifdef CONFIG_LARGE_ALLOCS  
  27.     CACHE(2097152)  
  28.     CACHE(4194304)  
  29.     CACHE(8388608)  
  30.     CACHE(16777216)  
  31.     CACHE(33554432)  
  32. #endif /* CONFIG_LARGE_ALLOCS */  
  33. #endif /* CONFIG_MMU */  
可以看到CACHE是一个宏,在malloc_sizes与kmalloc中的含义是不一样的,在malloc_sizes中:
[cpp]  view plain copy
  1. #define CACHE(x) { .cs_size = (x) }  
表示malloc_sizes的成员是13个大小不同的内存区,而kmalloc中:
[cpp]  view plain copy
  1. #define CACHE(x) \  
  2.                 if (size <= x) \  
  3.                         goto found; \  
  4.                 else \  
  5.                         i++;  
就是让32~131072依次与size进行大小比较,开始的时候i=0,如果找到的cache的object size比请求的size大,那么就调用kmem_cache_alloc从相应的cache分配object,这里i++为了对应相应的malloc_sizes[i]。
kmalloc()主要调用__kmalloc()进行分配:

[cpp]  view plain copy
  1. void * __kmalloc (size_t size, int flags)  
  2. {  
  3.     struct cache_sizes *csizep = malloc_sizes;  
  4.   
  5.     for (; csizep->cs_size; csizep++) {  
  6.         if (size > csizep->cs_size)  
  7.             continue;  
  8. #if DEBUG  
  9.         /* This happens if someone tries to call 
  10.          * kmem_cache_create(), or kmalloc(), before 
  11.          * the generic caches are initialized. 
  12.          */  
  13.         BUG_ON(csizep->cs_cachep == NULL);  
  14. #endif  
  15.         return __cache_alloc(flags & GFP_DMA ?  
  16.              csizep->cs_dmacachep : csizep->cs_cachep, flags);  
  17.     }  
  18.     return NULL;  
  19. }  
在__kmalloc中找一个大小比请求的大小大的cache。然后从那个cache中进行分配,对于每种大小,都有两个高速缓存:一个适用于ISA DMA,另一个适用于常规分配。这里调用__cache_alloc()在适当的cache中分配object。
调用kfree释放分配到的内存:

[cpp]  view plain copy
  1. void kfree (const void *objp)  
  2. {  
  3.     kmem_cache_t *c;  
  4.     unsigned long flags;  
  5.   
  6.     if (!objp)  
  7.         return;  
  8.     local_irq_save(flags);  
  9.     kfree_debugcheck(objp);  
  10.     c = GET_PAGE_CACHE(virt_to_page(objp));  
  11.     __cache_free(c, (void*)objp);  
  12.     local_irq_restore(flags);  
  13. }  
这里调用看了__cache_free释放object到cache。
        kmalloc的第一个参数是要分配的块的大小,第二个参数是分配标志(flags)。
       最常用的标志是GFP_KERNEL,它表示内存分配的(最终总是调用get_free_pages来实现实际的分配,这就是GFP_前缀的由来)是代表运行在内核空间的进程执行的。换句话说,这意味着调用它的函数正代表某个进程执行系统调用。使用GFP_KERNEL允许kmalloc在空闲内存较少时把当前进程转入休眠以等待一个页面。因此,使用GFP_KERNEL分配内存的函数必须是可重入的。在当前进程休眠时,内核会采取适当的行动,或者是把缓冲区的内存刷写到硬盘上,或者是从一个用户进程换出内存,以获取一个内存页面。
   GFP分配标志并不是始终适用,有时kmalloc是在进程上下文之外被调用的,例如在中断处理例程,tasklet以及内核定时器中调用。这种情况下current进程就不应该休眠,驱动程序则应该换用GFP_ATOMIC标志。内核通常会为原子性的分配预留一些空闲页面。使用GFP_ATOMIC标志时,kmalloc甚至可以调用最后一个空闲页面。不过如果连最后一页都没有了,分配就返回失败。
   下边来看一些常用的符号,这些符号都包含在<linux/gfp.h>:
GFP_ATOMIC
用于在中断处理例程或其他运行于进程上下文之外的代码中分配内存,不会休眠。
GFP_KERNEL
内核内存的通常分配方法,可能引起休眠。
GFP_USER
用于为用户空间分配内存,可能会引起休眠。
GFP_NOIO
GFP_NOFS
这两个标志的功能类似于GFP_KERNEL,但是为内核分配内存的工作方式添加了一些限制。具有GFP_NOFS标志的分配不允许执行任何文件系统调用,而GFP_NOIO禁止任何I/O的初始化。
__GFP_DMA
该标志请求分配发生在可进行DMA的内存区段中。
__GFP_HIGHMEM
  这个标志表明分配的内存科位于高端内存。
__GFP_COLD
表示从冷高速缓存分配,即per cpu pages的cold page。

下面一个测试程序,模拟mmu查表过程:
[cpp]  view plain copy
  1. #include <linux/init.h>  
  2. #include <linux/module.h>  
  3. #include <linux/kernel.h>  
  4. #include <linux/slab.h>  
  5. #include <linux/gfp.h>  
  6. #include <asm/pgtable.h>  
  7. #include <asm/page.h>  
  8. #include <linux/sched.h>  
  9. #include <linux/mm.h>  
  10. #include <linux/highmem.h>  
  11.       
  12. long unsigned int vaddr;  
  13.   
  14. static int __init kmalloc_test_init(void){  
  15.     pte_t *pte_tmp = NULL;  
  16.     pmd_t *pmd_tmp = NULL;  
  17.     pud_t *pud_tmp = NULL;  
  18.     pgd_t *pgd_tmp = NULL;  
  19.     long unsigned int paddr;  
  20.     printk("kmalloc test init!\n");  
  21.     vaddr = (long unsigned int)kmalloc(4, GFP_KERNEL);  
  22.     pgd_tmp = pgd_offset(current->mm, vaddr);  
  23.     if(pgd_present(*pgd_tmp))  
  24.     {  
  25.         pud_tmp = pud_offset(pgd_tmp, vaddr);  
  26.         if(pud_present(*pud_tmp))  
  27.         {  
  28.             pmd_tmp = pmd_offset(pud_tmp, vaddr);  
  29.             if(pmd_present(*pmd_tmp))  
  30.             {  
  31.                 if(!pmd_large(*pmd_tmp)){  
  32.                         pte_tmp = pte_offset_kernel(pmd_tmp, vaddr);      
  33.                     if(pte_present(*pte_tmp))  
  34.                     {  
  35.                         paddr = (pte_val(*pte_tmp) & PAGE_MASK) | (vaddr & ~PAGE_MASK);  
  36.                         printk("physical address of 0x%lx is 0x%lx\n", vaddr, paddr);  
  37.                         printk("__pa(vaddr) is 0x%lx\n", __pa(vaddr));  
  38.                     }  
  39.                     else  
  40.                     {  
  41.                         printk("pte entry is not present!\n");  
  42.                         return -1;  
  43.                     }  
  44.                 }  
  45.                 else  
  46.                 {  
  47.                     paddr = (pmd_val(*pmd_tmp) & PMD_MASK) | (vaddr & ~PMD_MASK);  
  48.                     printk("Use Large Page PSE = 1\n");  
  49.                     printk("physical address of 0x%lx is 0x%lx\n", vaddr, paddr);  
  50.                     printk("__pa(vaddr) is 0x%lx\n", __pa(vaddr));  
  51.                 }  
  52.             }  
  53.             else  
  54.             {  
  55.                 printk("pte entry is not present!\n");  
  56.             }  
  57.         }  
  58.         else  
  59.         {  
  60.             printk("pud entry is not present!\n");  
  61.             return -1;  
  62.         }  
  63.     }  
  64.     else  
  65.     {  
  66.         printk("pgd entry is not present!\n");  
  67.         return -1;  
  68.     }  
  69.     return 0;  
  70. }  
  71.   
  72. static void __exit kmalloc_test_exit(void){  
  73.     printk("kmalloc test exit!\n");  
  74.     if(vaddr)  
  75.         kfree(vaddr);  
  76. }  
  77.   
  78. module_init(kmalloc_test_init);  
  79. module_exit(kmalloc_test_exit);  
  80.   
  81. MODULE_LICENSE("GPL");  

测试结果:

[cpp]  view plain copy
  1. [  212.054880] kmalloc test init!  
  2. [  212.054883] Use Large Page PSE = 1  
  3. [  212.054884] physical address of 0xf6692b80 is 0x36692b80  
  4. [  212.054886] __pa(vaddr) is 0x36692b80  


KMALLOC  PK VMALLOC 

  • kmalloc是基于slab的,所以速度比较快。vmalloc的内部会调用到kmalloc,但是只是分配vm_struct描述符,和分配nr_pages指针数组,这个数组里的每个元素指向vm_struct对应的非连续内存区域的每一个页描述符结构。真正的分页是通过alloc_page一页一页的从buddy system分配。所以物理地址是不连续的,一页一页分配物理地址不一定啥地方了。kmalloc分配的页已经映射好了,而vmalloc分配后一级一级建立页表很是麻烦。
  • 高端内存使用的是小页,所以使用vmalloc的时候不会打印出"Use Large Page PSE = 1"。
  • 在中断上下文中可以使用kmalloc,前提是使用GFP_ATOMIC标志,而中断上下文中不能用vmalloc替代kmalloc,vmalloc调用kmalloc和alloc_page都使用了GFP_KERNEL标志,这个标志可能因此进程休眠。
  • 用vmalloc分配页修改的是内核页表部分,并没有修改进程的相关项,在访问的时候需要通过page fault来同步,而kmalloc就不需要这一过程。
Linux常见驱动源码分析(kernel hacker修炼之道)--李万鹏 李万鹏 IBM Linux Technology Center kernel team 驱动资料清单内容如下: Linux设备模型(中)之上层容器.pdf Linux设备模型(上)之底层模型.pdf Linux驱动修炼之道-驱动中一些常见的宏.pdf Linux驱动修炼之道-内存映射.pdf Linux驱动修炼之道-看门狗框架源码分析.pdf Linux驱动修炼之道-触摸屏驱动之s3c2410_ts源码分析.pdf Linux驱动修炼之道-SPI驱动框架源码分析(中).pdf Linux驱动修炼之道-SPI驱动框架源码分析(下).pdf Linux驱动修炼之道-SPI驱动框架源码分析(上).pdf Linux驱动修炼之道-RTC子系统框架与源码分析.pdf Linux驱动修炼之道-platform.pdf Linux驱动修炼之道-LCD背光与gpio控制.pdf Linux驱动修炼之道-INPUT子系统(下).pdf Linux驱动修炼之道-INPUT子系统(上).pdf Linux驱动修炼之道-framebuffer(中).pdf Linux驱动修炼之道-framebuffer(下).pdf Linux驱动修炼之道-framebuffer(上).pdf Linux驱动修炼之道-DMA框架源码分析(下).pdf Linux驱动修炼之道-DMA框架源码分析(上).pdf Linux驱动修炼之道-DM9000A网卡驱动框架源码分析(中).pdf Linux驱动修炼之道-DM9000A网卡驱动框架源码分析(下).pdf Linux驱动修炼之道-DM9000A网卡驱动框架源码分析(上).pdf Linux驱动修炼之道-clock框架.pdf Linux驱动修炼之道-ADC驱动.pdf Linux内核访问外设I O资源的方式.pdf LINUX内核USB子系统学习笔记之初识USB.pdf kernel hacker修炼之道之驱动-流水灯.pdf kernel hacker修炼之道之驱动-混杂设备.pdf kernel hacker修炼之道之驱动-按键.pdf kernel hacker修炼之道之PCI subsystem(五).pdf kernel hacker修炼之道之PCI subsystem(四).pdf kernel hacker修炼之道之PCI subsystem(三).pdf kernel hacker修炼之道之PCI subsystem(六).pdf kernel hacker修炼之道之PCI subsystem(二).pdf
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值