vmalloc kmem_cache_create

vmalloc:         
       计算机语言的一种函数名,原型为:void *vmalloc(unsigned long size)。 size要分配内存的大小. 以字节为单位。在设备驱动程序或者内核模块中动态开辟内存,不是用malloc,而是kmalloc ,vmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,vfree,或free_pages. kmalloc函数返回的是虚拟地址(线性地址). kmalloc特殊之处在于它分配的内存是物理上连续的,这对于要进行DMA的设备十分重要. 而用vmalloc分配的内存只是线性地址连续,物理地址不一定连续,不能直接用于DMA。 [1]  

vmalloc函数的工作方式类似于kmalloc,只不过前者分配的内存虚拟地址是连续的,而物理地址则无需连续。它通过分配非连续的物理内存块,再修改页表,把内存映射到逻辑地址空间的连续区域中。通过vmalloc获得的页必须一个一个地进行映射,效率不高,因此,只在不得已(一般是为了获得大块内存)时使用。vmalloc函数返回一个指针,指向逻辑上连续的一块内存区,其大小至少为size。在发生错误时,函数返回NULL。vmalloc可能睡眠,因此,不能从中断上下文中进行调用,也不能从其它不允许阻塞的情况下调用。要释放通过vmalloc所获得的内存,应使用vfree函数

kmalloc、vmalloc、malloc的区

简单的说:

  1. kmalloc和vmalloc是分配的是内核的内存,malloc分配的是用户的内存
  2. kmalloc保证分配的内存在物理上是连续的,vmalloc保证的是在虚拟地址空间上的连续,malloc不保证任何东西(这点是自己猜测的,不一定正确)
  3. kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相对较大
  4. 内存只有在要被DMA访问的时候才需要物理上连续
  5. vmalloc比kmalloc要慢

详细的解释

      对于提供了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 <linux/module.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
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("<1>pagemem addr=%x", pagemem);

kmallocmem = (unsigned char*)kmalloc(100, 0);
printk("<1>kmallocmem addr=%x", kmallocmem);

