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()釋放。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章