我们已经看到在分配页面时,如果页面数不够,那么会调用page_launder,reclaim_page,__free_page将页面换出,并重新投入分配。
为了避免总是在CPU忙碌的时候,也就是在缺页异常发生的时候,临时再来搜寻可供换出的内存页面并加以换出,Linux内核定期地检查并且预先将若干页面换出,腾出空间,以减轻系统在缺页异常发生时的负担。
为此,在Linux内核中设置了一个专司定期将页面换出的“守护神”kswapd和kreclaimd。
static int __init kswapd_init(void)
{
printk("Starting kswapd v1.8\n");
swap_setup();
kernel_thread(kswapd, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
kernel_thread(kreclaimd, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
return 0;
}
启动了两个内核线程,kswapd和kreclaimd。
首先分析kswapd,代码如下:
int kswapd(void *unused)
{
struct task_struct *tsk = current;
tsk->session =1;
tsk->pgrp = 1;
strcpy(tsk->comm, "kswapd");
sigfillset(&tsk->blocked);
kswapd_task = tsk;
.....
tsk->flags |= PF_MEMALLOC;//执行公务,标志位置1
/*
* Kswapd main loop.
*/
for (;;) {
static int recalc = 0;
/* If needed, try to free some memory. */
if (inactive_shortage() || free_shortage()) {
int wait = 0;
/* Do we need to do some synchronous flushing? */
if (waitqueue_active(&kswapd_done))
wait = 1;
do_try_to_free_pages(GFP_KSWAPD, wait);//主体函数
}
......
refill_inactive_scan(6, 0);
......
......
if (!free_shortage() || !inactive_shortage()) {
interruptible_sleep_on_timeout(&kswapd_wait, HZ);//每隔1秒钟唤醒一次,继续执行循环
......
} else if (out_of_memory()) {
oom_kill();
}
}
}
在一些简单的初始化操作以后,程序便进入一个无限循环。在每次循环的末尾一般都会调用interruptible_sleep_on_timeout()进入睡眠,让内核自由地调度别的进程运行。但是内核在1秒钟后又会唤醒并调度kswapd继续运行,这时候kswapd就又回到这无限循环开始的地方。
这个函数执行的主体函数是do_try_to_free_pages,代码如下:
static int do_try_to_free_pages(unsigned int gfp_mask, int user)
{
int ret = 0;
......
if (free_shortage() || nr_inactive_dirty_pages > nr_free_pages() +
nr_inactive_clean_pages())
ret += page_launder(gfp_mask, user);
......
if (free_shortage() || inactive_shortage()) {
shrink_dcache_memory(6, gfp_mask);
shrink_icache_memory(6, gfp_mask);
ret += refill_inactive(gfp_mask, user);
} else {
......
kmem_cache_reap(gfp_mask);
ret = 1;
}
return ret;
}
shrink_dcache_memory和shrink_icache_memory用来回收积累起来的大量的dentry数据结构和inode数据结构。这些数据结构在文件关闭以后并不立即释放,而是放在LRU队列中作为后备,以防不久将来的文件操作又要用到。
kmem_cache_reap用于收割slab块。slab管理机制也是倾向于分配和保持更多的空闲物理页面,而不热衷于退还这些页面,所以过一段时间就要通过kmem_cache_reap来收割。
一、我们首先分析的是refill_inactive,代码如下:
static int refill_inactive(unsigned int gfp_mask, int user)
{
int priority, count, start_count, made_progress;
count = inactive_shortage() + free_shortage();
if (user)
count = (1 << page_cluster);
start_count = count;
/* Always trim SLAB caches when memory gets low. */
kmem_cache_reap(gfp_mask);//收割slab
priority = 6;//循环从优先级最低的6级开始,逐步加大"力度"直到0级,
do {
made_progress = 0;
if (current->need_resched) {//内核线程必须自律,因为永远不会返回用户空间,就永远不会检查这个标志位
__set_current_state(TASK_RUNNING);//表示希望继续执行的愿望
schedule();//调度
}
while (refill_inactive_scan(priority, 1)) {
made_progress = 1;
if (--count <= 0)//达到目标,就提前结束
goto done;
}
......
shrink_dcache_memory(priority, gfp_mask);//回收积累起来的大量的dentry数据结构和inode数据结构
shrink_icache_memory(priority, gfp_mask);
......
while (swap_out(priority, gfp_mask)) {
made_progress = 1;
if (--count <= 0)//达到目标,就提前结束
goto done;
}
......
if (!inactive_shortage() || !free_shortage())
goto done;//不缺少页面了,也提前结束
......
if (!made_progress)
priority--;
} while (priority >= 0);
/* Always end on a refill_inactive.., may sleep... */
while (refill_inactive_scan(0, 1)) {
if (--count <= 0)
goto done;
}
done:
return (count < start_count);
}
1、refill_inactive_scan函数,如下:
int refill_inactive_scan(unsigned int priority, int oneshot)
{
struct list_head * page_lru;
struct page * page;
int maxscan, page_active = 0;
int ret = 0;
/* Take the lock while messing with the list... */
spin_lock