vmallocmem = (unsigned char*)vmalloc(1000000);
printk("<1>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)处,符合前文所述的内存布局。

 当buddy系统还有大量的连续物理内存时,我们可以通过__pages_alloc成功分配很大的一块连续物理内存空间,随着系统运行时间加长,buddy系统内很难中找到一块大的连续物理内存空间,因此__pages_alloc可能会失败,即便通过kswapd进行页面的回收和交换,buddy仍然不可避免的碎片化


首先我们要明确的是,连续物理内存的分配并不是必要的。对于大部分DMA操作,我们的确需要连续的物理内存;但是对于某些分配内存情况:比如,模块加载,设备和声音驱动程序中,可以在内核源码中关键字vmalloc查找,对vmalloc的使用有个感性认识。


vmalloc把buddy系统内的不连续物理内存,映射到内核中一段连续的地址空间内,因此对于那些无法直接映射的高端物理内存Highmem来说,vmalloc是主要用途之一(另外一个用途是应用程序的地址映射,之前我一直搞不清它和vmalloc的关系。实际二者没关系,只是看起来很像)。因此vmalloc理应优先使用廉价的Highmem内存,而把宝贵的低端内存,留给其他的内核操作。事实上也是如此,vmalloc实现函数的分配标志,指明了从Highmem分配

  1. void *vmalloc(unsigned long size)  
  2. {  
  3.         return __vmalloc(size, GFP_KERNEL | <strong>__GFP_HIGHMEM</strong>, PAGE_KERNEL);  
  4. }  

对于vmalloc来说是需要预留一定的地址空间的,我个人觉得地址空间也算是一种资源,尤其对于IA32体系结构和大部分32bit体系结构,整个内核地址空间只有1G bytes(3:1 split)。而DMA和Normal内存zone 又需要占用数百M的地址空间,参见下面这个经典的kernel地址空间划分图


Persistent mappings和Fixmaps地址空间都比较小,这里我们忽略它们,这样只剩下直接地址映射和VMALLOC区,这个划分应该是平衡两个需求的结果

1. 尽量增加DMA和Normal区大小,也就是直接映射地址空间大小,当前主流平台的内存,基本上都超过了512MB,很多都是标配1GB内存,因此注定有一部分内存无法进行线性映射。

2. 保留一定数量的VMALLOC大小,这个值是应用平台特定的,如果应用平台某个驱动需要用vmalloc分配很大的地址空间,那么最好通过在kernel参数中指定vmalloc大小的方法,预留较多的vmalloc地址空间。

3. 并不是Highmem没有或者越少越好,这个是我的个人理解,理由如下:高端内存就像个垃圾桶和缓冲区,防止来自用户空间或者vmalloc的映射破坏Normal zone和DMA zone的连续性,使得它们碎片化。当这个垃圾桶较大时,那么污染Normal 和DMA的机会自然就小了。


下面的图是VMALLOC地址空间内部划分情况



在直接地址映射和VMALLOC区之间有一个8MiB的隔离带,隔离带是做什么的呢? 隔离带是用来针对内核故障的保护措施,当访问虚拟地址越界时,则会产生一个page fault异常,也就是说这个内核地址空间没有对应相应的物理地址,这在内核地址空间是不允许的。如果不存在隔离带,那么越界访问不知不觉的跨越直接映射和VMALLOC区,内核却没注意到这个错误。


在VMALLOC内部,会划分为多个vmalloc_area,每个vmalloc_area直间有一个4KB的地址空隙,通过这个小的隔离,可以防止不同映射区直接的越界访问。


数据结构

在进入vmalloc代码实现之前,我们先了解相关的数据结构。

  1. struct vm_struct {  
  2.         /* keep next,addr,size together to speedup lookups */  
  3.         struct vm_struct        *next;  
  4.         void                    *addr;  
  5.         unsigned long           size;  
  6.         unsigned long           flags;  
  7.         struct page             **pages;  
  8.         unsigned int            nr_pages;  
  9.         unsigned long           phys_addr;  
  10. };  

内核在管理虚拟内存地址空间时,必须通过数据结构来跟踪哪些子区域被使用,哪些是空闲的。所有的这些数据连接到一个链表中

@next:所有的vm_struct通过next 组成一个单链表,表头为全局变量vmlist

@addr:定义了这个虚拟地址空间子区域的起始地址

@size:定义了这个虚拟地址空间子区域的大小

@flags:存储了与该内存区关联的标志

@pages是一个指针,指向page指针的数组,每个数组成员都表示一个映射到这个地址空间的物理页面的实例。

@nr_pages:page指针数据的长度

@phys_addr:仅当用ioremap映射了由物理地址描述的物理内存区域才有效。


注意 vm_struct和vm_area_struct是完全不同的,虽然二者都是做虚拟地址空间映射的:

1. 前者是内核虚拟地址空间映射,而后者则是应用进程虚拟地址空间映射。

2. 前者不会产生page fault,而后者一般不会提前分配页面,只有当访问的时候,产生page fault来分配页面。


vmalloc 映射示例

下图给除了vmalloc映射的一个实例,这个vmalloc区映射了三个物理内存页面


从VMALLOC_START+100开始,大小为3*PAGE_SIZE的内核地址空间,被映射到物理页面725, 1023和7311


vmalloc 代码实现

因为大部分体系结构都支持mmu,这里我们只考虑有mmu的情况。实际上没有mmu支持时,vmalloc就无法实现非连续物理地址到连续内核地址空间的映射,vmalloc退化为kmalloc实现

  1. 506 void *__vmalloc(unsigned long size, gfp_t gfp_mask, pgprot_t prot)  
  2. 507 {  
  3. 508         return __vmalloc_node(size, gfp_mask, prot, -1);  
  4. 509 }  
  5. 510 EXPORT_SYMBOL(__vmalloc);  
  6.   
  7.   
  8. 512 /**  
  9. 513  *      vmalloc  -  allocate virtually contiguous memory  
  10. 514  *      @size:          allocation size  
  11. 515  *      Allocate enough pages to cover @size from the page level  
  12. 516  *      allocator and map them into contiguous kernel virtual space.  
  13. 517  *  
  14. 518  *      For tight control over page level allocator and protection flags  
  15. 519  *      use __vmalloc() instead.  
  16. 520  */  
  17. 521 void *vmalloc(unsigned long size)  
  18. 522 {  
  19. 523         return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);  
  20. 524 }  
  21. 525 EXPORT_SYMBOL(vmalloc);  

