Linux内存管理第十一章 -- Swap Management

Linux内存管理第十一章 – Swap Management

Linux会使用空闲的内存来缓存磁盘上的数据,然后需要再释放进程私有的或者正在使用的匿名page。这些page不像有在磁盘上对应的文件的的page那样简丹地丢弃然后再来重新读。相反必须小心滴将它们复制到磁盘介质上,有时候我们称这些page为swap area。本章主要讲述Linux如何使用和管理它的后备介质。
swap space存在的原因有两个:

  • first,它拓展了一个进程可以使用的内存。虚拟内存和swap space允许一个巨大的进程运行即便这个进程只有一部分在内存中。因为旧的page将被换出,大量的内存寻址将很容易地超过RAM因为demand paging需要保证页面重新加载。
  • second,许多进程在早期使用了很多page仅仅是做一些初始化而这些page之后再也不会被进程用到。因此最好是能换出这些page来创建更多的磁盘buff,总比放在内存中而不是用要强很多。

需要重点注意的是swap不是没有缺点而且最重要的缺点也是最明显的缺点。磁盘太慢了。如果进程需要高频访问大量内存,没有swap或者昂贵的磁盘操作将会使他的运行时间合理,而这种情况只有更多的RAM才有帮助。这就说明了换出正确的page是多么重要,这些被换出的page也和存储在swap space的page相关,这些page有可能被同时换出。我们即将要描述Linux如何来描述一个swap area。

本章开始于描述Linux中每个active swap area的结构以及swap area的信息如何在磁盘上被识别。然后再来讲述Linux如何记住和找到swap的page,以及swap slots如何分配。再额按后来讨论下对于共享page很重要的swap cache。此时将会有大量的信息来弄懂swap area如何被active和deactive,page如何page in以及page out,最后就是swap area的读写问题。

Describing the Swap Area

每一个active swap area都用struct swap_info_struct来描述这个area,它是一个文件或者一个文件的部分。系统中所有的struct swap_info_struct都在数组swap_info中静态定义。swap_info的大小为MAX_SWAPFILES,其通常被静态定义为32。这就意味着系统中最多有32个swap area。struct swap_info_struct的结构如下:

struct swap_info_struct {
	unsigned int flags;
	spinlock_t sdev_lock;
	struct file *swap_file;
	struct block_device *bdev;
	struct list_head extent_list;
	int nr_extents;
	struct swap_extent *curr_swap_extent;
	unsigned old_block_size;
	unsigned short * swap_map;
	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;
	unsigned long inuse_pages;
	int next;			/* next entry on swap list */
};

下面来看下其中的重要字段:

  • flags:这是一个拥有两种取值的bit字段。SWP_USED:如果该swap area当前是激活状态,SWP_USED被置位。SWP_WRITEOK 被定义为3,其包含SWP_USED。当Linux准备往该swap area 写入的时候,flags被设置为SWP_WRITEOK,此时该swap area必须是active的然后才能写入。Linux kernel2.6貌似已经改变了,新增了SWP_ACTIVE:
