内存管理之12:守护进程kswapd

date: 2014-10-10 19:09node

1 kswapd的建立

为了不总在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函数的流程。对象

2函数调用关系

kswapd流程

2.1 kswapd

在进行一些简答的初始化后,kswapd进入一个死循环。死循环中的操做能够分为两部分:第一部分是当内存短缺时调用do_try_to_free_pages,目的在于预先找出若干页面,将这些页面的映射断开,而且使之从活跃状态变为不活跃状态,为页面换出作准备。第二部分是例行公事,每一个循环都会作的,那就是把不活跃的脏页面写入交换设备,使它们成为不活跃干净页面继续缓冲,或更进一步,回收一些这样的页面使之成为空闲页面。blog

处理完这两部分工做以后,kswapd已经设法回收了一些页面了,这时能够唤醒哪些因内存不足而转入睡眠的进程(参考内存页面分配流程)。若是本次尝试已经使得系统的有了必定的空闲内存“保有量”,为了避免占用CPU时间,kswapd像一个谦谦君子,主动转入睡眠,睡眠时间是1秒,但在睡眠期间运行被提早唤醒。若是内存仍然不足,那就只能忍痛杀死一个进程(oom_kill),牺牲小我成就大我,以此来回收一些内存。索引

2.2 do_try_to_free_pages

将活跃页面的映射断开,使之转入不活跃状态甚至最终被交换出去,这都是不得已而为之,是内核的一种痛。所以do_try_to_free_pages按照痛的等级从浅到深的顺序来选择页面。先调用page_launder来将不活跃脏页面洗净,使他们变成当即可分配的页面。若是可分配的页面数量仍然短缺,再从三个方面着手:队列

  • 其1、在打开文件的过程当中,要分配表明着目录项dentry的数据结构以及表明着索引节点inode的数据结构,这些数据结构在文件关闭是并无当即释放,而是放到LRU队列中养起来,以备后面打开同一个文件时再用到。这样通过一段时间之后,系统中积累起大量的这种结构,占据着可观的物理页面,是时候将它们适度回收了,这就是shrink_dcache_memory和shrink_icache_memory函数的做用。
  • 其2、内核中的slab分配器担负着为小对象分配内存的工做,slab一方面从内核“批发”成块的内存页面,再“零售”给内核中的小对象。会哭的孩子有奶吃,slab也倾向于向内核多要内存以做为本身的缓冲,却没有“及时归还”的美德,是时候回收一下了,kmem_cache_reap(“reap”的意思是收割)函数就是用于此目的。
  • 其3、没办法,只能将“场上”的球员换到“替补席”上了。选择若干活跃的页面,使之转入不活跃状态甚至交换出去,这些操做由函数refill_inactive领衔。

2.3 refill_inactive

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找到归宿,必要时,将页面的内容“转移”到对应的磁盘页面上就能够了。

相关文章
相关标签/搜索