非常清楚,vmalloc优先使用HIGHMEM内存。返回值为内核虚拟地址空间地址,这个地址以及@size决定的分配空间,一定在VMALLOC范围之内。

__vmalloc也仅仅是__vmalloc_node包装函数


  1. 479 /**  
  2. 480  *      __vmalloc_node  -  allocate virtually contiguous memory  
  3. 481  *      @size:          allocation size  
  4. 482  *      @gfp_mask:      flags for the page level allocator  
  5. 483  *      @prot:          protection mask for the allocated pages  
  6. 484  *      @node:          node to use for allocation or -1  
  7. 485  *  
  8. 486  *      Allocate enough pages to cover @size from the page level  
  9. 487  *      allocator with @gfp_mask flags.  Map them into contiguous  
  10. 488  *      kernel virtual space, using a pagetable protection of @prot.  
  11. 489  */  
  12. 490 static void *__vmalloc_node(unsigned long size, gfp_t gfp_mask, pgprot_t prot,  
  13. 491                             int node)  
  14. 492 {  
  15. 493         struct vm_struct *area;  
  16. 494   
  17. 495         size = PAGE_ALIGN(size);  
  18. 496         if (!size || (size >> PAGE_SHIFT) > num_physpages)  
  19. 497                 return NULL;  
  20. 498   
  21. 499         area = get_vm_area_node(size, VM_ALLOC, node, gfp_mask);  
  22. 500         if (!area)  
  23. 501                 return NULL;  
  24. 502   
  25. 503         return __vmalloc_area_node(area, gfp_mask, prot, node);  
  26. 504 }  

495 把请求的@size按照页面对齐,说明分配是按照4K对齐的

非常自然的,分配过程分为两个步骤:1 分配地址空间,2 进行映射

499 从VMALLOC地址空间申请一块合适的地址空间

503 有了地址空间后,就需要对地址空间进行页面映射,也就是说分配页面物理页面


分配地址空间

  1. 263 struct vm_struct *get_vm_area_node(unsigned long size, unsigned long flags,  
  2. 264                                    int node, gfp_t gfp_mask)  
  3. 265 {  
  4. 266         return __get_vm_area_node(size, flags, VMALLOC_START, VMALLOC_END, node,  
  5. 267                                   gfp_mask);  
  6. 268 }  

