# swapfile原理使用和演进

先抄一段来自度娘的定义：

> Swap分区在系统的运行内存不够用的时候，把物理内存中的一部分空间释放出来，以供当前运行的程序使用。

也就是系统提供了一种方法，当系统内存吃紧（或者在必要的时候），可以将内存内容保存到指定的文件上。这样释放出来的内存空间，就可以给到系统其他进程使用了。

本章我们就来看看这个swap工作的基本概念。

## 开启一个swap分区

swap并不是系统中必须的，也就是我们可以不用swap。那么此时当系统内存吃紧时，我估计会把内存扔掉吧。

那如何开启呢？ 下面给出一个简单的例子：

```
# create a file with 256M
sudo dd if=/dev/zero of=myswap bs=32k count=8192
# format myswap to swap file
sudo mkswap myswap
# enable it
sudo swapon myswap
```

怎么样，是不是很简单。我们只要指定一个文件，格式化成swap分区。这样就可以通过swapon系统调用将一个swap分区用起来了。

可以通过很多命令来获取swap分区的使用情况，如top, htop, cat /proc/swaps。不过信息比较简单。

## 初始化一个swap分区

要使用一个swap分区，那就要对这个分区初始化。这个初始化的过程有点长，但是从数据结构的角度来看也不算太复杂。让俺尝试用下面的图来表示。

```
                                         0     1              nr_swapfiles       MAX_SWAPFILES -1
                                         |     |                 |                 |
                                         v     v                 v                 v
        struct swap_info_struct       +-----+-----+-----+-----+-----+-----+-----+-----+
         *swap_info[MAX_SWAPFILES]    |     |     |     |     |     |     |     |     |
                                      |     |     |     |     |     |     |     |     |
                                      +-----+-----+-----+-----+-----+-----+-----+-----+
        struct address_space          +-----+-----+-----+-----+-----+-----+-----+-----+
      *swapper_space[MAX_SWAPFILES]   |     |     |     |     |     |     |     |     |
                                      |     |     |     |     |     |     |     |     |
                                      +-----+-----+-----+-----+-----+-----+-----+-----+
        unsigned int                  +-----+-----+-----+-----+-----+-----+-----+-----+
      nr_swapper_spaces[MAX_SWAPFILES]|     |     |     |     |     |     |     |     |
                                      |     |     |     |     |     |     |     |     |
                                      +-----+-----+-----+-----+-----+-----+-----+-----+
                                                     ^
                                                     |
                                                     |
                                                     +----------------------+
                                                                            |
                                                                            |
                                                                            |
                                      struct swap_info_struct               |
                                      +--------------------------------+    |
                                      |type                         ---|----+
                                      |                                | index in swap_info,
                                      |                                | swapper_spaces, nr_swapper_spaces
                                      |                                |
                                      |flags                           | SWP_USED
                                      |                                |
                                      |prio                            |
                                      |    (signed short)              |
     swap_active_head      <--->      |list                            |
     swap_avail_heads[]    <--->      |avail_lists[0]                  |
                                      |    (struct plist_node)         |
                                      +--------------------------------+
```

对这张图有几点可以说：

* 每个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这个数据结构。

```
           struct swap_info_struct
           +--------------------------------+
           |max                             | = maxpages
           |pages                           | = nr_good_pages real available pages
           |inuse_pages                     |
           |                                |
           |highest_bit                     |    ----------------------+
           |lowest_bit                      |    ----+                 |
           |    (unsigned int)              |        |                 |
           |                                |  0     v                 v  maxpages
           |swap_map                        | [  |  |  |  |  |  |  |  |  |  ]
           |    (char *)                    |               ^
           |                                |               |
           |cluster_next                    |    -----------+     indicate next allocation
           |    (unsigned int)              |
           |                                |
           +--------------------------------+
```

出了前面那张图中展示的信息，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分区中对应的块，接下来就要把我们想要释放的内存写入。这样才能最后释放对应的内存。

个人感觉这个动作可以在多个地方完成，先找出其中之一记录一下：

```
  shrink_page_list()
    ...
    add_to_swap()
      entry = get_swap_page()                        (1)
        ...
        get_swap_pages()
      add_to_swap_cache(page, entry, GFP_xxx)        (2)
    mapping = page_mapping(page)
    pageout(page, mapping)
      mapping->a_ops->writepage(page, wbc)           (3)
```