enum {
	SWP_USED	= (1 << 0),	/* is slot in swap_info[] used? */
	SWP_WRITEOK	= (1 << 1),	/* ok to write to this swap?	*/
	SWP_ACTIVE	= (SWP_USED | SWP_WRITEOK),
};
  • bdev:当前swap area存储内容所对应的device。如果swap area是一个file,则该字段为NULL。
  • sdev_lock:sdev_lock是用于保护swap_map.swap_device_lock() and swap_device_unlock()分别来加锁和解锁。
  • swap_file:被mount到当前swap_area中的file所对应的dentry,Linux2.6中时struct file
  • swap_map:这是大数组,一个元素对应于一个swap entry,该数组中的每一个元素代表此page slot的一个使用者。swap cache作为一个使用者。而在page slot中的每个PTE也当做是一个使用者。如果它的使用者达到SWAP_MAP_MAX,该slot将永久分配。如果使用者个数等于SWAP_MAP_BAD,该slot将永远不会被使用。(不是很理解)
  • lowest_bit:用于表示最低可能性能够被使用的空闲slot。其作用是用来减少搜索空间,当低于这个mark,就完全没有空闲slots可用了。
  • highest_bit:最高可能性被使用的free slot,高于这个mark也无空闲slots可以使用。
  • cluster_next:block的next cluster的偏移。swap area尽量使用cluster block中被分配的pages这样可以增加相关联的page被存储在一起的概率。
  • cluster_nr:cluster中需要被分配的page个数
  • prio:每个swap area的优先级存放在该字段。swap area按优先级顺序存放,优先级决定该swap area如何被使用。默认的优先级是按照激活的先后循序,但是系统管理员可以通过swapon -p 命令指定优先级。
  • pages:因为swap file中有些slot不可使用,因此该字段用于存放swap area中可用的page 个数。与max字段不同之处在于被标记为SWAP_MAP_BAD的slots不参与计数
  • max:swap area中所有slots的总数
  • next:swap_info数组中下一个swap area的index
    swap area虽然已经被存储在swap_info这个全局数组中,还存储在swap_list中。
struct swap_list_t {
	int head;	/* head of priority-ordered swapfile list */
	int next;	/* swapfile to be used next */
};

swap_list_t->head字段是可使用的最高优先级的swap area。swap_list_t->next表示下一个被使用的swap area。这表明当进行搜索查找合适的area的时候,area仍有可能按优先级存放,但在数组中查找依然很快。

每一个swap area被切分成多个page size大小的slots存储在磁盘上,这就意味着在x86上每一个slot的大小是4K。第一个slot始终被保留起来因为它存储了swap area的信息其不能被覆盖。swap area的前1KB被用作存放磁盘label,以便这部分可以被用户空间的工具挑选出来。剩下的3KB用于存放swap area的信息,该信息是在swap area被创建的时候填入的。这些信息使用union swap_header填入:

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];
		unsigned int badpages[1];
	} info;
};
  • magic:magic结构的部分只是用来辨认里面的magic 字符串。该字符串存在的原因是保证不是swap area的一部分而被使用并决定了swap area的版本。如果该字符串为“SWAP_SPACE”,那么swap file的格式为version1.如果为“SWAPSPACE2”,则为版本2.magic结构中保留的一个大数组使用于将该magic string从page的尾部读出。
  • bootbits:保留为用于存放像disk label这样的信息
  • version:swap area布局的版本
  • last_page:上次可用的page
  • padding:磁盘的一个段一般为512字节,而version,last_page,nr_badpages总共12字节,padding是用来填充剩余的500字节。
  • nr_badpages:swap area中已知的bad pages的个数
  • badpages:The remainder of the page is used to store the indices of up to MAX_SWAP_BADPAGES number of bad page slots. These slots are filled in by the mkswap system program if the -c switch is specified to check the area.

Mapping Page Table Entries to Swap Entries

当一个page被换出,Linux使用相应的PTE来存储足够的信息用于将该page从磁盘中捞回来。很明显PTE不能完全存放用于查找该page在磁盘中的位置,但是PTE足够存下swap_info的index和swap_map内的偏移,而linux就是这么做的。
不管是什么架构,每个PTE都有足够的空间存储一个swp_entry_t:

typedef struct {
	unsigned long val;
} swp_entry_t;

内核有提供两个宏用于PTE和swap entry的互换:pte_to_swp_entry()和swp_entry_to_pte().
每种架构都能够如何分辨一个PTE是否present或者是swap out.在x86上,swp_entry_t中有两个bit始终是空闲的,因为bit 0是保留给_PAGE_PRESENT而bit 7保留给_PAGE_PROTNONE,bit 1 - 6位type,其含义是swap_info数组的index,通过SWP_TYPE()解析出type。
bit 8 - 31是用来存放在swap_map中的偏移,在x86上有24 bit可用,所以swap area可用空间是64GB。宏SWP_OFFSET()被用来解析offset。
它们之间的关系如下图:
ttt
需要说明的是type 中的bit位有6位,所以按这个逻辑推算在32位系统中应该最多有64个swap area而不是32:MAX_SWAPFILES。这个限制存在的原因是vmalloc地址空间的消耗。如果一个swap area达到最大size,那么一个swap_map就要占用32MB (2^32 * sizeof(short)),每个page使用一个short类型来记录引用计数。因此如果是32个swap area全部用满,那么需要1GB的虚拟内存,因为kernel space 和userspace的划分,这是不可能的。