在VMALLOC_START和VMALLOC_END指定的范围内查找


  1. 169 static struct vm_struct *__get_vm_area_node(unsigned long size, unsigned long flags,  
  2. 170                                             unsigned long start, unsigned long end,  
  3. 171                                             int node, gfp_t gfp_mask)  
  4. 172 {  
  5. 173         struct vm_struct **p, *tmp, *area;  
  6. 174         unsigned long align = 1;  
  7. 175         unsigned long addr;  
  8. 176   
  9. 177         BUG_ON(in_interrupt());  
  10. 178         if (flags & VM_IOREMAP) {  
  11. 179                 int bit = fls(size);  
  12. 180   
  13. 181                 if (bit > IOREMAP_MAX_ORDER)  
  14. 182                         bit = IOREMAP_MAX_ORDER;  
  15. 183                 else if (bit < PAGE_SHIFT)  
  16. 184                         bit = PAGE_SHIFT;  
  17. 185   
  18. 186                 align = 1ul << bit;  
  19. 187         }  
  20. 188         addr = ALIGN(start, align);  
  21. 189         size = PAGE_ALIGN(size);  
  22. 190         if (unlikely(!size))  
  23. 191                 return NULL;  
  24. 192   
  25. 193         area = kmalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);  
  26. 194   
  27. 195         if (unlikely(!area))  
  28. 196                 return NULL;  
  29. 197   
  30. 198         /*  
  31. 199          * We always allocate a guard page.  
  32. 200          */  
  33. 201         size += PAGE_SIZE;  
  34. 202   
  35. 203         write_lock(&vmlist_lock);  
  36. 204         for (p = &vmlist; (tmp = *p) != NULL ;p = &tmp->next) {  
  37. 205                 if ((unsigned long)tmp->addr < addr) {  
  38. 206                         if((unsigned long)tmp->addr + tmp->size >= addr)  
  39. 207                                 addr = ALIGN(tmp->size +  
  40. 208                                              (unsigned long)tmp->addr, align);  
  41. 209                         continue;  
  42. 210                 }  
  43. 211                 if ((size + addr) < addr)  
  44. 212                         goto out;  
  45. 213                 if (size + addr <= (unsigned long)tmp->addr)  
  46. 214                         goto found;  
  47. 215                 addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);  
  48. 216                 if (addr > end - size)  
  49. 217                         goto out;  
  50. 218         }  
  51. 219   
  52. 220 found:  
  53. 221         area->next = *p;  
  54. 222         *p = area;  
  55. 223   
  56. 224         area->flags = flags;  
  57. 225         area->addr = (void *)addr;  
  58. 226         area->size = size;  
  59. 227         area->pages = NULL;  
  60. 228         area->nr_pages = 0;  
  61. 229         area->phys_addr = 0;  
  62. 230         write_unlock(&vmlist_lock);  
  63. 231   
  64. 232         return area;  
  65. 233   
  66. 234 out:  
  67. 235         write_unlock(&vmlist_lock);  
  68. 236         kfree(area);  
  69. 237         if (printk_ratelimit())  
  70. 238                 printk(KERN_WARNING "allocation failed: out of vmalloc space - use vmalloc=<size> to increase size.\n");  
  71. 239         return NULL;  
  72. 240 }  

@start是进行扫描的首地址,@end是扫描的终止地址。在start和end指定的地址空间内分配。

193 首先分配一个vm_struct 结构,因为这个机构很小,自然使用kmalloc进行分配了,至于在哪个node分配,不用care

198~201 每个vm_struct之间都有4KB的隔离区,所以这里多分配4KB

204 ~ 218 循环遍历已经创建的vm_struct区,找到能够创建地址空间的位置

205 ~ 209 如果start大于当前vm_struct的起始位置,那么我们尝试下一个。同时判断start是否落在这个vm_struct内,如果是还要修改start

213 ~ 214 如果size + addr小于当前的vm_struct,说明匹配了一个可用位置,直接跳到found标号

221 ~ 222 把这个vm_struct增加到vmlist中去

从这个函数我们可以看出来,vm_struct的分配并不会考虑最优匹配,而是在碰到一个够用空间后直接返回。


分配物理页面 并映射