此处省去众多，只留下我认为比较关键的步骤。

* (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这个字节有了两种格式：

```
    First swap_map:

        COUNT_CONTINUED
         |    SWAP_HAS_CACHE
         |     |
         v     v
       +----+----+----+----+----+----+----+----+
       |    |    |    |    |    |    |    |    |
       +----+----+----+----+----+----+----+----+

    Continuations:

        COUNT_CONTINUED
         |
         |
         v
       +----+----+----+----+----+----+----+----+
       |    |    |    |    |    |    |    |    |
       +----+----+----+----+----+----+----+----+
```

总的来说，也就是第一个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)用来表达一个特殊含义。

所以这些定义确实有点让人需要花一些时间去理解。我把这些特殊的定义稍微总结一下：

```
Bit indication in first swap_map:

   SWAP_HAS_CACHE  0x40(0100 0000)

Bit indication in all swap_map(?):

   COUNT_CONTINUED 0x80(1000 0000)

Special value in first swap_map:

   SWAP_MAP_MAX    0x3e(0011 1110)
   SWAP_MAP_BAD    0x3f(0011 1111)
   SWAP_MAP_SHMEM  0xbf(1011 1111)

Special value in swap_map continuation:

   SWAP_CONT_MAX   0x7f(0111 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(5bits) |       index/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设备的没有这么多。这个值如下定义：

```
#define MAX_SWAPFILES \
	((1 << MAX_SWAPFILES_SHIFT) - SWP_DEVICE_NUM - 	SWP_MIGRATION_NUM - SWP_HWPOISON_NUM)
```

也就是这些位中会拿出一部分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有：

```
815c2c543d3a 2013-09-11 swap: make swap discard async
2a8f94493432 2013-09-11 swap: change block allocation algorithm for SSD

6b53491598a4 2016-10-02 mm, swap: add swap_cluster_list
```

前两个是Shaohua的主要贡献，第三个是HuangYing将某些数据结构做了清理，让人更加容易读懂。所以我们现在看到的代码是在HuangYing修改后的版本。

其中重要的数据结构是：

* cluster\_info
* free\_clusters
* discard\_clusters

用一张图来展示这三者和swap\_map之间的关系会比较好的看清这个的用途：

```
    struct swap_info_struct
    +--------------------------------+
    |                                |    0                                                 maxpages
    |swap_map                        |   [  |  |  |  |  |   |  |  |  |  |  |  |  |  |  |  |  ]
    |    (char *)                    |                
    |                                |
    |cluster_info                    | [maxpages/SWAPFILE_CLUSTER]
    |    (swap_cluster_info*)        |
    |    +---------------------------+
    |    |lock                       |
    |    |    (spinlock_t)           |   +---------+   +---------+   +---------+   +---------+
    |    |data:24                    |   |1        |   |3        |   |count    |   |         |
    |    |flags:8                    |   |FREE     |   |FREE     |   |         |   |FREE     |
    |    |    (unsigned int)         |   +---------+   +---------+   +---------+   +---------+
    |    |                           |    ^      |       ^     |                     ^
    |    +---------------------------+    |      |       |     |                     |
    |                                |    |      +-------+     +---------------------+
    |free_clusters                   |    |                                          |
    |discard_clusters                |    |                                          |
    |    (swap_cluster_list)         |    |                                          |
    |    +---------------------------+    |                                          |
    |    |head                     --|----+                                          |
    |    |tail                       |                                               |
    |    |   (swap_cluster_info)   --|-----------------------------------------------+
    |    |                           |
    |    +---------------------------+
    |                                |
    +--------------------------------+
```

我想看到这里基本上能明白了，也就是

* 对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

```
4b3ef9daa4fc 2017-02-22 mm/swap: split swap cache into 64MB trunks
235b62176712 2017-02-22 mm/swap: add cluster lock
```

### 分割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()来获取，其功效就是用来计算回收比例。具体的作用我们将在回收部分统一来看。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://richardweiyang-2.gitbook.io/kernel-exploring/nei-cun-guan-li/00-index-1/01-swapfile.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
