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

date: 2014-10-10 19:09

1 kswapd的创建

为了避免总在CPU忙碌时也就是缺页异常发生时,临时再来搜寻空换出的页面进行换出,内核将定期检查并预先将若干页面换出以腾出空间,维持系统空闲内存的的保有量,以减轻系统在缺页异常发生时的负担。为此内核设置了一个专司页面换出的守护神kswapd进程。

kswapd进程是个内核进程,这意味着,一方面kswapd没有对应用户空间(只有系统空间),但是有对应task_struct结构(因此调度器可能对它进行调度);一方面进程的可执行代码与内核链接在一起(编译到内核镜像中),因此他可以自由访问内核中的全局函数。

kswapd进程创建的代码如下:

    <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,目的在于预先找出若干页面,将这些页面的映射断开,并且使之从活跃状态变为不活跃状态,为页面换出做准备。第二部分是例行公事,每个循环都会做的,那就是把不活跃的脏页面写入交换设备,使它们成为不活跃干净页面继续缓冲,或更进一步,回收一些这样的页面使之成为空闲页面。

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

2.2 do_try_to_free_pages

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

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

转载于:https://my.oschina.net/u/3857782/blog/1854548

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值