__vmalloc_area_node

  1. 426 void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,  
  2. 427                                 pgprot_t prot, int node)  
  3. 428 {  
  4. 429         struct page **pages;  
  5. 430         unsigned int nr_pages, array_size, i;  
  6. 431   
  7. 432         nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;  
  8. 433         array_size = (nr_pages * sizeof(struct page *));  
  9. 434   
  10. 435         area->nr_pages = nr_pages;  
  11. 436         /* Please note that the recursion is strictly bounded. */  
  12. 437         if (array_size > PAGE_SIZE) {  
  13. 438                 pages = __vmalloc_node(array_size, gfp_mask | __GFP_ZERO,  
  14. 439                                         PAGE_KERNEL, node);  
  15. 440                 area->flags |= VM_VPAGES;  
  16. 441         } else {  
  17. 442                 pages = kmalloc_node(array_size,  
  18. 443                                 (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO,  
  19. 444                                 node);  
  20. 445         }  
  21. 446         area->pages = pages;  
  22. 447         if (!area->pages) {  
  23. 448                 remove_vm_area(area->addr);  
  24. 449                 kfree(area);  
  25. 450                 return NULL;  
  26. 451         }  
  27. 452   
  28. 453         for (i = 0; i < area->nr_pages; i++) {  
  29. 454                 if (node < 0)  
  30. 455                         area->pages[i] = alloc_page(gfp_mask);  
  31. 456                 else  
  32. 457                         area->pages[i] = alloc_pages_node(node, gfp_mask, 0);  
  33. 458                 if (unlikely(!area->pages[i])) {  
  34. 459                         /* Successfully allocated i pages, free them in __vunmap() */  
  35. 460                         area->nr_pages = i;  
  36. 461                         goto fail;  
  37. 462                 }  
  38. 463         }  
  39. 464   
  40. 465         if (map_vm_area(area, prot, &pages))  
  41. 466                 goto fail;  
  42. 467         return area->addr;  
  43. 468   
  44. 469 fail:  
  45. 470         vfree(area->addr);  
  46. 471         return NULL;  
  47. 472 }  

432 根据area的size计算需要的物理页面数目,减去1个PAGE_SIZE是因为这个vmalloc区包含一个4KB的隔离区

433 ~ 445 为area->pages数组分配内存,理论上一个页面只能保证1000个page指针,所以area->pages也使用vmalloc分配就很正常了

453 ~ 463调用alloc_pages_node一个一个的分配page,所以vmalloc的分配速度自然没有使用alloc_pages的kmalloc高,但是vmalloc的成功率就很高了。

465 目前为止,还有一件事没完成,那就是物理地址对逻辑地址的映射,map_vm_area就是做这事的


线性地址到物理地址映射

  1. 148 int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page ***pages)  
  2. 149 {  
  3. 150         pgd_t *pgd;  
  4. 151         unsigned long next;  
  5. 152         unsigned long addr = (unsigned long) area->addr;  
  6. 153         unsigned long end = addr + area->size - PAGE_SIZE;  
  7. 154         int err;  
  8. 155   
  9. 156         BUG_ON(addr >= end);  
  10. 157         pgd = pgd_offset_k(addr);  
  11. 158         do {  
  12. 159                 next = pgd_addr_end(addr, end);  
  13. 160                 err = vmap_pud_range(pgd, addr, next, prot, pages);  
  14. 161                 if (err)  
  15. 162                         break;  
  16. 163         } while (pgd++, addr = next, addr != end);  
  17. 164         flush_cache_vmap((unsigned long) area->addr, end);  
  18. 165         return err;  
  19. 166 }  
  20. 167 EXPORT_SYMBOL_GPL(map_vm_area);  

153 别忘了,减去隔离区的一个PAGE_SIZE大小

158 ~ 163 对addr和end范围内的页表进行映射,包括pgd pud pmd和pte。

164 这是一个体系结构相关的函数,有些体系结构无法察觉到页表的变化,因此在修改页表后,需要程序主动的去刷新以下;有些CPU有感知变化的能力,会自动的刷新高速缓存,IA32就是如此。


GOOD,分析完了,vmalloc的代码还是简明清晰的,这次阅读解决了几个以前迷惑的问题

1. vmalloc区域是不是和应用空间内存映射一样,通过page fault来装载页面的。

答案:不是,vmalloc映射建立好后,逻辑地址,物理页面全部分配好,而且页表也已经更新好,和用户空间映射完全一样。这是合理的,因为如果像用户空间映射那样,访问地址产生page fault,会使得vmalloc获得的内存使用上受到极大的限制,比如不能在禁止调度的地方访问vmalloc分配的地址。


Slab函数分析之kmem_cache_create函数

7235人阅读 评论(0) 收藏 举报

