linux服务器开发相关视频解析:
linux多线程之epoll原理剖析与reactor原理及应用
5个方面分析linux内核架构,让你对内核不再陌生
手把手带你实现一个Linux内核文件系统
内存池是用于预先申请一些内存用于备用,当系统内存不足无法从伙伴系统和slab中获取内存时,会从内存池中获取预留的那些内存。内存池与特殊slab一样,需要使用的设备需要自己创建内存池,而不是系统会自动生成。书上形容得好,内存比作新鲜食物,内存池比作罐头食物,人比作拥有此内存池的模块,当无法吃到新鲜食物时,就需要打开罐头吃罐头食物。
一般情况下,内存池建立在slab之上,也就是说池子里存放的是slab对象,当某个模块创建一个属于自己的内存池时,创建之前,需要设计好一个分配函数和一个释放函数,创建函数用于创建内存池时自动申请对应的slab对象,而释放函数则是用于将slab对象释放回到slab/slub中。当然经过看代码,感觉内存池中也可以存放页,这个可以写个模块测试一下。
具体先看看内存池主要的数据结构:
1 /* 内存池,用于拥有该内存池的"拥有者"进行内存储备,只有在常规情况下分配不到内存的时候才会使用自己的内存池 */
2 typedef struct mempool_s {
3 spinlock_t lock;
4 /* 最大元素个数,也是初始个数,当内存池被创建时,会调用alloc函数申请此变量相应数量的slab放到elements指向的指针数组中 */
5 int min_nr; /* nr of elements at *elements */
6 /* 当前元素个数 */
7 int curr_nr; /* Current nr of elements at *elements */
8 /* 指向一个数组,在mempool_create中会分配内存,数组中保存指向元素指针 */
9 void **elements;
10
11 /* 内存池的拥有者的私有数据结构,当元素是slab中的对象时,这里保存的是slab缓存描述符 */
12 void *pool_data;
13 /* 当元素是slab中的对象时,会使用方法mempool_alloc_slab()和mempool_free_slab() */
14 /* 分配一个元素的方法 */
15 mempool_alloc_t *alloc;
16 /* 释放一个元素的方法 */
17 mempool_free_t *free;
18 /* 当内存池为空时使用的等待队列,当内存池中空闲内存对象为空时,获取函数会将当前进程阻塞,直到超时或者有空闲内存对象时才会唤醒 */
19 wait_queue_head_t wait;
20 } mempool_t;
内核里使用mempool_create()创建一个内存池,使用mempool_destroy()销毁一个内存池,使用mempool_alloc()申请内存和mempool_free()是否内存。一切信息都在代码当中,我们直接看代码就清楚知道内存池是怎么实现的了。
首先我们先看mempool_create(),mempool_create()流程很简单,它主要做的就是分配一个mempool_t结构体,然后根据参数初始化此结构体,最后调用传入的alloc()函数min_nr次,把申请到的内存全部存放到elements中,如下:
1 mempool_t *mempool_create_node(int min_nr, mempool_alloc_t *alloc_fn,
2 mempool_free_t *free_fn, void *pool_data,
3 gfp_t gfp_mask, int node_id)
4 {
5 mempool_t *pool;
6 /* 分配一个内存池结构体 */
7 pool = kzalloc_node(sizeof(*pool), gfp_mask, node_id);
8 if (!pool)
9 return NULL;
10 /* 分配一个长度为min_nr的数组用于存放申请后对象的指针 */
11 pool->elements = kmalloc_node(min_nr * sizeof(void *),
12 gfp_mask, node_id);
13 if (!pool->elements) {
14 kfree(pool);
15 return NULL;
16 }
17 /* 初始化锁 */
18 spin_lock_init(&pool->lock);
19 pool->min_nr = min_nr;
20 /* 私有成员 */
21 pool->pool_data = pool_data;
22 /* 初始化等待队列 */
23 init_waitqueue_head(&pool->wait);
24 pool->alloc = alloc_fn;
25 pool->free = free_fn;
26
27 /*
28 * First pre-allocate the guaranteed number of buffers.
29 */
30 /* pool->curr_nr初始为0,因为pool使用kzalloc_node分配的,会清0 */
31 while (pool->curr_nr < pool->min_nr) {
32 void *element;
33
34 /* 调用pool->alloc函数min_nr次 */
35 element = pool->alloc(gfp_mask, pool->pool_data);
36 /* 如果申请不到element,则直接销毁此内存池 */
37 if (unlikely(!element)) {
38 mempool_destroy(pool);
39 return NULL;
40 }
41 /* 添加到elements指针数组中 */
42 add_element(pool, element);
43 }
44 /* 返回内存池结构体 */
45 return pool;
46 }
【文章福利】需要C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等)
再看看mempool_destroy(),此函数也很简单,直接将elements存放的内存依个释放掉,然后将该释放的elements指针数组和mempool_t结构都释放掉
1 /* 销毁一个内存池 */
2 void mempool_destroy(mempool_t *pool)
3 {
4 while (pool->curr_nr) {
5 /* 销毁elements数组中的所有对象 */
6 /* element = pool->elements[--pool->curr_nr] */
7 void *element = remove_element(pool);
8 pool->free(element, pool->pool_data);
9 }
10 /* 销毁elements指针数组 */
11 kfree(pool->elements);
12 /* 销毁内存池结构体 */
13 kfree(pool);
14 }
现在我们看mempool_alloc()函数,当模块从此内存池中获取内存对象时,会调用此函数,此函数优先从伙伴系统或slab缓冲区获取需要的内存对象,当内存不足导致无法获取内存对象时,才会从内存池elements数组中获取,如果elements也没有空闲的内存对象,根据传入的分配标识进行相应的处理:
1 /* 内存池分配对象 */
2 void * mempool_alloc(mempool_t *pool, gfp_t gfp_mask)
3 {
4 void *element;
5 unsigned long flags;
6 wait_queue_t wait;
7 gfp_t gfp_temp;
8
9 VM_WARN_ON_ONCE(gfp_mask & __GFP_ZERO);
10 /* 如果有__GFP_WAIT标志,则会先阻塞,切换进程 */
11 might_sleep_if(gfp_mask & __GFP_WAIT);
12
13 /* 不使用预留内存 */
14 gfp_mask |= __GFP_NOMEMALLOC; /* don't allocate emergency reserves */
15 /* 分配页时如果失败则返回,不进行重试 */
16 gfp_mask |= __GFP_NORETRY; /* don't loop in __alloc_pages */
17 /* 分配失败不提供警告 */
18 gfp_mask |= __GFP_NOWARN; /* failures are OK */
19
20 /* gfp_temp等于gfp_mask去除__GFP_WAIT和__GFP_IO的其他标志 */
21 gfp_temp = gfp_mask & ~(__GFP_WAIT|__GFP_IO);
22
23 repeat_alloc:
24
25 /* 使用内存池中的alloc函数进行分配对象,实际上就是从伙伴系统或者slab缓冲区获取内存对象 */
26 element = pool->alloc(gfp_temp, pool->pool_data);
27 /* 在内存富足的情况下,一般是能够获取到内存的 */
28 if (likely(element != NULL))
29 return element;
30
31 /* 在内存不足的情况,造成从伙伴系统或slab缓冲区获取内存失败,则会执行到这 */
32 /* 给内存池上锁,获取后此段临界区禁止中断和抢占 */
33 spin_lock_irqsave(&pool->lock, flags);
34 /* 如果当前内存池中有空闲数量,就是初始化时获取的内存数量保存在curr_nr中 */
35 if (likely(pool->curr_nr)) {
36 /* 从内存池中获取内存对象 */
37 element = remove_element(pool);
38 /* 解锁 */
39 spin_unlock_irqrestore(&pool->lock, flags);
40
41 /* 写内存屏障,保证之前的写操作已经完成 */
42 smp_wmb();
43 /* 用于debug */
44 kmemleak_update_trace(element);
45 return element;
46 }
47
48 /* 这里是内存池中也没有空闲内存对象的时候进行的操作 */
49 /* gfp_temp != gfp_mask说明传入的gfp_mask允许阻塞等待,但是之前已经阻塞等待过了,所以这里立即重新获取一次 */
50 if (gfp_temp != gfp_mask) {
51 spin_unlock_irqrestore(&pool->lock, flags);
52 gfp_temp = gfp_mask;
53 goto repeat_alloc;
54 }
55
56 /* 传入的参数gfp_mask不允许阻塞等待,分配不到内存则直接退出 */
57 if (!(gfp_mask & __GFP_WAIT)) {
58 spin_unlock_irqrestore(&pool->lock, flags);
59 return NULL;
60 }
61
62 init_wait(&wait);
63 /* 加入到内存池的等待队列中,并把当前进程的状态设置为只有wake_up信号才能唤醒的状态 ,也就是当内存池中有空闲对象时,会主动唤醒等待队列中的第一个进程,或者等待超时时,定时器自动唤醒 */
64 prepare_to_wait(&pool->wait, &wait, TASK_UNINTERRUPTIBLE);
65
66 spin_unlock_irqrestore(&pool->lock, flags);
67
68 /* 阻塞等待5秒 */
69 io_schedule_timeout(5*HZ);
70
71 /* 从内存池的等待队列删除此进程 */
72 finish_wait(&pool->wait, &wait);
73 /* 跳转到repeat_alloc,重新尝试获取内存对象 */
74 goto repeat_alloc;
75 }
76 EXPORT_SYMBOL(mempool_alloc);
最后我们看看mempool_free()函数,此函数用于将空闲内存对象释放到内存池中,当内存池中空闲对象不足时,优先将空闲内存对象放到elements数组中,否则直接返回到伙伴系统或slab缓冲区中。
1 /* 内存池释放内存对象操作 */
2 void mempool_free(void *element, mempool_t *pool)
3 {
4 unsigned long flags;
5
6 /* 传入的对象为空,则直接退出 */
7 if (unlikely(element == NULL))
8 return;
9
10 /* 读内存屏障 */
11 smp_rmb();
12
13 /* 如果当前内存池中空闲的内存对象少于内存池中应当保存的内存对象的数量时,优先把释放的对象加入到内存池空闲数组中 */
14 if (unlikely(pool->curr_nr < pool->min_nr)) {
15 spin_lock_irqsave(&pool->lock, flags);
16 if (likely(pool->curr_nr < pool->min_nr)) {
17 /* 加入到pool->elements[pool->curr_nr++]中 */
18 add_element(pool, element);
19 spin_unlock_irqrestore(&pool->lock, flags);
20 /* 唤醒等待队列中的第一个进程 */
21 wake_up(&pool->wait);
22 return;
23 }
24 spin_unlock_irqrestore(&pool->lock, flags);
25 }
26 /* 直接调用释放函数 */
27 pool->free(element, pool->pool_data);
28 }
29 EXPORT_SYMBOL(mempool_free);
或许看完这些还是不太清楚怎么使用内存池,毕竟alloc和free函数需要我们自己去设计,如果内存池是使用slab缓冲区进行内存分配时,可将slab缓冲区描述符写入到mempool_t中的pool_data中,alloc和free函数可以直接指定mempool_alloc_slab()和mempool_free_slab(),如下:
1 /* 创建一个slab缓冲区 */
2 drbd_request_cache = kmem_cache_create(
3 "drbd_req", sizeof(struct drbd_request), 0, 0, NULL);
4 if (drbd_request_cache == NULL)
5 goto Enomem;
6
7 /* 创建一个内存池,私有成员设置为drbd_request_cache这个slab缓冲区,alloc和free函数设置为mempool_alloc_slab()和mempool_free_slab() */
8 drbd_request_mempool = mempool_create(number,mempool_alloc_slab,mempool_free_slab,drbd_request_cache);
9 if (drbd_request_mempool == NULL)
10 goto Enomem;
11
12
13 /* 若内存池从slab缓冲区中获取内存对象,则内核提供的alloc函数 */
14 void *mempool_alloc_slab(gfp_t gfp_mask, void *pool_data)
15 {
16 struct kmem_cache *mem = pool_data;
17 return kmem_cache_alloc(mem, gfp_mask);
18 }
19 EXPORT_SYMBOL(mempool_alloc_slab);
20
21 /* 若内存池从slab缓冲区中获取内存对象,则内核提供的free函数 */
22 void mempool_free_slab(void *element, void *pool_data)
23 {
24 struct kmem_cache *mem = pool_data;
25 kmem_cache_free(mem, element);
26 }
27 EXPORT_SYMBOL(mempool_free_slab);