华为数通硬件四部李昂
[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. We rely on a critical section to prevent
01805 * preemption and migration. We release the critical section in
01806 * order to acquire the zone mutex if we are unable to allocate from
01807 * the current cache; when we re-acquire the critical section, we
01808 * must detect and handle migration if it has occurred.
01809 */
01810 zalloc_restart:
01811 critical_enter();/* 进入critical 状态,防止线程切换和迁移*/
01812 cpu = curcpu;/* 获取当前cpuid */
/* 从zone 里取出当前cpu 的cache(注意此处的cache 不是cpu 内部的那个cache) */
01813 cache = &zone->uz_cpu[cpu];
01814
01815 zalloc_start:
01816 bucket = cache->uc_allocbucket;/* 从cache 里取出allocbucket 指针*/
01817
01818 if
(bucket) {
01819 if
(bucket->ub_cnt > 0) {
/*如果存在allocbucket 并且剩余的bucket 大于0
*则在此bucket 里直接分配内存出来
*/
01820 bucket->ub_cnt--;
/*此时item 的值就是我们所申请到的内存空间首地址。
*ub_bucket 是个指针数组,里面存放的是n 个指向固定大小的内存空间的指针
*/
01821 item = bucket->ub_bucket[bucket->ub_cnt];
01825 KASSERT(item != NULL,
01826 ("uma_zalloc: Bucket pointer mangled."));
01827 cache->uc_allocs++;
01828 critical_exit();
/*如果zone 配置了构造函数,则在用构造函数对申请到的空间初始化
*如果你对slab 分配器比较熟悉,会发现这个行为和slab 初衷是相矛盾的
*slab 分配器里是预先对一块内存初始化完毕后,以后每次申请不再重复初始化
*而此处确实每次都调用构造函数。
*实际上这里的uz_ctor 并不是slab 分配器里的构造函数
*与之对应的应该是zone->uz_init
*FreeBSD 里提供两种不同阶段执行的构造函数
*事实上只有少数地方在创建zone 的时候配置了uz_ctor,例如mbuf 的zone
*/
01834 if
(zone->uz_ctor != NULL) {
01835 if
(zone->uz_ctor(item, zone->uz_keg->uk_size,
01836 udata, flags) != 0) {
01837 uma_zfree_internal(zone, item, udata,
01838 SKIP_DTOR, ZFREE_STATFAIL |
01839 ZFREE_STATFREE);
01840 return
(NULL);
01841 }
01842 }
01843 if
(flags & M_ZERO)
01844 bzero(item, zone->uz_keg->uk_size);
01845 return
(item);
01846 } else
if
(cache->uc_freebucket) {
01847 /*
01848 * We have run out of items in our allocbucket.
01849 * See if we can switch with our free bucket.
01850 */
01851 if
(cache->uc_freebucket->ub_cnt > 0) {
/*如果不存在allocbucket 而存在freebucket 并且不为空,
*则交换allocbucket 和freebucket,跳回到起始点再进行分配
*/
01856 bucket = cache->uc_freebucket;
01857 cache->uc_freebucket = cache->uc_allocbucket;
01858 cache->uc_allocbucket = bucket;
01859
01860 goto
zalloc_start;
01861 }
01862 }
01863 }
01864 /*
01865 * Attempt to retrieve the item from the per-CPU cache has failed, so
01866 * we must go back to the zone. This requires the zone lock, so we
01867 * must drop the critical section, then re-acquire it when we go back
01868 * to the cache. Since the critical section is released, we may be
01869 * preempted or migrate. As such, make sure not to maintain any
01870 * thread-local state specific to the cache from prior to releasing
01871 * the critical section.
01872 */
/*从per-CPU cache 分配失败了,则要回到zone 里来进行分配了。
*首先退出critical 状态,将zone 锁住,再重新进入critical 状态,还是防止线程切换及迁移
*/
01873 critical_exit();
01874 ZONE_LOCK(zone);
01875 critical_enter();
01876 cpu = curcpu;
01877 cache = &zone->uz_cpu[cpu];
01878 bucket = cache->uc_allocbucket;
/*由于退出critical 状态和进入critical 状态之间的短暂时间内
*有可能发生了线程切换或者迁移,因此有可能其他的线程释放了部分内存
*或者当前cpu 所属的cache 里有可分配的资源了
*再试尝试从per-CPU cache 里分配,因为这样分配一次速度较快
*/
01879 if
(bucket != NULL) {
01880 if
(bucket->ub_cnt > 0) {
01881 ZONE_UNLOCK(zone);
01882 goto
zalloc_start;
01883 }
01884 bucket = cache->uc_freebucket;
01885 if
(bucket != NULL && bucket->ub_cnt > 0) {
01886 ZONE_UNLOCK(zone);
01887 goto
zalloc_start;
01888 }
01889 }
01890
01891 /* Since we have locked the zone we may as well send back our stats */
01892 zone->uz_allocs += cache->uc_allocs;
01893 cache->uc_allocs = 0;
01894 zone->uz_frees += cache->uc_frees;
01895 cache->uc_frees = 0;
01896
01897 /* Our old one is now a free bucket */
01898 if
(cache->uc_allocbucket) {
/*既然执行到这里了,就表示allocbucket 和freebucket 都已经分配完了
*那么我们就需要把allocbucket 添加到zone 的free_bucket 链表上,等待释放
*/
01899 KASSERT(cache->uc_allocbucket->ub_cnt == 0,
01900 ("uma_zalloc_arg: Freeing a non free bucket."));
01901 LIST_INSERT_HEAD(&zone->uz_free_bucket,
01902 cache->uc_allocbucket, ub_link);
01903 cache->uc_allocbucket = NULL;
01904 }
01905
01906 /* Check the free list for a new alloc bucket */
01907 if
((bucket = LIST_FIRST(&zone->uz_full_bucket)) != NULL) {
01908 KASSERT(bucket->ub_cnt != 0,
01909 ("uma_zalloc_arg: Returning an empty bucket."));
01910 /*现在bucket 里allocbucket 已经不存在了
*要分配只能从zone 里full_bucket 链表里再拆出一个来
*/
01911 LIST_REMOVE(bucket, ub_link);
01912 cache->uc_allocbucket = bucket;
01913 ZONE_UNLOCK(zone);
01914 goto
zalloc_start;
01915 }
01916 /* We are no longer associated with this CPU. */
01917 critical_exit();
01918
01919 /* Bump up our uz_count so we get here less */
01920 if
(zone->uz_count < BUCKET_MAX)
01921 zone->uz_count++;
01922 /* zone 的full_bucket 链表里的bucket 也分配完了,则要创建bucket 再来分配*/
01923 /*
01924 * Now lets just fill a bucket and put it on the free list. If that
01925 * works we'll restart the allocation from the begining.
01926 */
01927 if
(uma_zalloc_bucket(zone, flags)) {
01928 ZONE_UNLOCK(zone);
01929 goto
zalloc_restart;
01930 }
01931 ZONE_UNLOCK(zone);
01932 /*
01933 * We may not be able to get a bucket so return an actual item.
01934 */
01938 /* 如果连bucket 都无法创建了,则用此函数进行分配*/
01939 return
(uma_zalloc_internal(zone, udata, flags));
01940 }
用现实中的例子来说明如果上面的分配过程:假设我设计一台ATM 自动存取款机(当然真实
的ATM 是不是这么做我不清楚)。malloc 就如同从ATM 中取钱,而free 就如同向ATM 中存钱。
我们的allocbucket 就是当前取钱用的钱袋,每次取钱的时候,ATM 都到这个袋子里去找还有
没有钱。而freebucket 就是专门用来放客户存入的钱的。当用户取钱而取钱用的钱袋
allocbucket 里没有钱了,则ATM 机去看看存钱用的freebucket 里有没有钱,如果有则把存
钱和取钱用的袋子交换,这样以来,取钱的钱袋里又有钱了。但是如果两个袋子里都没有钱,则
需要银行工作人员来往里面放钱了。也就是申请新的装满钱的钱袋。下面我们就来将申请钱袋的
过程。
2. bucket 的创建
1918 行之前都是在已经预先分配的内存里想方设法把能分配的bucket 都拿来分配。如果
zone 里目前已经有的bucket 都已经分配完了,就需要uma_zalloc_bucket 来分配新的
bucket 了。对前面代码分析可以看出从bucket 里分配内存的代码,就是自减计数器,然后利用
计数器在数组里获取内存指针。因此我们分配新的bucket 也只要给bucket 结构体分配空间,
然后分配一些固定大小的内存,把地址填入bucket 的指针数组里。bucket 和slab 的关系如下
图:
bucket 创建详细过程如下代码所示:
02066 static int
02067 uma_zalloc_bucket(uma_zone_t zone, int flags)
02068 {
02069 uma_bucket_t bucket;
02070 uma_slab_t slab;
02071 int16_t saved;
02072 int max, origflags = flags;
02073
02074 /*
02075 * Try this zone's free list first so we don't allocate extra buckets.
02076 */
02077 if
((bucket = LIST_FIRST(&zone->uz_free_bucket)) != NULL) {
02078 KASSERT(bucket->ub_cnt == 0,
02079 ("uma_zalloc_bucket: Bucket on free list is not empty."));
02080 LIST_REMOVE(bucket, ub_link);
02081 } else
{
02082 int bflags;
02083
02084 bflags = (flags & ~M_ZERO);
02085 if
(zone->uz_keg->uk_flags & UMA_ZFLAG_CACHEONLY)
02086 bflags |= M_NOVM;
02087
02088 ZONE_UNLOCK(zone);
/* 分配空间创建一个bucket 结构体出来*/
02089 bucket = bucket_alloc(zone->uz_count, bflags);
02090 ZONE_LOCK(zone);
02091 }
02092
02093 if
(bucket == NULL)
02094 return
(0);
02095
02106 zone->uz_fills++;
02107
02108 max = MIN(bucket->ub_entries, zone->uz_count);
02109 /* Try to keep the buckets totally full */
02110 saved = bucket->ub_cnt;
02111 while
(bucket->ub_cnt < max &&
02112 (slab = uma_zone_slab(zone, flags)) != NULL) {
02113 while
(slab->us_freecount && bucket->ub_cnt < max) {
/* 申请固定大小的内存,把地址写入指针数组*/
02114 bucket->ub_bucket[bucket->ub_cnt++] =
02115 uma_slab_alloc(zone, slab);
02116 }
02117
02118 /* Don't block on the next fill */
02119 flags |= M_NOWAIT;
slab
us_keg
us_link
us_data
data[0]
data[1]
data[2]
data[3]
.
.
.
bucket
ub_bucket[1]
ub_bucket[2]
ub_bucket[3]
ub_link
ub_cnt
ub_entries
ub_bucket[0]
.
.
.
bucket
ub_bucket[1]
ub_bucket[2]
ub_bucket[3]
ub_link
ub_cnt
ub_entries
ub_bucket[0]
.
.
.
cache
uc_freebucket
uc_allocbucket
uc_allocs
uc_frees
zone
uz_fullbucket
uz_free_bucket
uz_cpu[0]
uz_cpu[1]
cache
uc_freebucket
uc_allocbucket
uc_allocs
uc_frees
02120 }
02121
02122 /*
02123 * We unlock here because we need to call the zone's init.
02124 * It should be safe to unlock because the slab dealt with
02125 * above is already on the appropriate list within the keg
02126 * and the bucket we filled is not yet on any list, so we
02127 * own it.
02128 */
/*如果有构造函数就调用构造函数初始化刚刚分配的空间*/
02129 if
(zone->uz_init != NULL) {
02130 int i;
02131
02132 ZONE_UNLOCK(zone);
02133 for
(i = saved; i < bucket->ub_cnt; i++)
02134 if
(zone->uz_init(bucket->ub_bucket[i],
02135 zone->uz_keg->uk_size, origflags) != 0)
02136 break
;
02137 /*
02138 * If we couldn't initialize the whole bucket, put the
02139 * rest back onto the freelist.
02140 */
02141 if
(i != bucket->ub_cnt) {
02142 int j;
02143
02144 for
(j = i; j < bucket->ub_cnt; j++) {
02145 uma_zfree_internal(zone, bucket->ub_bucket[j],
02146 NULL, SKIP_FINI, 0);
02150 }
02151 bucket->ub_cnt = i;
02152 }
02153 ZONE_LOCK(zone);
02154 }
02155
02156 zone->uz_fills--;
02157 if
(bucket->ub_cnt != 0) {
/*把当前申请的bucket 添加到zone 的full_bucket 链表里
*表示是完全空闲的bucket
*/
02158 LIST_INSERT_HEAD(&zone->uz_full_bucket,
02159 bucket, ub_link);
02160 return
(1);
02161 }
/* 如果遇到错误,则释放申请的bucket 结构体空间*/
02165 bucket_free(bucket);
02166
02167 return
(0);
02168 }
2112 行是从zone 上获取一个slab。先看看下面这个图。
slab 是由keg 来管理的。每个keg 有三条单向链表,分别表示半空、全空、全满的slab。
通过对前面代码的分析,我们可以了解到bucket 只保存空闲内存的地址,也就是slab 里那些
data[0]、data[1]的地址,slab 才是真正提供内存空间的。当bucket 里能分配的空间都用
完的时候,就需要从keg 里取出空闲的slab 来填充bucket。下面就是获取的代码。
01942 static uma_slab_t
01943 uma_zone_slab(uma_zone_t zone, int flags)
01944 {
01945 uma_slab_t slab;
01946 uma_keg_t keg;
01947
01948 keg = zone->uz_keg;
01949
01950 /*
01951 * This is to prevent us from recursively trying to allocate
01952 * buckets. The problem is that if an allocation forces us to
01953 * grab a new bucket we will call page_alloc, which will go off
01954 * and cause the vm to allocate vm_map_entries. If we need new
01955 * buckets there too we will recurse in kmem_alloc and bad
01956 * things happen. So instead we return a NULL bucket, and make
01957 * the code that allocates buckets smart enough to deal with it
01958 *
01959 * XXX: While we want this protection for the bucket zones so that
01960 * recursion from the VM is handled (and the calling code that
01961 * allocates buckets knows how to deal with it), we do not want
01962 * to prevent allocation from the slab header zones (slabzone
01963 * and slabrefzone) if uk_recurse is not zero for them. The
01964 * reason is that it could lead to NULL being returned for
01965 * slab header allocations even in the M_WAITOK case, and the
01966 * caller can't handle that.
01967 */
01968 if
(keg->uk_flags & UMA_ZFLAG_INTERNAL && keg->uk_recurse != 0)
01969 if
(zone != slabzone && zone != slabrefzone && zone != zones)
01970 return
(NULL);
01971
01972 slab = NULL;
01973
01974 for
(;;) {
01975 /*
01976 * Find a slab with some space. Prefer slabs that are partially
01977 * used over those that are totally full. This helps to reduce
01978 * fragmentation.
01979 */
01980 if
(keg->uk_free != 0) {
01981 if
(!LIST_EMPTY(&keg->uk_part_slab)) {
slab
us_keg
us_link
us_data
data[0]
data[1]
data[2]
data[3]
.
.
.
slab
us_keg
us_link
us_data
data[0]
data[1]
data[2]
data[3]
.
.
.
slab
us_keg
us_link
us_data
data[0]
data[1]
data[2]
data[3]
.
.
.
slab
us_keg
us_link
us_data
data[0]
data[1]
data[2]
data[3]
.
.
.
keg
uk_link
part_slab
free_slab
full_slab
keg
uk_link
part_slab
free_slab
full_slab
keg
uk_link
part_slab
free_slab
full_slab
uma_kegs
/* 如果还有没有用完的slab 则使用它们*/
01982 slab = LIST_FIRST(&keg->uk_part_slab);
01983 } else
{
/* 否则就用全空的slab */
01984 slab = LIST_FIRST(&keg->uk_free_slab);
01985 LIST_REMOVE(slab, us_link);
01986 LIST_INSERT_HEAD(&keg->uk_part_slab, slab,
01987 us_link);
01988 }
01989 return
(slab);
01990 }
01991
01992 /*
01993 * M_NOVM means don't ask at all!
01994 */
01995 if
(flags & M_NOVM)
01996 break
;
01997
01998 if
(keg->uk_maxpages &&
01999 keg->uk_pages >= keg->uk_maxpages) {
02000 keg->uk_flags |= UMA_ZFLAG_FULL;
02001
02002 if
(flags & M_NOWAIT)
02003 break
;
02004 else
02005 msleep(keg, &keg->uk_lock, PVM,
02006 "zonelimit", 0);
02007 continue
;
02008 }
02009 keg->uk_recurse++;
/* 创建新的slab */
02010 slab = slab_zalloc(zone, flags);
02011 keg->uk_recurse--;
02012
02013 /*
02014 * If we got a slab here it's safe to mark it partially used
02015 * and return. We assume that the caller is going to remove
02016 * at least one item.
02017 */
02018 if
(slab) {
/* 把新生成的slab 加入uk_part_slab 链表表示此slab 还没有被用完*/
02019 LIST_INSERT_HEAD(&keg->uk_part_slab, slab, us_link);
02020 return
(slab);
02021 }
02022 /*
02023 * We might not have been able to get a slab but another cpu
02024 * could have while we were unlocked. Check again before we
02025 * fail.
02026 */
02027 if
(flags & M_NOWAIT)
02028 flags |= M_NOVM;
02029 }
02030 return
(slab);
02031 }
3. slab 的创建
2010 行可以看到,如果能用的slab 都已经用完了,则需要使用slab_zalloc 来创建新的
slab。
00771 /*
00772 * Allocate a new slab for a zone. This does not insert the slab onto a list.
00773 *
00774 * Arguments:
00775 * zone The zone to allocate slabs for
00776 * wait Shall we wait?
00777 *
00778 * Returns:
00779 * The slab that was allocated or NULL if there is no memory and the
00780 * caller specified M_NOWAIT.
00781 */
00782 static uma_slab_t
00783 slab_zalloc(uma_zone_t zone, int wait)
00784 {
00785 uma_slabrefcnt_t slabref;
00786 uma_slab_t slab;
00787 uma_keg_t keg;
00788 u_int8_t *mem;
00789 u_int8_t flags;
00790 int i;
00791
00792 slab = NULL;
00793 keg = zone->uz_keg;
00794
00795 #ifdef UMA_DEBUG
00796 printf("slab_zalloc: Allocating a new slab for %s/n", zone->uz_name);
00797 #endif
00798 ZONE_UNLOCK(zone);
00799
/*slab 所管理的内存一般直接用vm 的页分配器分配得到
*slab 结构体有两种方式存放
*一种就是放在vm 分配器分配到的1 个或多个连续页面的尾部,和数据公用这些页面
*一种是放在页面以外的地方
*/
00800 if
(keg->uk_flags & UMA_ZONE_OFFPAGE) {
/*slab 结构体在页面外,则要在专门用来申请slab 结构体的zone 里申请空间*/
00801 slab = uma_zalloc_internal(keg->uk_slabzone, NULL, wait);
00802 if
(slab == NULL) {
00803 ZONE_LOCK(zone);
00804 return
NULL;
00805 }
00806 }
00807
00808 /*
00809 * This reproduces the old vm_zone behavior of zero filling pages the
00810 * first time they are added to a zone.
00811 *
00812 * Malloced items are zeroed in uma_zalloc.
00813 */
00814
00815 if
((keg->uk_flags & UMA_ZONE_MALLOC) == 0)
00816 wait |= M_ZERO;
00817 else
00818 wait &= ~M_ZERO;
00819
/*利用更底层的分配器给slab 申请内存空间
*一般来说此处的uk_allocf 都是page_alloc,
*而在malloc 的大内存分配分支里,也是使用此函数进行内存页面分配
*page_alloc 直接调用vm 子系统里的页面内存分配函数kmem_malloc
*vm 子系统内的内存分配在后面再分析
*/
00820 mem = keg->uk_allocf(zone, keg->uk_ppera * UMA_SLAB_SIZE,
00821 &flags, wait);
00822 if
(mem == NULL) {
00823 if
(keg->uk_flags & UMA_ZONE_OFFPAGE)
00824 uma_zfree_internal(keg->uk_slabzone, slab, NULL,
00825 SKIP_NONE, ZFREE_STATFREE);
00826 ZONE_LOCK(zone);
00827 return
(NULL);
00828 }
00829
00830 /* Point the slab into the allocated memory */
00831 if
(!(keg->uk_flags & UMA_ZONE_OFFPAGE))
/*若slab 结构体和它管理的数据公用连续的页面
*把前uk_pgoff 个字节留出来做分配用
*剩下的用来存放slab 结构体
*/
00832 slab = (uma_slab_t )(mem + keg->uk_pgoff);
00833
00834 if
((keg->uk_flags & UMA_ZONE_MALLOC) ||
00835 (keg->uk_flags & UMA_ZONE_REFCNT))
00836 for
(i = 0; i < keg->uk_ppera; i++)
/* 给申请到的空间对应的vm_page 结构体上设置PG_SLAB 属性*/
00837 vsetslab((vm_offset_t)mem + (i * PAGE_SIZE), slab);
00838
00839 slab->us_keg = keg;
00840 slab->us_data = mem;
00841 slab->us_freecount = keg->uk_ipers;
00842 slab->us_firstfree = 0;
00843 slab->us_flags = flags;
00844
00845 if
(keg->uk_flags & UMA_ZONE_REFCNT) {
/*初始化slab 里的free_list 索引数组
*作用可以在待会分析uma_slab_alloc 的时候看到
*/
00846 slabref = (uma_slabrefcnt_t)slab;
00847 for
(i = 0; i < keg->uk_ipers; i++) {
00848 slabref->us_freelist[i].us_refcnt = 0;
00849 slabref->us_freelist[i].us_item = i+1;
00850 }
00851 } else
{
00852 for
(i = 0; i < keg->uk_ipers; i++)
00853 slab->us_freelist[i].us_item = i+1;
00854 }
00855
/* 如果配置了slab 的构造函数则调用构造函数*/
00856 if
(keg->uk_init != NULL) {
00857 for
(i = 0; i < keg->uk_ipers; i++)
00858 if
(keg->uk_init(slab->us_data + (keg->uk_rsize * i),
00859 keg->uk_size, wait) != 0)
00860 break
;
/* 如果构造函数没有完全成功,则调用析构函数将内存回退到之前的状态*/
00861 if
(i != keg->uk_ipers) {
00862 if
(keg->uk_fini != NULL) {
00863 for
(i--; i > -1; i--)
00864 keg->uk_fini(slab->us_data +
00865 (keg->uk_rsize * i),
00866 keg->uk_size);
00867 }
00868 if
((keg->uk_flags & UMA_ZONE_MALLOC) ||
00869 (keg->uk_flags & UMA_ZONE_REFCNT)) {
00870 vm_object_t obj;
00871
00872 if
(flags & UMA_SLAB_KMEM)
00873 obj = kmem_object;
00874 else
00875 obj = NULL;
00876 for
(i = 0; i < keg->uk_ppera; i++)
00877 vsetobj((vm_offset_t)mem +
00878 (i * PAGE_SIZE), obj);
00879 }
00880 if
(keg->uk_flags & UMA_ZONE_OFFPAGE)
00881 uma_zfree_internal(keg->uk_slabzone, slab,
00882 NULL, SKIP_NONE, ZFREE_STATFREE);
00883 keg->uk_freef(mem, UMA_SLAB_SIZE * keg->uk_ppera,
00884 flags);
00885 ZONE_LOCK(zone);
00886 return
(NULL);
00887 }
00888 }
00889 ZONE_LOCK(zone);
00890
00891 if
(keg->uk_flags & UMA_ZONE_HASH)
00892 UMA_HASH_INSERT(&keg->uk_hash, slab, mem);
00893
00894 keg->uk_pages += keg->uk_ppera;
00895 keg->uk_free += keg->uk_ipers;
00896
00897 return
(slab);
00898 }
00899
4. slab 分配
在bucket 的创建过程中可以看到,填充到ub_bucket 指针数组里的地址是由
uma_slab_alloc 分配的。
02033 static void *
02034 uma_slab_alloc(uma_zone_t zone, uma_slab_t slab)
02035 {
02036 uma_keg_t keg;
02037 uma_slabrefcnt_t slabref;
02038 void *item;
02039 u_int8_t freei;
02040
02041 keg = zone->uz_keg;
02042
02043 freei = slab->us_firstfree;
02044 if
(keg->uk_flags & UMA_ZONE_REFCNT) {
02045 slabref = (uma_slabrefcnt_t)slab;
02046 slab->us_firstfree = slabref->us_freelist[freei].us_item;
02047 } else
{
02048 slab->us_firstfree = slab->us_freelist[freei].us_item;
02049 }
02050 item = slab->us_data + (keg->uk_rsize * freei);
02051
02052 slab->us_freecount--;
02053 keg->uk_free--;
02057 /* Move this slab to the full list */
02058 if
(slab->us_freecount == 0) {
02059 LIST_REMOVE(slab, us_link);
02060 LIST_INSERT_HEAD(&keg->uk_full_slab, slab, us_link);
02061 }
02062
02063 return
(item);
02064 }
slab 分配过程比较简单。由于slab 是用来分配固定大小的内存单元,所以只要使用free
的索引号就可以计算出空闲的地址。us_firstfree 用来表示当前还没有分配的索引。从slab
里分配的时候,只需要使用这个索引从us_freelist 里取出下一个空闲的索引,放入
us_firstfree。然后计算出本次申请空间的首地址即可。
5. uma_zalloc_internal 的分配过程
uma_zalloc_internal 实际就是使用uma_slab_alloc 来分配空间。
02169 /*
02170 * Allocates an item for an internal zone
02171 *
02172 * Arguments
02173 * zone The zone to alloc for.
02174 * udata The data to be passed to the constructor.
02175 * flags M_WAITOK, M_NOWAIT, M_ZERO.
02176 *
02177 * Returns
02178 * NULL if there is no memory and M_NOWAIT is set
02179 * An item if successful
02180 */
02181
02182 static void *
02183 uma_zalloc_internal(uma_zone_t zone, void *udata, int flags)
02184 {
02185 uma_keg_t keg;
02186 uma_slab_t slab;
02187 void *item;
02188
02189 item = NULL;
02190 keg = zone->uz_keg;
02191
02195 ZONE_LOCK(zone);
02196
02197 slab = uma_zone_slab(zone, flags);
02198 if
(slab == NULL) {
02199 zone->uz_fails++;
02200 ZONE_UNLOCK(zone);
02201 return
(NULL);
02202 }
02203
02204 item = uma_slab_alloc(zone, slab);
02205
02206 zone->uz_allocs++;
02207
02208 ZONE_UNLOCK(zone);
02209
02210 /*
02211 * We have to call both the zone's init (not the keg's init)
02212 * and the zone's ctor. This is because the item is going from
02213 * a keg slab directly to the user, and the user is expecting it
02214 * to be both zone-init'd as well as zone-ctor'd.
02215 */
02216 if
(zone->uz_init != NULL) {
02217 if
(zone->uz_init(item, keg->uk_size, flags) != 0) {
02218 uma_zfree_internal(zone, item, udata, SKIP_FINI,
02219 ZFREE_STATFAIL | ZFREE_STATFREE);
02220 return
(NULL);
02221 }
02222 }
02223 if
(zone->uz_ctor != NULL) {
02224 if
(zone->uz_ctor(item, keg->uk_size, udata, flags) != 0) {
02225 uma_zfree_internal(zone, item, udata, SKIP_DTOR,
02226 ZFREE_STATFAIL | ZFREE_STATFREE);
02227 return
(NULL);
02228 }
02229 }
02230 if
(flags & M_ZERO)
02231 bzero(item, keg->uk_size);
02232
02233 return
(item);
02234 }
二、malloc 的uma_large_malloc 分支
看了小内存分配分支觉得分配还真是个体力活。没办法,谁让人家量小呢。不过一旦每次申
请的内存量比较大,那就好说了,因为即使有碎片,相对申请的量来说也可以接受了。申请1M 的
时候浪费个100 字节也无所谓。下面回顾一下malloc 里面大内存分配分支的那部分代码。
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);
看到了吧,把size 以PAGE_SIZE 对齐,然后用uma_large_malloc 去分配。极限情况下
也就浪费PAGE_SIZE - 1 字节。感觉这样直接对齐之后再分配还是比较奢侈的,有时间研究研
究这个地方取整对齐后到底浪费有多大,要是可以把浪费的部分分出来创建slab 倒是不错的。
uma_large_malloc 的代码如下,直接调用page_alloc 申请空间然后设置到一个slab
上。
02699 void *
02700 uma_large_malloc(int size, int wait)
02701 {
02702 void *mem;
02703 uma_slab_t slab;
02704 u_int8_t flags;
02705
02706 slab = uma_zalloc_internal(slabzone, NULL, wait);
02707 if
(slab == NULL)
02708 return
(NULL);
02709 mem = page_alloc(NULL, size, &flags, wait);
02710 if
(mem) {
02711 vsetslab((vm_offset_t)mem, slab);
02712 slab->us_data = mem;
02713 slab->us_flags = flags | UMA_SLAB_MALLOC;
02714 slab->us_size = size;
02715 } else
{
02716 uma_zfree_internal(slabzone, slab, NULL, SKIP_NONE,
02717 ZFREE_STATFAIL | ZFREE_STATFREE);
02718 }
02719
02720 return
(mem);
02721 }
page_alloc 调用的是vm 里的函数kmem_malloc 分配内存。
00937 /*
00938 * Allocates a number of pages from the system
00939 *
00940 * Arguments:
00941 * zone Unused
00942 * bytes The number of bytes requested
00943 * wait Shall we wait?
00944 *
00945 * Returns:
00946 * A pointer to the alloced memory or possibly
00947 * NULL if M_NOWAIT is set.
00948 */
00949 static void *
00950 page_alloc(uma_zone_t zone, int bytes, u_int8_t *pflag, int wait)
00951 {
00952 void *p; /* Returned page */
00953
00954 *pflag = UMA_SLAB_KMEM;
00955 p = (void *) kmem_malloc(kmem_map, bytes, wait);
00956
00957 return
(p);
00958 }