该函数用于创建Slab缓存。
参数介绍:
name: 缓存名字
size: 对象的大小
align:对齐
flag:Slab标志位
ctor:对象构造函数
dtor:对象析构函数

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))
{
 size_t left_over, slab_size, ralign;
 struct kmem_cache *cachep = NULL;
 struct list_head *p;

  对函数使用的检查,下面一种情况发生都将导致死机
  1.name为null
  2.处于中断上下文中
  3.对象大小小于4个字节或者大于32个页面
  4.构造函数存在,析构函数不存在
 if ((!name) ||
     in_interrupt() ||
     (size < BYTES_PER_WORD) ||
     (size > (1 << MAX_OBJ_ORDER) * PAGE_SIZE) || (dtor && !ctor)) {
  printk(KERN_ERR "%s: Early error in slab %s/n",
         __FUNCTION__, name);
  BUG();
 }
 
 锁住CPU热插拔功能
 lock_cpu_hotplug();

  为cache_chain加互斥锁
 mutex_lock(&cache_chain_mutex);

  遍历cache_chain链表
 list_for_each(p, &cache_chain) {
  struct kmem_cache *pc = list_entry(p, struct kmem_cache, next);
  
  由于__get_user函数只在用户环境中执行,因此我们要
  改变fs寄存器,在内核空间也能调用该函数
  mm_segment_t old_fs = get_fs();
  char tmp;
  int res;
   
    设置fs段的内容为内核数据段
  set_fs(KERNEL_DS);
  res = __get_user(tmp, pc->name);
  还原fs段
  set_fs(old_fs);
  if (res) {
   printk("SLAB: cache with size %d has lost its name/n",
          pc->buffer_size);
   continue;
  }
    判断是不是为name的cache是不是已经存在
  if (!strcmp(pc->name, name)) {
   printk("kmem_cache_create: duplicate cache %s/n", name);
   dump_stack();
   goto oops;
  }
 }

  如果采用2.6提供的新的锁机制,read-copy-update来对其释放,
  检查析构函数是不是为空。
 if (flags & SLAB_DESTROY_BY_RCU)
  BUG_ON(dtor);

 检查创建时相应的标志位是不是设置好了。
 CREATE_MASK: (# define CREATE_MASK (SLAB_HWCACHE_ALIGN | SLAB_NO_REAP | /
    SLAB_CACHE_DMA | SLAB_MUST_HWCACHE_ALIGN | /
    SLAB_RECLAIM_ACCOUNT | SLAB_PANIC | /
    SLAB_DESTROY_BY_RCU))
   
 if (flags & ~CREATE_MASK)
  BUG();

  检查对象的大小是不是32位对齐,如果不是
  则进行调整
 if (size & (BYTES_PER_WORD - 1)) {
  size += (BYTES_PER_WORD - 1);
  size &= ~(BYTES_PER_WORD - 1);
 }

  检查Slab是不是按照硬件缓冲行对齐
 if (flags & SLAB_HWCACHE_ALIGN) {
   取硬件缓冲行大小
  ralign = cache_line_size();
  进行对齐大小的调整,直到对象大小大于对齐的大小
  while (size <= ralign / 2)
   ralign /= 2;
 } else {
   默认情况下,对齐大小为4个字节
  ralign = BYTES_PER_WORD;
 }

  对align进行重新调整,确定最终的align值
 if (ralign < ARCH_SLAB_MINALIGN) {
  ralign = ARCH_SLAB_MINALIGN;
  if (ralign > BYTES_PER_WORD)
   flags &= ~(SLAB_RED_ZONE | SLAB_STORE_USER);
 }

 if (ralign < align) {
  ralign = align;
  if (ralign > BYTES_PER_WORD)
   flags &= ~(SLAB_RED_ZONE | SLAB_STORE_USER);
 }
 align = ralign;

  从cache_cache中划分一个新的kmem_cache节点。
 cachep = kmem_cache_alloc(&cache_cache, SLAB_KERNEL);
 if (!cachep)
  goto oops;
 初始化得到的内存
 memset(cachep, 0, sizeof(struct kmem_cache));

  如果发现对象大小是大于512字节,那么Slab缓存的管理区部分应该和
  对象分开存放
 if (size >= (PAGE_SIZE >> 3))
  flags |= CFLGS_OFF_SLAB;

  对齐
 size = ALIGN(size, align);

  计算存放大小为size的对象的order值,以及可以存放的对象数量。返回Slab缓存的
  碎片大小。
 left_over = calculate_slab_order(cachep, size, align, flags);

  如果发现对象数量为0,表示不能创建
 if (!cachep->num) {
  printk("kmem_cache_create: couldn't create cache %s./n", name);
  kmem_cache_free(&cache_cache, cachep);
  cachep = NULL;
  goto oops;
 }
 计算Slab缓存的管理区的大小:slab结构的大小+每个对象有一个控制域的大小(kmem_bufctl_t)
 slab_size = ALIGN(cachep->num * sizeof(kmem_bufctl_t)
     + sizeof(struct slab), align);

 如果Slab管理区是和对象分开存放的,并且Slab中的碎片大小足够存放
 管理区。则需要重新调整Slab管理区的存放位置,可以和Slab对象一起存放。
 if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) {
  flags &= ~CFLGS_OFF_SLAB;
  left_over -= slab_size;
 }

  如果Slab管理区和对象分开存放,Slab管理区的大小为:slab结构的大小+每个对象有一个控制域的大小(kmem_bufctl_t)
 if (flags & CFLGS_OFF_SLAB) {
  slab_size =
      cachep->num * sizeof(kmem_bufctl_t) + sizeof(struct slab);
 }

  计算着色偏移区:初始值为硬件缓存行大小
 cachep->colour_off = cache_line_size();
 调整着色偏移区
 if (cachep->colour_off < align)
  cachep->colour_off = align;
 计算颜色大小:Slab碎片大小除以着色偏移区
 cachep->colour = left_over / cachep->colour_off;
 cachep->slab_size = slab_size;
 cachep->flags = flags;
 cachep->gfpflags = 0;
 if (flags & SLAB_CACHE_DMA)
  cachep->gfpflags |= GFP_DMA;
 初始化自旋锁
 spin_lock_init(&cachep->spinlock);
 buffer_size为一个对象的大小
 cachep->buffer_size = size;

  如果对象和管理区分开存放,使用slabp_cache指针来指向管理区的内存,从通用缓存中申请。
 if (flags & CFLGS_OFF_SLAB)
  cachep->slabp_cache = kmem_find_general_cachep(slab_size, 0u);
 cachep->ctor = ctor;
 cachep->dtor = dtor;
 cachep->name = name;

  接下来是对kmem_list3/array cache的一个初始化。每个CPU对应一个array cache
  系统先是分配array cache中的对象,目的是减少对Slab cache的访问次数来
  提高系统性能。
 if (g_cpucache_up == FULL) {
  enable_cpucache(cachep);
 } else {
  if (g_cpucache_up == NONE) {
    为array cache和list3分配内存
   cachep->array[smp_processor_id()] =
       &initarray_generic.cache;
   set_up_list3s(cachep, SIZE_AC);
   if (INDEX_AC == INDEX_L3)
    g_cpucache_up = PARTIAL_L3;
   else
    g_cpucache_up = PARTIAL_AC;
  } else {
    为array cache和list3分配内存
   cachep->array[smp_processor_id()] =
       kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);

   if (g_cpucache_up == PARTIAL_AC) {
    set_up_list3s(cachep, SIZE_L3);
    g_cpucache_up = PARTIAL_L3;
   } else {
    int node;
    初始化kmem_list3
    for_each_online_node(node) {
     cachep->nodelists[node] =
         kmalloc_node(sizeof
        (struct kmem_list3),
        GFP_KERNEL, node);
     BUG_ON(!cachep->nodelists[node]);
     kmem_list3_init(cachep->
       nodelists[node]);
    }
   }
  }
  cachep->nodelists[numa_node_id()]->next_reap =
      jiffies + REAPTIMEOUT_LIST3 +
      ((unsigned long)cachep) % REAPTIMEOUT_LIST3;

  初始化array cache
  cpu_cache_get(cachep)->avail = 0;
  cpu_cache_get(cachep)->limit = BOOT_CPUCACHE_ENTRIES;
  cpu_cache_get(cachep)->batchcount = 1;
  cpu_cache_get(cachep)->touched = 0;
  cachep->batchcount = 1;
  cachep->limit = BOOT_CPUCACHE_ENTRIES;
 }
  将创建的新的kmem_cache连入链表cache_cache中。
 list_add(&cachep->next, &cache_chain);
      oops:
 if (!cachep && (flags & SLAB_PANIC))
  panic("kmem_cache_create(): failed to create slab `%s'/n",
        name);
 mutex_unlock(&cache_chain_mutex);
 unlock_cpu_hotplug();
 return cachep;
}

