前沿
往篇回顾
在上一篇中,着重分析了匿名页RMAP的机制及建立过程
-
在malloc触发缺页中断、写时复制等场景下产生匿名页面的同时建立新页的RMAP,主要有下面几个步骤
-
通过anon_vma_prepare()创建新页对应vma的av,avc;并使用anon_vma_chain_link初始化结构体数据,建立该vma的RMAP基础框架
-
从Buddy sys中申请到新页面后,使用
page_add_new_anon_rmap
为该页面添加RMAP;主要处理struct page,struct av,struct avc成员变量初始化
-
-
在fork后,要更新进程的RMAP框架,要让页面能反向找到子进程,关键子进程对父进程各个vma的复制
-
在
dup_mmap()
函数中,遍历所有父进程所有非VM_DONTCOPY的vma,使用anon_vma_fork
复制对应的子进程vma -
在
anon_vma_fork
中首先使用anon_vma_clone
对挂在vma->anon_vma_chain中的所有avc进行复制,并使用anon_vma_chain_link使复制的avc加入子进程RMAP,当然要加入父进程的av红黑树 -
在avc的复制后,在
anon_vma_fork
再建立完全属于子进程的av,avc
-
匿名页的诞生
-
malloc/mmap(共享)分配内存,发生缺页中断,调用
do_anonymous_page()
产生匿名页面 -
发生写时复制,缺页中断出现写保护错误
-
do_wp_page()
: fork后访问页面触发、页面swap in触发的写时复制 -
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
/*
* 新建一个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
/*
* 销毁一个内存池
* 初始化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
/*
* 从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
/*
* 释放内存
* 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
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;