当物理内存出现不足时,Linux内存管理子系统需要释放部分物理页面内存。这一人任务由内核的交换守护进程kswapd完成,该内核守护进程实际是一个内核线程,它在内核初始化时启动,并周期的运行。就是为了保证系统中具有足够的空闲页面,从而使内存管理子系统能够正常运行法。
一.交换的基本原理
在计算机技术的发展史上很早就有了把内存的内容与一个专用的磁盘空间交换技术,在Linux中,把用作交换的磁盘空间叫交换文件或交换区。
当空闲内存数量小于一个固定的极限值时,就执行换出操作。换出操作包含把进程的整个地址空间拷贝到磁盘上。反之,当调度算法选择出一个进程时,整个进程又被从磁盘中交换出来。但是在现在,交换的单位是页面而不是进程。但交换还是需要付出一定的代价,尤其是时间的代价。页面交换是不得已而为之,因为页面交换用时间交换空间,所以在时间要求比较紧急的实时系统中,是不宜采用页面交换机制的。因为这一点,所以Linux给用户提供了一种选择,可以通过命令或系统调用开启或关闭交换机制。
在页面交换中,页面置换算法是影响交换性能的关键性指标,其复杂性主要与换出有关。
我们在这里所提到的页或页面指的是其中存放的数据,所谓页面的换入换出实际上是指页面中数据的换入换出。
1.那种页面被换出
实际上,交换的最终目的是页面的回收。而在内存中并非所有的页面都是可以被换出的,只有与用户空间建立映射的页面才会被换出,而内核空间中内核所占的页面则常驻页面。
可以把用户空间中的页面按其内容和性质分为以下几种。
(1)进程映像所占的页面,包括进程的代码段,数据段,堆栈段以及动态分配的“存储堆”。(进程的代码段,数据段所占的内存页面可以被换入换出,但堆栈所占的页一般不被换出,因为这样可以简化内核的设计)
(2)通过系统调用mmap()把文件的内容映射到用户空间。(这些页面所使用的交换区就是被映射的文件本身)
(3)进程共享区间。(其页面的换入换出都比较复杂)
映射到内核空间中的页面都不会被换出。具体来说,内核代码和内核中的全局变量所占有的内存页面即不需要分配(启动时被装入),也不会被释放,这部分空间是静态的。相比之下,进程的代码段和全局量都在用户空间,所占的内存页面都是动态的,使用前要经过分配,最后都会被释放,中途可能被换出而回收后另行分配。除此之外,内核在执行过程中使用的页面要经过动态分配,但永驻内存,此类页面根据其内容和性质可以飞为以下两种:
(1)内核调用kmalloc()或vmalloc()为内核中临时使用的数据结构而分配的页面用完立即释放。但是,由于一个页面中存放有多种类型的数据结构,所以要到整个页民都空闲时才释放该页面。
(2)内核调用__get_free_pages()为某些临时使用的管理目的而分配的页面,例如,每个进程的内核所占的两个页面,从内核空间复制参数时所使用的页面等,这些页面页是一旦使用完毕便无保存的价值,立即释放。
在内核中还有一种页面,虽然使用完毕,但其内容仍有价值,因此,并不立即释放。这类页面“释放“之后进入一个LRU(最近最少使用)队列,经过一段时间的缓冲让其“老化”。如果在此期间有要用到其内容了,就又将其投入使用,否则便继续让其老化,直到条件不再允许时才加以回收。这种用途的内核页面大致有:
(1)文件系统中用来缓冲存储一些文件目录结构dentry的空间。
(2)文件系统用来缓冲存储一些索引节点inode的空间。
(3)用于文件系统读写操作的缓冲区。
2.如何在交换区间存放页面
我们知道物理内存被划分为若干个页面,每个页面的大小为4KB。实际上,交换区也被划分为块,每个块的大小正好等于一页,我们把交换区中的一块叫做一个页插槽。意思是说,把一个物理页面插入到一个插槽中,当进行换出,内核尽可能把换出的页面放在相邻的插槽中,从而减少在访问交换区时磁盘的寻道时间。这是高效的页面置换算法的物质基础。
如果系统使用多个交换区,事情就变的复杂了。快速交换区(也就是存放在快速磁盘中的交换区)可以获得比较高的优先级。当查找一个空闲插槽时,有从优先级最高的交换区中开始搜索。如果优先级最高的交换区不止一个,为了避免超负荷的使用其中一个,应该循环选择相同优先级的交换区。如果在优先级最高的交换区没有找到空闲插槽,就在优先级次高的交换区中继续进行搜索,以此类推。
3.如何选择被交换出的页面
页面交换时非常复杂的,其主要内容之一就是如何选择要换出的页面,下面以循序渐进的方式来讨论页面交换策略的选择。
(1).需要时在交换。每当缺页异常发生时,就给它分配一个物理页面。如果发现没有空闲的页面可以提供分配,就设法将一个或多个页内存页面换出到磁盘上,从而腾出一些内存页面。但该方法是被动的交换策略,所以会付出相当多的时间进行换入换出。
(2).系统空闲时交换。在系统空闲时,预先换车一些页面而腾出一些内存页面,从而在内存中维持一定的空闲页面供应量,使得在缺页中断发生时总有空闲页面可以使用。至于换出页面一般都选择最近最少使用的算法。但是并没有哪种方法可以预测对页面的访问,最坏的情况便是刚刚换出的页面又要被访问到,这样一来可能整个系统的处理能力都被这样的换入换出影响,而不能进行有效的计算和操作(这种现象被称为页面的抖动)。
(3).换出但不立即释放。当系统的跳出若干页面进行换出时,将相应的页面写入磁盘交换区中,并修改相应页表中页表项的内容(把Present标志位设置为0),但是并不立即释放,而时将其Page结构留在一个缓冲(Cache)队列中,使其从活跃状态转为不活跃状态。至于这些页面的最后释放,要推迟到必要时才进行。这样,如果一个页面在释放后由立即受到访问,就可以在物理页面的缓冲队列中找到相应的页面,再次为之建立映射。由于页面并未释放,还保留着原先的内容,就不需要磁盘读入了。经过一段时间后,一些不活跃的内存页面一直没受到访问,那么这个页面就需要真正的被释放。
(4).把页面换出推迟到不能再推迟为止。实际上,第三种方法还有可以改进的地方。首先在换出页面时不一定要把它的内容写入磁盘。如果一个页面自最近一次换入后并没有被写过(如代码),那么这个页面就是“干净的”,就没有必要把它写入磁盘。其次,即使时“脏”页面,也没有必要立即写出去,可以采用方法三。至于“干净”页面,可以一直缓冲到必要时才加以回收,因为回收一个“干净的”页面花费的代价很小。
下面队伍里页面的换入换出给出一个概要描述,这里涉及前面介绍的page结构和free_area结构。
释放页面。如果一个页面变为空闲可用,就把该页面的page结构链入某个空闲队列free_area,同时页面的使用计数_count减1.
分配页面。调用__get_free_page()从某个空闲队列分配内存页面,并将其页面的使用计数_count置为1。
活跃状态。已分配的页面处于活跃状态,该页面的数据结构page通过其队列头结构lru链入活跃页面队列active_list,并且在进程地址空间中至少由一个页面与该页面进行了映射关系。
不活跃“脏状态”。处于该状态的页面其page结构通过其队列头结构lru链入不活跃“脏页面”队列inactive_dirty_list,并且原则是任何进程的页面表项不再指向该页面,也就是说断开页面的映射,同时把页面的的使用计数_count减1。将不活跃“脏”页面的内容写入交换区,并将该页面的page结构从不活跃“脏”页面队列inactive_dirty_lis转移到不活跃“干净”页面队列,准备被回收。
如果在转入不活跃状态以后的一段时间内,页面又受到访问,则又转入活跃状态并恢复映射。
当需要时,就从“干净”页面队列中回收页面,也就是说把页面链入到空闲页面,或者直接进行分配。
二 页面交换守护进程kswapd
为了避免CPU在忙碌时,也就是在缺页异常发生时,临时搜索可供换出的内存页面加以换出,Linux内核定期的检查系统内存内空闲的页面是否小于预定以的极限,一旦发现空闲页面数太少,就预先将若干页面换出,以减轻缺页异常发生时系统所承受的负担。当然,由于无法确切的预测页面的使用,即使这样做了,也还可能出现缺页异常发生时内存依然没有足够的空县页面的情况。但是预换出毕竟减少空闲页面不够用的概率。并且通过选择释放的参数(如每隔多久换出一次,每次换出多少页),可以使临时寻找要换出页面的情况很少发生。为此,Linux内核设置了一个定期将页面换出的守护进程kswapd。
从原理上说,kswapd相当于一个进程,它有自己的进程控制块task_struct结构。与其它进程一样受内核调度。而正因为内核将它按照进程来调度,所以可以让它在系统相对空闲的时候来运行。不过,与普通进程相比,kswapd有其特殊性。首先,它并没有自己独立的地址空间,所以在近代操作系统理论中把它称为“线程”或“守护进程”以与进程相区别。
kswapd每隔多久时间运行一次,这是由内核设计时所确定的一个常量HZ决定。HZ决定了内核中每秒时钟中断的次数,用户可以在编译内核前的系统配置阶段更改,但是一经编译就无法改变了。在Linux2.4中,每秒钟kswapd被调用一次。
kswapd的执行路线分为两部分,第一部分是发现物理页面已经短缺的情况下才执行的,目的在于预先找出若干页面,且将这些页面的映射断开,是这些物理页面从活跃状态转入不活跃状态,为页面的换出做好准备。第二部分是每次都要执行的,目的在于把已经处于不活跃状态的脏页面写出交换区,使之成为不活跃的干净页面继续缓冲,或者进一步回收这样的页面称为空闲页面。