内存管理是内核是最复杂同时也是最重要的一部分,其中就涉及到了多种内存分配器,如果内核初始化阶段使用的bootmem分配器,分配大块内存的伙伴系统,以及其分配较小块内存的slabslubslob分配器。


1.bootmem分配器

bootmem分配器用于在启动阶段早期分配内存。该分配器用一个位图来管理页,位图比特位的数目与系统中物理内存页的数目相同。比特位为1表示已用页,比特位为0,表示空闲页。在需要分配内存时,分配器逐位扫描位图,直至找到一个能提供足够连续页的位置,即所谓的最先最佳或最先适配位置。

该分配提供了如下内核接口:

内核接口

说明

alloc_bootmem

alloc_bootmem_pages(size)

按指定大小在ZONE_NORMAL内存域分配内存

alloc_bootmem_low

alloc_bootmem_low_pages(size)

功能同上,只是从ZONE_DMA内存域分配内存。

free_bootmem

释放内存

每个分配器必须实现一组特定的函数,用于内存分配和缓存:

kmalloc__kmallockmalloc_node是一般的内存分配函数。

kmem_cache_allockmem_cache_alloc_node提供特定类型的内核缓存。


Functions (which can be used only by drivers directly linked into the kernel) that
perform allocation and freeing of memory at system bootstrap time.



