Linux虚拟内存管理 - Swap Management(交换管理)

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 pagePTE(每一个引用该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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值