本文转载自 Linux内核那些事
链接:https://mp.weixin.qq.com/s/LtyvaDXiHlgr0eu2cJCN7A
当Linux内存不足时就会触发swap(交换)机制, swap机制是什么东西呢?
swap机制其实就是将外存(如硬盘)当内存使用, 怎么可以把外存当内存使用呢? 原理就是当系统内存不够用的时候, 内核会选择某些进程, 把其使用较少的内存的内容交换(swap)到外存中,然后把内存让给需要的进程使用.
那么Linux内核会把哪些内存交换到外存去呢? 在新版的Linux内核会使用LRU算法计算出那些使用比较少的内存交换到外存中. 而在这篇文件使用的Linux 1.0版本的内核, 所以算法比较简单. 下面来说说在Linux 1.0的swap机制是怎么实现的吧.
当进程需要使用内存时会通过调用内核函数__get_free_page()来获取一个内存页, 我们来看看__get_free_page()的源码:
unsigned long__get_free_page(int priority)
{
...
repeat:
REMOVE_FROM_MEM_QUEUE(free_page_list,nr_free_pages);
if (priority == GFP_BUFFER)
return 0;
if (priority != GFP_ATOMIC)
if (try_to_free_page())
goto repeat;
REMOVE_FROM_MEM_QUEUE(secondary_page_list,nr_secondary_pages);
return 0;
}
REMOVE_FROM_MEM_QUEUE()宏是从空闲内存页列表中获取一块内存页, 如果成功会直接返回. 如果失败就会调用try_to_free_page()函数来交换一块内存页到外存中. try_to_free_page()函数代码如下:
static inttry_to_free_page(void)
{
int i=6;
while (i--) {
if (shrink_buffers(i))
return 1;
if (shm_swap(i))
return 1;
if (swap_out(i))
return 1;
}
return 0;
}
在try_to_free_page()函数中会调用swap_out()函数进行交换工作. swap_out()函数的代码有点长, 具体步骤就是:
从进程列表中选择一个进程进行交换.
从进程的内核空间选择一块内存页进行交换.
找到一块内存页后, 内核会调用try_to_swap_out()函数把此内存页交换到外存中. try_to_swap_out()代码如下:
static inline int
try_to_swap_out(unsigned long* table_ptr)
{
...
page = *table_ptr;
...
if (PAGE_DIRTY & page) {
page &= PAGE_MASK;
if (mem_map[MAP_NR(page)] != 1)
return 0;
if (!(entry = get_swap_page()))
return 0;
*table_ptr = entry;
invalidate();
write_swap_page(entry, (char *) page);
free_page(page);
return 1;
}
page &= PAGE_MASK;
*table_ptr = 0;
invalidate();
free_page(page);
return 1 + mem_map[MAP_NR(page)];
}
代码中的page是要进行交换的内存页, 如果page是被修改过(PAGE_DIRTY & page等于1时), 那么就需要交换到交换文件中(外存).
换出过程首先调用get_swap_page()函数从交换文件中获取一块交换块. 然后把进程的内存映射到此交换块. 接着调用write_swap_page()把内存页中的数据写到交换块中, 然后调用free_page()释放此内存页, 这样就可以得到一块空闲内存页了.
当然, 如果内存页没有被修改过, 那么就不用交换到交换文件中, 直接释放此内存页即可.
上面分析了当内存不足时, 内核是怎么进行swap的. 那么当进程访问到一块被交换到交换文件的内存页时会发生什么事情呢?
因为被交换到交换文件的内存页会被打上不在物理内存的标志, 所以当进程访问到不在物理内存的内存页时会发生内存页错误中断. Linux内核会调用do_no_page()函数进行修复.
do_no_page()代码如下:
void do_no_page(unsigned longerror_code, unsigned long address,
struct task_struct *tsk, unsigned long user_esp)
{
...
page = get_empty_pgtable(tsk,address);
if (!page)
return;
page &= PAGE_MASK;
page += PAGE_PTR(address);
tmp = *(unsigned long *) page;
if (tmp & PAGE_PRESENT)
return;
++tsk->rss;
if (tmp) {
++tsk->maj_flt;
swap_in((unsigned long *) page);
return;
}
...
}
从上面的代码可以看出, 当访问的内存地址不在物理内存中, 当有具体的交换信息时, 就会调用swap_in()函数把内存页从交换文件中换入到物理内存页中. swap_in()函数最终会调用read_swap_page()函数把交换文件中的内存页读入到物理内存中.