date: 2014-10-10 19:09node
为了不总在CPU忙碌时也就是缺页异常发生时,临时再来搜寻空换出的页面进行换出,内核将按期检查并预先将若干页面换出以腾出空间,维持系统空闲内存的的保有量,以减轻系统在缺页异常发生时的负担。为此内核设置了一个专司页面换出的守护神kswapd进程。数据结构
kswapd进程是个内核进程,这意味着,一方面kswapd没有对应用户空间(只有系统空间),可是有对应task_struct结构(所以调度器可能对它进行调度);一方面进程的可执行代码与内核连接在一块儿(编译到内核镜像中),所以他能够自由访问内核中的全局函数。函数
kswapd进程建立的代码以下:code
<mm/vmscan.c> 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_init在系统初始化期间被调用,它经过调用kernel_thread建立了两个内核进程kswapd和kreclaimd。kernel_thread的第一参数为“进程入口函数”,kswapd进程对应的进程函数为kswapd(其实进程的名字是在kswapd函数中设置的),kswapd函数的实如今同一文件中。下文主要分析kswapd函数的流程。对象
在进行一些简答的初始化后,kswapd进入一个死循环。死循环中的操做能够分为两部分:第一部分是当内存短缺时调用do_try_to_free_pages,目的在于预先找出若干页面,将这些页面的映射断开,而且使之从活跃状态变为不活跃状态,为页面换出作准备。第二部分是例行公事,每一个循环都会作的,那就是把不活跃的脏页面写入交换设备,使它们成为不活跃干净页面继续缓冲,或更进一步,回收一些这样的页面使之成为空闲页面。blog
处理完这两部分工做以后,kswapd已经设法回收了一些页面了,这时能够唤醒哪些因内存不足而转入睡眠的进程(参考内存页面分配流程)。若是本次尝试已经使得系统的有了必定的空闲内存“保有量”,为了避免占用CPU时间,kswapd像一个谦谦君子,主动转入睡眠,睡眠时间是1秒,但在睡眠期间运行被提早唤醒。若是内存仍然不足,那就只能忍痛杀死一个进程(oom_kill),牺牲小我成就大我,以此来回收一些内存。索引
将活跃页面的映射断开,使之转入不活跃状态甚至最终被交换出去,这都是不得已而为之,是内核的一种痛。所以do_try_to_free_pages按照痛的等级从浅到深的顺序来选择页面。先调用page_launder来将不活跃脏页面洗净,使他们变成当即可分配的页面。若是可分配的页面数量仍然短缺,再从三个方面着手:队列
refill_inactive主要的操做有两层循环,外层循环以扫描力度priority(初始值为6)为终止条件。内层有两个循环,其一是循环调用refill_inactive_scan,扫描全局的活跃页面队列active_list,试图从中找到能够转入不活跃状态的页面,挑选的准则是页面“寿命”(该寿命由kswapd的赐予并由kswapd“倒数”)以及页面使用计数双重标准。固然并非扫描队列中的每个页面,扫描力度由priority决定,当priority为0时才扫描每一个页面;其二是循环调用swap_out,选择一个进程(选择的准则“劫富济贫”,即找驻内存页面rss最多的进程),而后后扫描其页面映射表,找出其中能够转入不活跃状态的页面。读者也许会问,既然这个页面是有映射的(不然不会出如今进程的页面映射表中),那怎么会不在活跃页面队列active_list中呢?在讲页面换入时咱们会看到,当因页面页面而恢复一个不活跃页面的映射时,该页面并非当即就转到活跃页面队列,而把这项工做留给page_launder,让其在系统比较空闲时再来处理,因此这样的页面有可能不在活跃页面队列中。进程
refill_inactive_scan和swap_out每次尝试“换出”一个页面,若是空闲内存以及非活跃内存再也不短缺,则可提早退出外层循环,若是refill_inactive_scan和swap_out“换出”页面困难,那么外层循环将是一个巨大的循环,将消耗大量的CPU时间。咱们在《进程与进程调度》一章将看到在系统调用或者是中断服务程序返回用户空间以前,内核会检查task_struct结构中的need_resched字段,若是该字段置1,则要求调度。但kswapd是个内核进程,永远不会返回用户空间,这样就可能绕过这个检查而占着CPU不放,因此只能靠它“自律”了。在refill_inactive的每次外层循环以前,主动检查current->need_resched并请求一次调度。内存
前面说过,将页面换出到磁盘页面上时,pte_t已经变身为swp_entry_t,那么swp_entry_t的值从何而来,每次换出都须要从新指定吗?显然,若是该内存页面以前曾换出过,并且从那以后该内存页面的内容没有被改写,意味着页面是干净的,那么只需找到以前的“磁盘页面”,从新创建联系就能够了,并不须要真正的写出到磁盘。并且即便改写了,创建这种一对一的联系也便于咱们管理。为了此目的,page结构中有一个index成员(其实就是swp_entry_t),指向该内存页面在磁盘上的“归宿”,须要换出时,根据index找到归宿,必要时,将页面的内容“转移”到对应的磁盘页面上就能够了。