Swap Management(交换管理)
- Linux所维护的每个活动交换区的结构以及如何在磁盘上组织交换区信息
- Linux如何在页面换出后的交换区定位该页,以及如何分配交换槽
- 交换高速缓存(swap cache)
- 如何激活和禁止交换区,内存的页面如何换出道交换区又如何换入到内存,以及如何读写交换区
1、描述交换区
每一个活跃的交换区,无论是一个文件或是一个分区,都由swap_info_struct
结构描述。系统中该结构存储在一个静态声明的swap_info
数组中,它有MAX_AWAPFILES
(一般被定义为32个)个元素项。意味着系统最多运行MAX_SWAPFILES
个交换区。
// /include/linux/swap.h
/*
* The in-memory structure used to track swap areas.
*/
struct swap_info_struct {
unsigned int flags; //SWP_USED表示此swap区域处于激活状态。SWAP_WRITEOK表示Linux准备对此交换区进行写操作,写之前必须激活交换区
kdev_t swap_device; //该交换区所占用的磁盘设备的信息
spinlock_t sdev_lock; //保护此结构的自旋锁,主要保护swap_map。swap_device_lock() and swap_device_unlock()
struct dentry * swap_file; //this is the dentry for the actual special file that is mounted as a swap area.实际的特殊文件作为交换区被挂在到系统的dentry。
struct vfsmount *swap_vfsmnt;
unsigned short * swap_map; //非常大的一个数组,一个项对应一个交换项。一个项是该页面槽用户数量的引用计数。一个PTE被换出到槽时算一个用户。如果计数为SWAP_MAP_MAX,将永久的分配该槽。若为SWAP_MAP_BAD,将不在使用该槽。
unsigned int lowest_bit; //交换区中最低的可用空闲槽
unsigned int highest_bit; //最高的可用空闲槽
unsigned int cluster_next; //下一个被使用的簇块偏移量。
unsigned int cluster_nr; //簇中剩余的供分配的页面数量
int prio; /* swap priority */
int pages; //记录交换区中可用的页面数量
unsigned long max; //交换区中所有的页面数量
int next; //swap_info数组中用于指向系统中下一个交互去的下标 /* next entry on swap list */
};
交换区同时存放在一个成为swap_list
的伪链表中。声明如下:
struct swap_list_t {
int head; /* head of priority-ordered swapfile list */ //指向最高优先级的交换区
int next; /* swapfile to be used next */ //下一个将被使用的交换区
};
每个交换区有很多页面大小的槽,在x86上,每个页面大小为4KB。第一个槽存放有交换区的基本信息,被保留且不可写。该交换区的第一个1KB用于存放分区的磁盘标签,这些磁盘标签为用户空间的工具提供信息。剩下的空间用于存放交换区的其他信息,当系统程序mkswap
创建交换区时,这些剩下的空间被填充。交换区的信息由一个 union swap_header
表示。如下:
// /include/linux/swap.h
/*
* Magic header for a swap area. The first part of the union is
* what the swap magic looks like for the old (limited to 128MB)
* swap area format, the second part of the union adds - in the
* old reserved area - some extra information. Note that the first
* kilobyte is reserved for boot loader or disk label stuff...
*
* Having the magic at the end of the PAGE_SIZE makes detecting swap
* areas somewhat tricky on machines that support multiple page sizes.
* For 2.5 we'll probably want to move the magic to just beyond the
* bootbits...
*/
union swap_header {
struct
{
char reserved[PAGE_SIZE - 10];
char magic[10]; /* SWAP-SPACE or SWAPSPACE2 */
} magic;
struct
{
char bootbits[1024]; /* Space for disklabel etc. */ //保留区,用于存放分区信息,如磁盘标签
unsigned int version; //交换区的版本号
unsigned int last_page; //交换区中最后一个可用页面
unsigned int nr_badpages; //交换区中一直的坏页面数
unsigned int padding[125]; //padding的值位500(125*4=500B),填充为512B(version + last_page + nr_fadpages + padding)
unsigned int badpages[1]; //页面剩下的部分用于存放至多MAX_SWAP_BADPAGES个坏页槽。系统程序mkswap打开-c开关检查交换区时,填充这些槽
} info;
};
MAX_SWAP_BADPAGES
是一个编译时常数,随着结构的改变而变化,但是根据当前的结构,由下面的简单公式可以知道它为637
。
其中1024是bootbits
的大小,512是padding
的大小,10是magic字符串
的大小,其中magic字符串用于鉴别交换文件的格式。
2、映射页表项到交换项
一个页面换出时,Linux使用相应的页表项PTE存放用于再次在磁盘上定位该页的信息。PTE不能精确存放交换页面的位置信息,但能存放交换槽所在swap_info数组的下标
以及swap_map中的偏移量
。有这些信息足够了,这也是Linux处理方式。
每一个PTE都必须大到能够存放一个swp_entry_t
变量。该变量定义如下:
// /include/linux/shmem_fs.h
/*
* A swap entry has to fit into a "unsigned long", as
* the entry is hidden in the "index" field of the
* swapper address space.
*
* We have to move it here, since not every user of fs.h is including
* mm.h, but mm.h is including fs.h via sched .h :-/
*/
typedef struct {
unsigned long val;
} swp_entry_t;
pte_to_swp_entry()
and swp_entry_to_pte()
用来做pte和swp_entry之间的转换。
_PAGE_PRESENT:页面常驻内存,不进行换出操作
_PAGE_PROTNONE:页面常驻内存,但不可访问
在x86架构下,位0为_PAGE_PRESENT位
,位7为_PAGE_PROTNONE位
。位1~位6标识swap_info类型
,即数组的下标,并且由SWP_TYPE()
宏返回得到。位8~位31代表交换区中的偏移
,即swap_map
中的偏移量。在x86中,24位是有效的,offset
限制了交换区的大小为64GB。offset由SWP_OFFSET()
宏得到。
4KB * 2^24(16M) = 64GB
SWP_ENTRY()
宏将类型和其相应的偏移编号放到swap_entry_t
中。各个函数及宏的关系如下图所示:
type
的6位表明应该允许64个交换区,而不是MAX_SWAPFILES(32)
个交换区。MAX_SWAPFILES
的限制是由于用于管理交换区的结构体有消耗vmalloc地址空间。若交换区达到最大,则swap_map
的空间需要很大。就算是只有MAX_SWAPFILES
个交换区存在,就需要很大的虚拟地址空间。由于用户/内核线性地址分割,这根本不可能。所以不值得为支持64个交换区而增加系统复杂度。某些现代机器将磁盘分为许多独立的块,在各个块之间均匀分布小的交换区,这样可以提高页面交换的并行度,对于交换密集的应用很重要。
若交换区达到最大,swap_map需要32MB(2^24 * sizeof(short))空间。MAX_SWAPFILES个最大尺寸的交换区存在时,就需要1GB的虚拟malloc空间。
3、分配一个交换槽(Allocating a Swap Slot)
一个页面大小的槽由swap_info_struct->swap_map
跟踪,数据元素为unsigned short
。在共享页面的情况下,数组中的每项都是一个槽使用者的引用计数,在空闲时为0。若数值为SWAP_MAP_MAX
,则对应的页将永久保留这个槽。若数值为SWAP_MAP_BAD
则对应的页将不能再使用。
寻找和分配一个交换项的任务分为两个步骤
。首先调用高层函数get_swap_page()
。从swap_list->next
处开始寻找合适的交换区,该交换区要能够分配合适槽的槽。一个槽寻找到之后,swap_list->next记录下一个将被使用的交换区并且返回要分配的项。scan_swap_map()
负责查找map
。它对map数组线性查找空闲的槽并返回。
Linux将磁盘中SWAPFILE_CLUSTER
个页组成成一个簇。Linux在交换区顺序分配SWAPFILE_CLUSTER
个页面,在swap_info_struct->cluster_nr
中记录簇中一分配的页面数,在swap_info_struct->cluster_next
中记录当前的偏移量。一块簇分配完之后, Linux寻找一块空闲的表项,该块的大小为SWAPFILE_CLUSTER
。若找到一个足够大的块,系统将会把它作为另一个cluster-sized sequence。
记录在当前簇中的偏移量?
若在交换区中没有总够对的空闲簇,系统从swap_info_struct->lowest_bit
记录的位置处进行first-free search
。
4、交换区高速缓存(Swap Cache)
Linux无法快速完成struct page
到PTE(每一个引用该struct page的PTE)
的映射,所以由多个进程共享的页面不能简单的换出。This leads to the rare condition where a page that is present for one PTE and swapped out for another gets updated without being synced to disk, thereby losing the update.
为了解决这一问题,共享页在后援存储器(backing storage)
中保留一个槽作为swap cache
的一部分。
backing storage: https://www.computerhope.com/jargon/b/backing-storage.htm
与swap cache有关的API
:
交换高速缓存是一个纯概念性的东西,它只是页面高速缓存的特殊形式。交换高速缓存与页面高速缓存的一个区别是交换高速缓存使用swapper_space
作为page->mapping
的地址空间。另一个区别是交换高速缓存中的页面通过add_to_swap_cache()
加入到交换高速缓存中,不是使用add_to_page_cache()。
匿名页只有在交换出去时才是交换高速缓存的一部分。系统首次写共享内存的页才把它们加入到交换高速缓存中。变量swapper_space
的声明如下:
// /mm/swap_state.c
struct address_space swapper_space = {
LIST_HEAD_INIT(swapper_space.clean_pages),
LIST_HEAD_INIT(swapper_space.dirty_pages),
LIST_HEAD_INIT(swapper_space.locked_pages),
0, /* nrpages */
&swap_aops,
};
后援存储地址空间的swapper_space使用swap_ops作为它的address_space->a_ops。
static struct address_space_operations swap_aops = {
writepage: swap_writepage,
sync_page: block_sync_page,
};
页面加入到交换高速缓存中后,使用系统调用get_swap_page()
来分配一个可用的交换页目录项,然后使用add_to_swap_cache()
将它加入到页高速缓存中,然后把它的标志为脏数据。
5、Reading Pages From Backing Storage(从后援存储器读取页面)
如果另一个进程映射了相同的页面,或者多个进程同时在同一页面上发生故障,则该页面可能已经存在于交换缓存中。
6、Writing Pages to Backing Storage(向后援存储器写页面)
7、Reading/Writing Swap Area Blocks(读/写交换区域的块)
读写交换去的高层函数是rw_swap_page()
。此函数确保所有操作在交换高速缓存中完成以避免丢失更新。rw_swap_page_base()
是完成实际工作的核心函数。
8、Activating a Swap Area(激活一个交换区)
sys_swapon()
9、Activating a Swap Area(禁止一个交换区)
sys_swapoff()
参考文献:
[1] 白洛. 深入理解Linux虚拟内存管理. 2006-1
[2] Mel Gorman. Understanding the Linux Virtual Memory Manager. 2004-5-9