forked from 0voice/kernel_memory_management
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5f4cefe
commit 5fc813f
Showing
1 changed file
with
262 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
## 前沿 | ||
|
||
### 往篇回顾 | ||
|
||
> 在上一篇中,着重分析了匿名页RMAP的机制及建立过程 | ||
- 在malloc触发缺页中断、写时复制等场景下产生匿名页面的同时建立新页的RMAP,主要有下面几个步骤 | ||
1. 通过anon_vma_prepare()创建新页对应vma的av,avc;并使用anon_vma_chain_link初始化结构体数据,建立该vma的RMAP基础框架 | ||
2. 从Buddy sys中申请到新页面后,使用`page_add_new_anon_rmap`为该页面添加RMAP;主要处理struct page,struct av,struct avc成员变量初始化 | ||
- 在fork后,要更新进程的RMAP框架,要让页面能反向找到子进程,关键子进程对父进程各个vma的复制 | ||
1. 在`dup_mmap()`函数中,遍历所有父进程所有非VM_DONTCOPY的vma,使用`anon_vma_fork`复制对应的子进程vma | ||
2. 在`anon_vma_fork`中首先使用`anon_vma_clone`对挂在vma->anon_vma_chain中的所有avc进行复制,并使用anon_vma_chain_link使复制的avc加入子进程RMAP,当然要加入父进程的av红黑树 | ||
3. 在avc的复制后,在`anon_vma_fork`再建立完全属于子进程的av,avc | ||
|
||
### 匿名页的诞生 | ||
|
||
- malloc/mmap(共享)分配内存,发生缺页中断,调用`do_anonymous_page()`产生匿名页面 | ||
- 发生写时复制,缺页中断出现写保护错误 | ||
1. `do_wp_page()`: fork后访问页面触发、页面swap in触发的写时复制 | ||
2. `do_cow_page()`: 共享文件匿名页(do_fault_page->do_cow_page) | ||
- do_swap_page(),从swap分去读回数据时会新分配匿名页 | ||
- 迁移页面 | ||
|
||
### 内核内存池简介 | ||
|
||
- 内存池是用于预先申请一些内存用于备用,当系统内存不足无法从伙伴系统和slab中获取内存时,会从内存池中获取预留的那些内存; | ||
- 内核里使用mempool_create()创建一个内存池,使用mempool_destroy()销毁一个内存池,使用mempool_alloc()申请内存和mempool_free()释放内存 | ||
|
||
### 本篇主要内容 | ||
|
||
> 内核内存池的使用流程分析 | ||
## 代码分析 | ||
|
||
### mempool_create_node | ||
|
||
mempool_create->mempool_create_node | ||
|
||
```c | ||
/* | ||
* 新建一个mempool,需要用户自己传入内存申请及释放函数 | ||
* 初始化mempool_t管理结构体,过程清晰简单 | ||
*/ | ||
mempool_t *mempool_create_node(int min_nr, mempool_alloc_t *alloc_fn, | ||
mempool_free_t *free_fn, void *pool_data, | ||
gfp_t gfp_mask, int node_id) | ||
{ | ||
mempool_t *pool; | ||
pool = kzalloc_node(sizeof(*pool), gfp_mask, node_id); | ||
if (!pool) | ||
return NULL; | ||
pool->elements = kmalloc_node(min_nr * sizeof(void *), | ||
gfp_mask, node_id); | ||
if (!pool->elements) { | ||
kfree(pool); | ||
return NULL; | ||
} | ||
spin_lock_init(&pool->lock); | ||
pool->min_nr = min_nr; | ||
pool->pool_data = pool_data; | ||
init_waitqueue_head(&pool->wait); | ||
pool->alloc = alloc_fn; | ||
pool->free = free_fn; | ||
|
||
/* | ||
* First pre-allocate the guaranteed number of buffers. | ||
*/ | ||
while (pool->curr_nr < pool->min_nr) { | ||
void *element; | ||
/* 调用pool->alloc函数min_nr次 */ | ||
element = pool->alloc(gfp_mask, pool->pool_data); | ||
/* 如果申请不到element,则直接销毁此内存池 */ | ||
if (unlikely(!element)) { | ||
mempool_destroy(pool); | ||
return NULL; | ||
} | ||
/* 添加到elements指针数组中 */ | ||
add_element(pool, element); | ||
} | ||
/* 返回内存池结构体 */ | ||
return pool; | ||
} | ||
``` | ||
### mempool_destroy | ||
```c | ||
/* | ||
* 销毁一个内存池 | ||
* 初始化mempool_t管理结构体 | ||
*/ | ||
void mempool_destroy(mempool_t *pool) | ||
{ | ||
while (pool->curr_nr) { | ||
/* 销毁elements数组中的所有对象 */ | ||
void *element = remove_element(pool); | ||
pool->free(element, pool->pool_data); | ||
} | ||
/* 销毁elements指针数组 */ | ||
kfree(pool->elements); | ||
/* 销毁内存池结构体 */ | ||
kfree(pool); | ||
} | ||
``` | ||
|
||
### mempool_alloc | ||
|
||
```c | ||
/* | ||
* 从mempool_t中申请内存: | ||
* 1. 首先从pool->pool_data中申请元素对象(即从伙伴系统、slub cache中申请) | ||
* 2. 再尝试从mempool中申请 | ||
* 3. mempool申请失败,且分配允许等待;则进程加入pool->wait等待队列 | ||
a. 在init_wait中初始化wait_queue_t wait结构体 | ||
b. 在prepare_to_wait中将本进程的wait结构体加入pool->wait | ||
c. 在io_schedule_timeout中本进入睡眠状态5s,并触发进程调度 | ||
* 4. 在finish_wait中触发本进程恢复执行,并再次执行1,2,3,4尝试分配内存 | ||
*/ | ||
void * mempool_alloc(mempool_t *pool, gfp_t gfp_mask) | ||
{ | ||
void *element; | ||
unsigned long flags; | ||
wait_queue_t wait; | ||
gfp_t gfp_temp; | ||
|
||
VM_WARN_ON_ONCE(gfp_mask & __GFP_ZERO); | ||
/* 如果有__GFP_WAIT标志,则会先阻塞,切换进程 */ | ||
might_sleep_if(gfp_mask & __GFP_WAIT); | ||
/* 不使用预留内存 */ | ||
gfp_mask |= __GFP_NOMEMALLOC; /* don't allocate emergency reserves */ | ||
/* 分配页时如果失败则返回,不进行重试 */ | ||
gfp_mask |= __GFP_NORETRY; /* don't loop in __alloc_pages */ | ||
/* 分配失败不提供警告 */ | ||
gfp_mask |= __GFP_NOWARN; /* failures are OK */ | ||
/* gfp_temp等于gfp_mask去除__GFP_WAIT和__GFP_IO的其他标志 */ | ||
gfp_temp = gfp_mask & ~(__GFP_WAIT|__GFP_IO); | ||
|
||
repeat_alloc: | ||
/* 使用内存池中的alloc函数进行分配对象,实际上就是从伙伴系统或者slab缓冲区获取内存对象 */ | ||
element = pool->alloc(gfp_temp, pool->pool_data); | ||
/* 在内存富足的情况下,一般是能够获取到内存的 */ | ||
if (likely(element != NULL)) | ||
return element; | ||
/* 在内存不足的情况,造成从伙伴系统或slab缓冲区获取内存失败,则会执行到这 */ | ||
/* 给内存池上锁,获取后此段临界区禁止中断和抢占 */ | ||
spin_lock_irqsave(&pool->lock, flags); | ||
/* 如果当前内存池中有空闲数量,就是初始化时获取的内存数量保存在curr_nr中 */ | ||
if (likely(pool->curr_nr)) { | ||
/* 从内存池中获取内存对象 */ | ||
element = remove_element(pool); | ||
spin_unlock_irqrestore(&pool->lock, flags); | ||
/* 写内存屏障,保证之前的写操作已经完成 */ | ||
smp_wmb(); | ||
/* 用于debug */ | ||
kmemleak_update_trace(element); | ||
return element; | ||
} | ||
|
||
/* 这里是内存池中也没有空闲内存对象的时候进行的操作 */ | ||
/* gfp_temp != gfp_mask说明传入的gfp_mask允许阻塞等待,但是之前已经阻塞等待过了,所以这里立即重新获取一次 */ | ||
if (gfp_temp != gfp_mask) { | ||
spin_unlock_irqrestore(&pool->lock, flags); | ||
gfp_temp = gfp_mask; | ||
goto repeat_alloc; | ||
} | ||
|
||
/* 传入的参数gfp_mask不允许阻塞等待,分配不到内存则直接退出 */ | ||
if (!(gfp_mask & __GFP_WAIT)) { | ||
spin_unlock_irqrestore(&pool->lock, flags); | ||
return NULL; | ||
} | ||
|
||
/* Let's wait for someone else to return an element to @pool */ | ||
init_wait(&wait); | ||
/* 将当前进程wait结构体加入到内存池的等待队列中,并把状态设置为只有wake_up信号才能唤醒的状态 | ||
* 1. 当内存池有新释放内存时,会主动唤醒(wake_up)等待队列中的第一个进程 | ||
* 2. 等待超时,定时器自动唤醒 | ||
*/ | ||
prepare_to_wait(&pool->wait, &wait, TASK_UNINTERRUPTIBLE); | ||
|
||
spin_unlock_irqrestore(&pool->lock, flags); | ||
|
||
/* | ||
* FIXME: this should be io_schedule(). The timeout is there as a | ||
* workaround for some DM problems in 2.6.18. | ||
*/ | ||
/* 阻塞等待5秒 */ | ||
io_schedule_timeout(5*HZ); | ||
/* 从内存池的等待队列删除此进程 */ | ||
finish_wait(&pool->wait, &wait); | ||
/* 跳转到repeat_alloc,重新尝试获取内存对象 */ | ||
goto repeat_alloc; | ||
} | ||
``` | ||
### mempool_free | ||
```c | ||
/* | ||
* 释放内存 | ||
* 1. 如果内存池中水位低于min_nr,则将释放的内存加入内存池中,并wake_up wait队列中任务 | ||
* 2. 否则直接释放内存 | ||
*/ | ||
void mempool_free(void *element, mempool_t *pool) | ||
{ | ||
unsigned long flags; | ||
/* 传入的对象为空,则直接退出 */ | ||
if (unlikely(element == NULL)) | ||
return; | ||
/* 读内存屏障 */ | ||
smp_rmb(); | ||
/* pool->curr_nr < pool->min_nr,优先把释放的对象加入到内存池空闲数组中 */ | ||
if (unlikely(pool->curr_nr < pool->min_nr)) { | ||
spin_lock_irqsave(&pool->lock, flags); | ||
if (likely(pool->curr_nr < pool->min_nr)) { | ||
add_element(pool, element); | ||
spin_unlock_irqrestore(&pool->lock, flags); | ||
/* 唤醒(default_wake_function)等待队列中的第一个进程 */ | ||
wake_up(&pool->wait); | ||
return; | ||
} | ||
spin_unlock_irqrestore(&pool->lock, flags); | ||
} | ||
/* 直接调用释放函数 */ | ||
pool->free(element, pool->pool_data); | ||
} | ||
``` | ||
|
||
### 内核中mempool例子可以搜索mempool_create函数被调用的地方 | ||
|
||
## 附录 | ||
|
||
### mempool_s | ||
|
||
```c | ||
typedef struct mempool_s { | ||
spinlock_t lock; | ||
/* 最大元素个数,也是初始个数 | ||
* 当内存池被创建时,会调用alloc函数申请此变量相应数量的slab放到elements指向的指针数组中 | ||
*/ | ||
int min_nr; | ||
int curr_nr; /* 当前元素个数 */ | ||
/* 指向一个数组,在mempool_create中会分配内存,数组中保存指向元素指针 */ | ||
void **elements; | ||
/* 内存池的拥有者的私有数据结构,当元素是slab中的对象时,这里保存的是slab缓存描述符 */ | ||
void *pool_data; | ||
/* 当元素是slab中的对象时,会使用方法mempool_alloc_slab()和mempool_free_slab() */ | ||
/* 分配一个元素的方法 */ | ||
mempool_alloc_t *alloc; | ||
/* 释放一个元素的方法 */ | ||
mempool_free_t *free; | ||
/* 当内存池为空时使用的等待队列 | ||
* 当内存池中空闲内存对象为空时,获取函数会将当前进程阻塞,直到超时或者有空闲内存对象时才会唤醒 */ | ||
wait_queue_head_t wait; | ||
} mempool_t; | ||
``` | ||
|
||
## 参考资料 | ||
|
||
[tolimit-内存池](https://www.cnblogs.com/tolimit/p/5266575.html) |