FreeBSD-7 内核malloc 源代码分析

华为数通硬件四部李昂
[email protected]
http://lllaaa.cublog.cn
看FreeBSD-7 的内核代码有一段时间了,但是一直没有能够总结一下。由于没有写文档,
很多地方都是一带而过,并没有深入分析。为了逼自己能够分析完整个malloc 过程的代码,我
决定一边分析一边记录自己的分析笔记。
一提到内存分配,自然会想到malloc 和free 这对双胞胎。在FreeBSD 内核里,也有
malloc 和free 这两个函数。它们的参数与C 语言标准库里面的略有不同,但是作用基本相同。
下面就从malloc 入手分析内存分配的过程。malloc 的源代码并不是很复杂,但为了分析方便,
我删除了一些调试、统计及诊断用的代码,只列出具体的实现代码。不过需要注意到是,单独看
malloc 的代码,有些数据结构的用途是无法分析清楚的,所以有些分析结果是我分析了free 的
代码得出的。如果你遇到对数据结构的功能不清楚的情况可以去看看free 的代码。在此我就不再
单独分析free 的代码了。
00297 void *
00298 malloc(unsigned long size, struct malloc_type *mtp, int flags)
00299 {
00300 int indx;
00301 caddr_t va;
00302 uma_zone_t zone;
00303 uma_keg_t keg;
00334 if
(flags & M_WAITOK)
00335 KASSERT(curthread->td_intr_nesting_level == 0,
00336 ("malloc(M_WAITOK) in interrupt context"));
00337
00346
00347 if
(size <= KMEM_ZMAX) {
00348 if
(size & KMEM_ZMASK)
00349 size = (size & ~KMEM_ZMASK) + KMEM_ZBASE;
00350 indx = kmemsize[size >> KMEM_ZSHIFT];
00351 zone = kmemzones[indx].kz_zone;
00352 keg = zone->uz_keg;
00356 va = uma_zalloc(zone, flags);
00357 if
(va != NULL)
00358 size = keg->uk_size;
00359 malloc_type_zone_allocated(mtp, va == NULL ? 0 : size, indx);
00360 } else
{
00361 size = roundup(size, PAGE_SIZE);
00362 zone = NULL;
00363 keg = NULL;
00364 va = uma_large_malloc(size, flags);
00365 malloc_type_allocated(mtp, va == NULL ? 0 : size);
00366 }
00367 if
(flags & M_WAITOK)
00368 KASSERT(va != NULL, ("malloc(M_WAITOK) returned NULL"));
00369 else
if
(va == NULL)
00370 t_malloc_fail = time_uptime;
00380 return
((void *) va);
00381 }
334~336 行主要保证不能在中断里使用M_WAITOK 参数来调用malloc。M_WAITOK 的目的
是可以一直等待到能够分配成功为止,也就是说如果遇到内存不够的时候,M_WAITOK 会让
malloc 函数阻塞,直到底层的分配器能够分配到内存再往下执行,因此我们不能在中断里这么玩。
但是要特别注意的是,不能因为使用了M_WAITOK 参数就认为只要函数返回就一定申请成功了。
实际上,申请成功了是一回事,而返回合法指针是另一回事。后面你会看到,申请到内存后还会
有个构造函数初始化内存空间的操作,如果那个操作失败了,还是会释放掉申请到的内存并返回
NULL。
347~366 行可以看到两个内存分配的分支。针对不同的内存申请量,malloc 函数里以
KMEM_MAX 为界限,提供了两种分配策略,347~359 是小内存的分配方案,360~366 是大内存
分配方案。如果申请内存空间比较小采用uma_zalloc 来分配,较大的(一般是超过1 个物理页
面大小)则采用uma_large_malloc。由于少量内存申请的情况比较多,这时候的内存碎片是最
容易产生的并且浪费巨大。
为了解决内存碎片和其它一系列问题FreeBSD 的uma_zalloc 采用了和solaris 和linux
类似的slab 分配器。关于slab 分配器有很多文档介绍。简单的说就是内核经常申请固定大小的
一些内存空间,这些空间一般都是结构体。而这些结构体往往都会有一个共同的初始化行为比如:
初始化里面的信号量、链表指针、成员。通过Sun 的大牛Jeff Bonwick 的研究发现,内核对
这些结构体的初始化所消耗的时间比分配它们的时间还要长。所以他设计了一种算法,当这些结
构体的空间被释放的时候,只是让他回到刚刚分配好的状态而不真正释放,下次再申请的时候就
可以节约初始化的时间。整个过程可以理解为借用白板的过程。申请空间就是从别人那里借多块
白板。由于每块白板的用处不同,每次用的时候都要先在不同的白板上画上不同的表格,然后往
里面填内容。如果一般的算法则是用完白板后,直接还给人家,下次要用的时候再借回来然后画
好表格。优化一点的算法就是用完后暂时不还人家,人家要用的时候再还,第二次再要用白板的
时候随便取一块白板重新画表格。而使用slab 算法就是不用白板的时候擦除表格的内容留下表
格,白板也暂时不还人家。下次要用的时候根据用途取出正确的白板,由于表格是现成的直接往
里面填内容就可以了。省去了借白板和画表格这两个操作。
一、malloc 的uma_zalloc 分支
00347 if
(size <= KMEM_ZMAX) {
00348 if
(size & KMEM_ZMASK)
00349 size = (size & ~KMEM_ZMASK) + KMEM_ZBASE;
00350 indx = kmemsize[size >> KMEM_ZSHIFT];
00351 zone = kmemzones[indx].kz_zone;
00352 keg = zone->uz_keg;
00356 va = uma_zalloc(zone, flags);
00357 if
(va != NULL)
00358 size = keg->uk_size;
00359 malloc_type_zone_allocated(mtp, va == NULL ? 0 : size, indx);
如果请求内存数量size 小于1 个页面,首先将size 以16 字节对齐。然后用对齐后的size
在kmemsize 数组里查到一个索引indx,再用这个indx 从kmemzones 里获取到size 对应的
zone。uma_zalloc 的作用就是从这个zone 里分配内存。也许你会好奇为何uma_zalloc 分
配的时候不用提供size?原因就在于zone 就是为特定大小内存分配准备的。每个zone 初始化
时就决定了它每次只能用来分配多大的空间。比如我们需要分配30 个字节,经过对齐后查出来的
zone 是用来分配32 字节用的。uma_zalloc 会在这个专门用来分配32 字节的zone 里进行分
配。这样做的好处就是每个zone 都是管理相同大小的内存对象,可以整整齐齐的在内存里摆放好,
没有一点浪费。但实际上你可以看到,kmemzones 的数量是有限的,它只能给有限种类大小的内
存提供分配,对于其他大小的内存申请都是由之前的对齐操作放大到最接近的zone 上去分配。
kmemzones 初始定义如下:
00135 struct {
00136 int kz_size;
00137 char *kz_name;
00138 uma_zone_t kz_zone;
00139 } kmemzones[] = {
00140 {16, "16", NULL},
00141 {32, "32", NULL},
00142 {64, "64", NULL},
00143 {128, "128", NULL},
00144 {256, "256", NULL},
00145 {512, "512", NULL},
00146 {1024, "1024", NULL},
00147 {2048, "2048", NULL},
00148 {4096, "4096", NULL},
00149 #if PAGE_SIZE > 4096
00150 {8192, "8192", NULL},
00151 #if PAGE_SIZE > 8192
00152 {16384, "16384", NULL},
00153 #if PAGE_SIZE > 16384
00154 {32768, "32768", NULL},
00155 #if PAGE_SIZE > 32768
00156 {65536, "65536", NULL},
00157 #if PAGE_SIZE > 65536
00158 #error "Unsupported PAGE_SIZE"
00159 #endif /* 65536 */
00160 #endif /* 32768 */
00161 #endif /* 16384 */
00162 #endif /* 8192 */
00163 #endif /* 4096 */
00164 {0, NULL},
00165 };
从uma_zalloc 分配到内存后做点必要的判断和统计工作就可以把申请到的首地址返回了。
1. zone 分配
下面我们深入uma_zalloc 来看看究竟在一个zone 里,内存是怎么被分配和管理的。
uma_zalloc 是个inline 函数,它只负责把参数传递给uma_zalloc_arg。
01779 void *
01780 uma_zalloc_arg(uma_zone_t zone, void *udata, int flags)
01781 {
01782 void *item;
01783 uma_cache_t cache;
01784 uma_bucket_t bucket;
01785 int cpu;
01786
01787 /* This is the fast path allocation */
01791 CTR3(KTR_UMA, "uma_zalloc_arg thread %x zone %s flags %d", curthread,
01792 zone->uz_name, flags);
01793
01794 if
(flags & M_WAITOK) {
01795 WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL,
01796 "uma_zalloc_arg: zone /"%s/"", zone->uz_name);
01797 }
01798
01799 /*
01800 * If possible, allocate from the per-CPU cache. There are two
01801 * requirements for safe access to the per-CPU cache: (1) the thread
01802 * accessing the cache must not be preempted or yield during access,
01803 * and (2) the thread must not migrate CPUs without switching which
01804 * cache it accesses.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值