Allocating a swap slot

所有page sized的slot都可以通过swap_info_struct->swap_map来追踪。swap_map中每个元素的值代表了该slot的引用计数,当一个shared page就有多个使用者,如果一个page的使用者为0,那么该solt就可以释放。如果某个元素的值为SWAP_MAP_MAX,那么该page就永驻在slot中。如果某个元素的值为SWAP_MAP_BAD,那么该slot将不可用。
查找和分配一个swap entry是两项重要的任务。首先调养get_swap_page(),该函数通过swap_list-<next 来查找一个可用的slot,如果一旦找到一个slot,它将记录下一个可用的slot然后再返回被分配的entry。
scan_swap_map()主要是用来查找slot的。理论上,它就是简丹地线性扫描一个数组查找空闲的slot然后返回。可以预料的是,实际实现的时候可能会更全面,更周全。
Linux倾向于将pages组织成一个个磁盘上的cluster,大小为SWAPFILE_CLUSTER.如果swap area中顺序的分配SWAPFILE_CLUSTER个page然后使用swap_info_struct->cluster_nr来记录已经分配的page的个数和用swap_info_struct->cluster_next来记录offset。一旦一个序列块分配完成,它将开始搜索空闲的SWAPFILE_CLUSTER个entry。如果一个block足够大,那么他将会被当做另外一个cluster size的序列。
如果swap area中没有足够大的空闲cluster,那么一个简单的first-free搜索将从swap_info_struct->lowest_bit开始执行。其目的是为了将同时换出的page靠在一起因为有一个假设:同时被swap out的page都是相互关联的。这个假设初看上去会觉得很奇怪,但实际上非常坚固。当考虑到当页替换算法线性扫描进程地址空间查找换出的page的时候,会经常用到swap space。有了这个假设之后,当进程退出时可以释放大块的slots。

Swap Cache

被多个进程共享的page不容易被换出是因为没有很快的办法将一个struct page映射到每一个引用它的PTE中。这会导致race condition:当一个page在一个PTE中present并且在另外一个PTE中被swapped out,该page的更新将不会同步到磁盘以此就会丢失更新。
为了能解决这个问题,在磁盘中保留有slot的共享page被当做是swap cache的一部分。swap cache可以当做是一种简单的特殊的page cache。但swap cache中的page于page cache中的page第一个不同点在于swap cache中的page只能将swapper_space当做page->mapping,第二个不同点在于使用add_to_swap_cache()往swap cache中添加page而使用add_to_page_cache()往address space中添加page。

匿名page最开始的时候不属于swap cache直到要将匿名page换出。变量swapper_space的定义如下:

struct address_space swapper_space = {
	.page_tree	= RADIX_TREE_INIT(GFP_ATOMIC),
	.tree_lock	= SPIN_LOCK_UNLOCKED,
	.a_ops		= &swap_aops,
	.i_mmap_nonlinear = LIST_HEAD_INIT(swapper_space.i_mmap_nonlinear),
	.backing_dev_info = &swap_backing_dev_info,
};

一旦page->mapping字段被指向全局变量swapper_space,那么该page就可以认定为swap cache的一部分。可以用PageSwapCache()来测试一个page是否属于swap cache。Linix几乎采用相同的code来保持page在swap和memory同步如同file-backed pages和memory的同步一样共享page code的代码,不同点仅仅是使用的函数不同。