2.slab分配器

功能:提供小的内存块,也可用作一个缓存。

分配和释放内存在内核代码上很常见。为了使频繁分配和释放内存所导致的开销尽量变小,程序员通常使用空闲链表。当分配的内在块不再需要时,将这块内存插入到空闲链表中,而不是真正的释放掉,这种空闲链表相当于内存块缓冲区。但这种方法的不足之处是,内核没有一种手段能够全局地控制空闲链表的大小,实时地更新这些空闲链表的大小。事实上,内核根本也不可能知道有多少空闲链表存在。

  • 为了解决上述问题,内核心提供了slab层或slab分配器。它作为一个通用的内核数据结构缓冲层。slab层使用了以下几个基本原理:

  • 经常使用的数据结构一般来说会被经常分配或释放,所以应该缓存它们。

  • 频繁地分配和释放内存会导致内在碎片(不能找到合适的大块连续的物理地址空间)。为了防止这种问题,缓冲后的空闲链表被存放到连续的物理地址空间上。由于被释放的数据结构返回到了空闲链表,所以没有导致碎片。

  • 在频繁地分配和释放内存空间在情况下,空闲链表保证了更好的性能。因为被释放的对象空间可立即用于下次的分配中。

  • 如果分配器能够知道诸如对象大小、页大小和总的缓冲大小时,它可以作出更聪明的决定。

  • 如果部分缓冲区对每-CPU变量,那么,分配和释放操作可以不需要SMP锁。

  • 如果分配器是非一致内存,它能从相同的内存结点中完成分配操作。

  • 存储的对象可以被着色,以防止多个对象映射到同一个缓冲。

linux中的slab层就是基于上述前提而实现的。

slab层将不同的对象进行分组,称之为“缓冲区(cache)”。一个缓冲区存储一种类型的对象。每种类型的对象有一个缓冲区。kmalloc()的实现就是基于slab层之上的,使用了一族通用的缓冲区。这些缓冲区被分成了一些slab。这些slab是由一个或多个物理上连续的页组成的。每个缓冲区可包含多个slab

每个slab包含有一些数量的对象,也即被缓冲的数据结构。每个slab问量处于三种状态之间:满、部分满、空。当内核请求一个新的对象时,它总是先查看处于部分满状态的slab,查看是否有合适的空间,如果没有,则空的slab中分配空间。



每个缓冲区由一个kmem_cache结构来表示。该结构包含了三个链表:slabs_full,slabs_partialslabs_emppty。存储在一个kmem_list3结构中。

slab分配器接口

接口名称

说明

kmem_cache_create

分配一个cache

kmem_cache_destroy

销毁一个cache

kmem_cache_alloc

从一个cache中分配一个对象空间

kmem_cache_free

释放一个对象空间到cache

这些接口不宜在中断上下文中使用。






  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值