swapfile原理使用和演进
先抄一段来自度娘的定义:
Swap分区在系统的运行内存不够用的时候,把物理内存中的一部分空间释放出来,以供当前运行的程序使用。
也就是系统提供了一种方法,当系统内存吃紧(或者在必要的时候),可以将内存内容保存到指定的文件上。这样释放出来的内存空间,就可以给到系统其他进程使用了。
本章我们就来看看这个swap工作的基本概念。
开启一个swap分区
swap并不是系统中必须的,也就是我们可以不用swap。那么此时当系统内存吃紧时,我估计会把内存扔掉吧。
那如何开启呢? 下面给出一个简单的例子:
怎么样,是不是很简单。我们只要指定一个文件,格式化成swap分区。这样就可以通过swapon系统调用将一个swap分区用起来了。
可以通过很多命令来获取swap分区的使用情况,如top, htop, cat /proc/swaps。不过信息比较简单。
初始化一个swap分区
要使用一个swap分区,那就要对这个分区初始化。这个初始化的过程有点长,但是从数据结构的角度来看也不算太复杂。让俺尝试用下面的图来表示。
对这张图有几点可以说:
每个swap分区由一个swap_info_struct结构体表示
这个结构体的指针将保存在swap_info[]数组中,下标是swap_info_struct->type。(神奇的名字)
对应的还有两个数组swapper_space[]和nr_swapper_spaces[],用来保存对应的address_space指针。可以一个swap分区可以对应多个address_space。
swap_info_struct又会链接在两个优先级队列swap_active_head和swap_avail_heads[]上。看注释,前者用于swapoff,后者用于找到合适的磁盘空间。
有了大致的数据结构概念,我们可以来看看如何把内存保存到swap分区的。
找到空闲的swap空间
要把内存保存到某个地方,要分成两步:
找到可以存放的地方
放进去
要了解怎么找到一个空闲的空间,就要再看一眼swap_info_struct这个数据结构。
出了前面那张图中展示的信息,swap_info_struct结构体中还包含了
max: 整个swap分区页面个数
pages: 整个swap分区可用的页面个数
inuse_pages: 已经用掉了多少页面
swap_map: 对每一个页面都有一个字节表示状态
接下来三个都是围绕着swap_map数组的下标:
lowest_bit/highest_bit: 当前可用范围的最大最小下标
cluster_next: 建议的下次可使用的下标
按照我现在的理解,这个过程就是当有请求时就从cluster_next下标去找swap_map中第一个空闲的块。然后把这个块分配出去。
具体的算法在scan_swap_map_slots()中,还有写不是特别清楚。待后续了解后再来补充。
写入swap分区
找到了swap分区中对应的块,接下来就要把我们想要释放的内存写入。这样才能最后释放对应的内存。
个人感觉这个动作可以在多个地方完成,先找出其中之一记录一下:
此处省去众多,只留下我认为比较关键的步骤。
(1) 从swap分区中找到一个空闲块,这里还有些优化暂时跳过
(2) 将页加入到address_space中,就是swapper_space[]数组中保存的
(3) 调用address_space对应的函数将页写入
这里因为是一个swapfile,所以对应的a_ops就是swap_aops。在函数init_swap_address_space()中初始化。
到这里基本算是了解了swap分区的基本工作流程,当然还有更多细节有待挖掘。
基本概念
到这里我们要补充几个基本概念,了解一下重要的几个常用数据结构的样子。
swap_count
在“找到空闲的swap空间”这部分,我们可以看到swap_map这个数组用来保存对应页面的状态信息。包括了:
被映射的数目
是否有cache
是不是坏的
这这一小节我们主要来看swap_count是如何存储被映射的数目的。
在 commit 570a335b8e22 swap_info: swap count continuations中,引入了采用链式的存储数目的方法。这样呢就导致了swap_map这个字节有了两种格式:
总的来说,也就是第一个swap_map的格式和后续的会不一样。差别主要是第一个字节中的SWAP_HAS_CACHE被占用来表达了一个特殊含义,而后续的swap_map可以有7个bit来表达数目信息。 而每个swap_map的最高位COUNT_CONTINUED用来表示后续是否还有更高位的信息。
这样一来 后续swap_map能表达的数目信息由低7位表达,也就是 SWAP_CONT_MAX 0x7f(0111 1111)。 但是呢,第一个swap_map能表达的数目信息却不是低6位的全部,而是 SWAP_MAP_MAX 0x3e(0011 1110)。因为SWAP_MAP_BAD 0x3f(0011 1111)用来表达一个特殊含义。
所以这些定义确实有点让人需要花一些时间去理解。我把这些特殊的定义稍微总结一下:
其实呢,了解了数据结构,就基本能理解后续代码是如何运作的了。这部分主要的函数是swap_count_continued(),这个函数相当于一个对于swap_map的加减法。而函数add_swap_count_continuation()则是在需要的时候扩展相对应的计数空间。当然里面用的招数是链接page->lru,有点高难度了。
swp_entry
这个swp_entry就是一个索引,用它就可以找到对应的swap_map和swap cache中保存的那个page。
总的来说,这个swp_entry就是将swap_info_struct->type和offset编码存放起来。大致格式如下图所示
具体将type和offset编码,以及从swp_entry解码的函数为:
编码: (type, offset) -> swp_entry
entry = swp_entry(type, offset)
解码: swp_entry -> (type, offset)
type = swp_type(entry) offset = swp_offset(entry)
而这个swp_entry在把page释放到设备的时候就会写到pte中。所以这是一个非常重要的结构。
此外对其中的type部分要进一步展开,这个type占用5个bit,但是真正能用来表示swap设备的没有这么多。这个值如下定义:
也就是这些位中会拿出一部分SWP_DEVICE_NUM, SWP_MIGRATION_NUM, SWP_HWPOISON_NUM来表示某些对页做的特殊的操作。比如对于迁移的页面,就会用SWP_MIGRATION_READ|WRITE来表示。
演进
基本概念差不多是上面描述的样子了,接下来我们看看更加具体的细节。而这些细节大多是一步步演进过来的。
swap_cluster_info for SSD
有好些个优化是基于ssd的,主要的思想是将swap切分成一个个小的cluster,每个cluster是256个页。当然之前也有这个cluster概念,但是并没有用数据结构将它独立出来。
相关的commit有:
前两个是Shaohua的主要贡献,第三个是HuangYing将某些数据结构做了清理,让人更加容易读懂。所以我们现在看到的代码是在HuangYing修改后的版本。
其中重要的数据结构是:
cluster_info
free_clusters
discard_clusters
用一张图来展示这三者和swap_map之间的关系会比较好的看清这个的用途:
我想看到这里基本上能明白了,也就是
对swap_map中,每256个page,创建一个swap_cluster_info。形成一个数组叫cluster_info。
然后有两个链表,free_clusters和discard_clusters。一个用来分配,一个用来discard
还有两点需要说明:
只有整个cluster都为空,才会加入到free_clusters链表
初始化时的free_cluster链表不是顺序的,为了防止false cache line sharing
关于这个false cache line sharing,可以参考以下两个commit
分割swap cache
这个方法的理念和cluster类似,也是切分。之前切分的是swap_map,这次切分的是address_space。也就是swapper_space[]。
这个工作就是由下面这个提交完成的。
4b3ef9daa4fc 2017-02-22 mm/swap: split swap cache into 64MB trunks
这么一说出来,好像也不怎么神秘了。就是那么一回事儿。
一次多分几个
这个优化的理念和之前的,虽然做法上略有不同,但是根本还是一样的。-- 减少频繁得拿锁。
先来看以下这个改进的提交:
36005bae205d 2017-02-22 mm/swap: allocate swap slots in batches
其实呢这个改进还要个readahead结合起来说。当有预取的时候,我们会一次性从swap中拿出SWAP_BATCH个entry。改进之前,每次拿一个entry,都会拿一次锁再释放。这样对锁的竞争就比较大。而这次的改进则是对SWAP_BATCH个entry只拿一次锁,这样就减少了竞争。
swappiness控制交换
为了减少匿名页回收对系统性能造成的影响,系统有两个参数调整回收时究竟多少比例回收匿名页。
/proc/sys/vm/swappiness
/sys/fs/cgroup/memory/memory.swappiness
而这两个参数在代码中通过mem_cgroup_swappiness()来获取,其功效就是用来计算回收比例。具体的作用我们将在回收部分统一来看。
Last updated