swapper_space使用全局变量swap_ops当做address_space->aops.而此时page->index字段就是用来存放swp_entry_t的而不再是一个文件的偏移了。
当一个page被添加到swap cache的过程中,通过get_swap_page()来分配一个slot,然后再调用add_to_swap_cache()将page添加到page cache并标示为dirty。当该page下一次被扫描到,它将被写会到磁盘上。
SW
随后从共享PTE交换出来的page会调用swap_duplicate(),该函数会简单的增加swap_map中的slot的引用计数。如果该PTE被因为有写入被硬件标记为dirty,该PTE中的dirty标记会被清楚然后通过调用set_page_dirty()来讲struct page标记为dirty,因此该PTE在磁盘中对应的备份将会在该page丢弃之前被同步。这是为了保证有一个检查动作能够保证将page frame中的数据与磁盘中的数据保持一致直到所有对该page的引用都被丢弃。
当一个page的引用计数到达0,那么该page有资格从page cache中释放,并且swap map count将拥有PTE的slot在磁盘中的计数,从而该slot不会被释放。如果再次被轮训到,那么该page将从LRU中丢弃。
从另外一个方面讲,如果一个page fault发生在一个被swap out的page上,do_swap_page()将会来通过look_up_swap_cache()检查该page是否在swap cache中存在,如果存在,该PTE将会更行成其应该指向的page frame,然后改page的引用计数加一,而swap slot将会调用swap_free()来减一,如果为0,则释放该slot。

swp_entry_t get_swap_page()
该函数通过搜索active swap area然后再在swap_map中分配一个slot
int add_to_swap_cache(struct page *page, swp_entry_t entry)
可以通过该函数项swap cache中添加一个page。首先通过swap_duplicate()来check是否已经存在,如果不存在则调用普通page cache的接口将其加入到swap cache
struct page * lookup_swap_cache(swp_entry_t entry)
该函数搜索swap cache然后返回一个与entry项对应的struct page
int swap_duplicate(swp_entry_t entry)
该函数验证一个swap entry是否合法如果合法,则增加它的swap map count
void swap_free(swp_entry_t entry)
该函数与swap_dunpicate()相反,它来减少响应的swap_map的计数,如果到达0,则释放响应的slot

Reading Pages from Backing Storage

在page fault的过程中,当要读取一个swap cache中的page的时候,使用最终要的函数read_swap_cache_asyn()。该函数通过find_get_page()来搜索swap cache。通常来讲搜索swap cache使用lookup_swap_cache(),但是find_get_page()可以更新搜索操作的统计信息并且可能会有多次搜索。
read pg cache
在swap cache中的page如果有被其他进程映射该page或者多个进程同时在该页上触发page fault,如果page不在swap cache中,则需要重新分配一个page并将磁盘中的数据填充到里面。
如果page一旦分配,然后就会通过调用add_to_swap_cache()将其加入到swap cache中因为swap cache的操作函数只会对swap cache中的page进行操作。如果page不能加入到swap cache,该swap cache将会被重新搜索来确保另外的进程没有向swap cache中写入新数据。
一旦执行完成find_get_page()将会调用page_cache_release()来减少对该page的引用计数。

Writing Pages to Backing Storage

任何page想要被写会到磁盘,将会调用到address_space->a_ops中的写函数。如果当前的address_space是swapper_space,然后就会调用其包含的swap_aops,其中写函数就是swap_writepage()
swappp
swap_writepage()会根据是否当前写进程是否是当前swap cache page的最后一个使用者而不同。remove_exclusive_swap_page()函数会检查是否有其他进程正在使用该page。通过检查pagecache_lock来检查page的引用计数。如果没有其他进程在映射该page,那么它将从swap cache中删除和释放。
如果 remove_exclusive_swap_page()函数将page从swap cache中删除和释放,swap_writepage()将会把该page解锁因为没有再使用。如果该page仍然存在于swap cache,将调用rw_swap_page()将数据写入到磁盘当中。

Reading/Writing Swap Area Blocks

rw_swap_page()用来读写swap area。该函数保证所有的操作都通过swap cache从而避免数据漏更新。rw_swap_page_base()是真正干活的核心函数。
它首先检查当前操作是否是读。如果是,它将通过ClearPageUptodate()清除uptodate flag,是因为要求IO向page中填入数据,该page显然不是要更新。该flag会被重新设置为1,一旦page从磁盘中成功读取数据。然后调用get_swaphandle_info()请求device中该swap file的iNode.
这些将会在block layer中执行真正的IO操作。
该核心函数既能用作swap area和普通文件,因为它调用block layer的brw_page()函数来操作真实地磁盘IO。

