一文看懂 | 内存交换机制
扫描二维码
随时随地手机看文章
本文基于 Linux-2.4.16 内核版本由于计算机的物理内存是有限的, 而进程对内存的使用是不确定的, 所以物理内存总有用完的可能性. 那么当系统的物理内存不足时, Linux内核使用什么方案来避免申请不到物理内存这个问题呢?
相对于内存来说, 磁盘的容量是非常大的, 所以Linux内核实现了一个叫
内存交换
的功能 -- 把某些进程的一些暂时用不到的内存页保存到磁盘中, 然后把物理内存页分配给更紧急的用户使用, 当进程用到时再从磁盘读回到内存中即可. 有了 内存交换
功能, 系统可使用的内存就可以远远大于物理内存的容量.LRU算法
内存交换
过程首先是找到一个合适的用户进程内存管理结构,然后把进程占用的内存页交换到磁盘中,并断开虚拟内存与物理内存的映射,最后释放进程占用的内存页。由于涉及到IO操作,所以这是一个比较耗时的过程。如果被交换出去的内存页刚好又被访问了,这时又需要从磁盘中把内存页的数据交换到内存中。所以,在这种情况下不单不能解决内存紧缺的问题,而且增加了系统的负荷。为了解决这个问题,Linux内核使用了一种称为 LRU (Least Recently Used)
的算法, 下面介绍一下 LRU算法
的大体过程.LRU
的中文翻译是 最近最少使用
, 顾名思义就是一段时间内没有被使用, 那么Linux内核怎么知道哪些内存页面最近没有被使用呢? 最简单的方法就是把内存页放进一个队列里, 如果内存页被访问了, 就把内存页移动到链表的头部, 这样没被访问的内存页在一段时间后便会移动到队列的尾部, 而释放内存页时从链表的尾部开始. 著名的缓存服务器 memcached
就是使用这种 LRU算法
.Linux内核也使用了类似的算法, 但相对要复杂一些. Linux内核维护着三个队列: 活跃队列, 非活跃脏队列和非活跃干净队列. 为什么Linux需要维护三个队列, 而不是使用一个队列呢? 这是因为Linux希望内存页交换过程慢慢进行, Linux内核有个内核线程 kswapd
会定时检查系统的空闲内存页是否紧缺, 如果系统的空闲内存页紧缺时时, 就会选择一些用户进程把其占用的内存页添加到活跃链表中并断开进程与此内存页的映射关系. 随着时间的推移, 如果内存页没有被访问, 那么就会被移动到非活跃脏链表. 非活跃脏链表中的内存页是需要被交换到磁盘的, 当系统中空闲内存页紧缺时就会从非活跃脏链表的尾部开始把内存页刷新到磁盘中, 然后移动到非活跃干净链表中, 非活跃干净链表中的内存页是可以立刻分配给进程使用的. 各个链表之间的移动如下图:如果在这个过程中, 内存页又被访问了, 那么Linux内核会把内存页移动到活跃链表中, 并且建立内存映射关系, 这样就不需要从磁盘中读取内存页的内容.注意: 内核只维护着一个活跃链表和一个非活跃脏链表, 但是非活跃干净链表是每个内存管理区都有一个的.
这是因为分配内存是在内存管理区的基础上进行的, 所以一个内存页必须属于某一个内存管理区.
kswapd内核线程
在Linux系统启动时会调用kswapd_init()
函数, 代码如下: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()
函数会创建 kswapd
和 kreclaimd
两个内核线程, 这两个内核线程负责在系统物理内存紧缺时释放一些物理内存页, 从而使系统的可用内存达到一个平衡. 下面我们重点来分析 kswapd
这个内核线程, kswapd()
的源码如下:int kswapd(void *unused)
{
struct task_struct *tsk = current;
tsk->session = 1;
tsk->pgrp = 1;
strcpy(tsk->comm, "kswapd");
sigfillset(