Activating a Swap Area

在此小节之前介绍了swap area是什么以及它们是如何呈现的以及page是如何追踪的。现在来看看它们是如何把前面的内容绑定在一起来激活一个area。激活一个area概念上箱单简单。打开一个文件,从磁盘中加载header,搞一个swap_info_struct然后添加到swap list中。
负责激活一个swap area的函数是sys_swapon()。它有两个参数,指向swap area的特殊文件和一组标志位。当该函数执行过程中Big Kernel Lock将被锁上来阻止其他应用进入到kernel space。该函数相当大但可以拆解为以下几个简单步骤:

  • 从swap_info中找到一个swap_info_struct,然后将其初始化为默认值。
    调用user_path_work()来编译输入参数special file对应的目录树然后再生成一个namidata的结构用于存放该file相关的数据如:dentry和存在vfsmount中文件系统的信息。
  • 填充swap_info_struct中的字段,用来如何找到它。如果swap area是一个分区,block size将会被配置为PAGE_SIZE在计算size之前。如果是一个file,这些信息将会从inode中获取。
  • 保证该area没有已经被激活。如果没有,那么将从memory中分配一个page然后读取swap area中的第一个page sized slot。该page将存放有多少个好slot以及如何使用bad entries来填充swap_info_struct->swap_map。
  • 通过vmalloc()来分配swap_info_struct->swap_map的空间和然后初始化每个entry,好的slot填入0坏的填入SWAP_MAP_BAD。
  • 当确保header中初始化的信息和实际的swap area是匹配的之后,让swap_info_struct中填入剩下的信息如:page的最大个数,可用good pages。然后再更新全局的统计信息nr_swap_pages和total_swap_pages.
  • 至此一个swap area已经被激活和被初始化。它按照优先级被正确的插入到swap list中了

在最后释放BLK,至此系统将有一个新的swap area可用了。

Deactivating a Swap Area

与激活swap area相比,注销一个swap area代价相当大了。最基本的问题是该swap area不能简单地删除,每一个被换出的page必须要在换回去。正如前面所说的,没有一个快速的办法将一个page映射到每一个引用它的PTE中,因此也没有一个快速的办法将一个swap entry快速映射到一个PTE。这就要求所有进程的page table被遍历来找到有引用swap area的PTEs。swap主要会失败如果物理内存不足。
负责主要一个swap area的函数为sys_swapoff()。该函数主要功能是通过条用try_to_unuse()来将被换出的page再page in。try_to_unuse()相当昂贵。通过遍历所有进程的page tables来找到swap_map中的一个slot。最坏的情况是所有mm_structS的page tables都有可能被遍历。因此注销一个swap area主要步骤如下:

  • 调用user_path_walk()来获取要被主要的special file的信息,然后锁住BLK
  • 从swap list中删除swap_info_struct,更新全局统计信息,BLK释放
  • 调用try_to_unuse()来page in 所有page out的pages。通过循环调用find_next_to_unuse()来定位每一个slot:
    • 调用read_swap_cache_async()为存储在磁盘中的该slot分配一个page,按道理它还在swap cache中,但是如果不在,page allocator会被调用
    • 等待该page完全page in然后锁住它。一旦锁住,为每一个引用该page的进程调用unuse_process()。该函数将会遍历page table找到相关的PTE然后更新该page到PTE。如果该page是一个共享内存的page并且没有人用,则使用shmem_unuse()
    • 释放所有slot的映射。
    • 从swap cache中删除该page来阻止try_to_swap_out()来引用该case下的page,它在swap_map中仍有引用
  • 如果当前没有足够的物理内存,该swap area将会被重新插入到正在运行的系统中,因为它不能简单滴丢弃。如果成功,swap_info_struct将被设置为未初始化状态,swap_map所对应的memory将使用vfree()释放。
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值