swaponシステムコール-その1
Rev.3を表示中。最新版はこちら。
スワップファイルは先頭のページスロット(ページサイズ分の連続するブロック)に、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. */ __u32 version; __u32 last_page; __u32 nr_badpages; unsigned char sws_uuid[16]; unsigned char sws_volume[16]; __u32 padding[117]; __u32 badpages[1]; } info; }swap_headerはunionです。従って先頭にbootbitsからの情報が書き込まれ、ページスロットの最後にmagic[10]が書き込まれています。magic[10]には文字列のSWAP-SPACE/SWAPSPACE2が書き込まれ、これによりカーネルはこのファイルがスワップファイルだと認識します。なおSWAP-SPACEはver2.6からサポートされなくなりました。badpages[1]は配列が1つとなっていますが、それ以降magicまでの領域をbadpagesとしています。
swaponコマンドはこのスワップファイルの情報参照して、struct swap_info_struct swap_info[MAX_SWAPFILES]の未使用のswap_info[]に設定し、これをswap_listをヘッドとするリストに優先度に従って挿入する事にあります。
struct swap_info_struct { unsigned int flags; int prio; /* swap priority */ struct file *swap_file; struct block_device *bdev; struct list_head extent_list; 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; unsigned int pages; unsigned int max; unsigned int inuse_pages; int next; /* next entry on swap list */ };スワップファイルはデバイスファイル/通常のファイルを設定することが可能です。スワップ処理その物としての基本的な違いはありません。ただスワップファイルはブロックが物理的に連続している必要があります。通常のファイルのように物理的に連続してないケースをサポートするためその管理は、物理的に連続するブロック単位をswap_extentとして、それをextent_listにリストすることで実装しています。デバイスファイルだとswap_extentは1つというわけです。swap_mapでextent_listでリストしたswap_extentをページスロットの使用/未使用を管理することになります。
スワップはPAGE_SIZEサイズで読み書きが行われます。これはページスロットがPAGE_SIZEサイズの連続するブロックである事を意味します。ブロックデバイスはブロックサイズは可変です。スワップファイルがデバイスファイルの場合、ブロックデバイスのブロックサイズを無視してブロックサイズをPAGE_SIZEに変更して処理していますが、通常ファイルの場合はそうは行きません。この辺りがチェックぐちゃぐちゃしています。なをこの処理は setup_swap_extents()で行っています。
SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags) { struct swap_info_struct * p; char *name = NULL; struct block_device *bdev = NULL; struct file *swap_file = NULL; struct address_space *mapping; unsigned int type; int i, prev; int error; union swap_header *swap_header = NULL; int swap_header_version; unsigned int nr_good_pages = 0; int nr_extents = 0; sector_t span; unsigned long maxpages = 1; int swapfilesize; unsigned short *swap_map = NULL; struct page *page = NULL; struct inode *inode = NULL; int did_down = 0; if (!capable(CAP_SYS_ADMIN)) return -EPERM; spin_lock(&swap_lock); p = swap_info; for (type = 0 ; type < nr_swapfiles ; type++,p++) if (!(p->flags & SWP_USED)) break;swap_info[]から未使用のインデックを取得します。nr_swapfilesはswap_info[]を使用している最大インデックです。
error = -EPERM; if (type >= MAX_SWAPFILES) { spin_unlock(&swap_lock); goto out; } if (type >= nr_swapfiles) nr_swapfiles = type+1;nr_swapfilesまでの間で空きが無かった場合です。
memset(p, 0, sizeof(*p)); INIT_LIST_HEAD(&p->extent_list); p->flags = SWP_USED; p->next = -1; spin_unlock(&swap_lock); name = getname(specialfile); error = PTR_ERR(name); if (IS_ERR(name)) { name = NULL; goto bad_swap_2; } swap_file = filp_open(name, O_RDWR|O_LARGEFILE, 0); error = PTR_ERR(swap_file); if (IS_ERR(swap_file)) { swap_file = NULL; goto bad_swap_2; }swap_infoに(p = swap_infoとしています。)を初期化し、スワップファイルをオープンします。
p->swap_file = swap_file; mapping = swap_file->f_mapping; inode = mapping->host; error = -EBUSY; for (i = 0; i < nr_swapfiles; i++) { struct swap_info_struct *q = &swap_info[i]; if (i == type || !q->swap_file) continue; if (mapping == q->swap_file->f_mapping) goto bad_swap; }アドレス空間が同じ物が、すでにswaponしているスワップファイル内にあるかチェックしています。これは同じリンクで結果的にスワップファイルで再swaponした場合のケースでしょうか?
error = -EINVAL; if (S_ISBLK(inode->i_mode)) { bdev = I_BDEV(inode); error = bd_claim(bdev, sys_swapon); if (error < 0) { bdev = NULL; error = -EINVAL; goto bad_swap; } p->old_block_size = block_size(bdev); error = set_blocksize(bdev, PAGE_SIZE); if (error < 0) goto bad_swap; p->bdev = bdev;スワップファイルがブロックファイルのケースです。bd_claim()はこのデバイスが既に使用しているかどうかのチェックを行って、使用されていないならスワップデバイスでして使用する旨の設定をbdevに施します。そしてblock_size()でデバイスブロックサイズをp->old_block_sizeに保存した後、set_blocksize()でこのデバイスブロックサイズをPAGE_SIZEに設定します。
} else if (S_ISREG(inode->i_mode)) { p->bdev = inode->i_sb->s_bdev; mutex_lock(&inode->i_mutex); did_down = 1; if (IS_SWAPFILE(inode)) { error = -EBUSY; goto bad_swap;did_down = 1はスワップファイルとしてOKなら、このinode->i_flagsにS_SWAPFILEを設定するためです。これでこのファイルはスワップファイルとして使用していることになります。以下でIS_SWAPFILE(inode)とこのファイルが既にスワップとして使用されているかチェックしています。そうなると上でのアドレス空間のチェックの意味が今ひとつ解らなくなってきました。
} } else { goto bad_swap; } swapfilesize = i_size_read(inode) >> PAGE_SHIFT; if (!mapping->a_ops->readpage) { error = -EINVAL; goto bad_swap; } page = read_mapping_page(mapping, 0, swap_file); if (IS_ERR(page)) { error = PTR_ERR(page); goto bad_swap; } kmap(page); swap_header = page_address(page);先頭の1ブロック(ページスロット)を読み込んでいます。kmap()はそのページがハイメモリのケースを想定してのおまじないです。
if (!memcmp("SWAP-SPACE",swap_header->magic.magic,10)) swap_header_version = 1; else if (!memcmp("SWAPSPACE2",swap_header->magic.magic,10)) swap_header_version = 2; else { printk(KERN_ERR "Unable to find swap-space signature\n"); error = -EINVAL; goto bad_swap; }swap_headerのmagic文字列をチェックすることで、スワップファイルのバージョンを取得します。
switch (swap_header_version) { case 1: printk(KERN_ERR "version 0 swap is no longer supported. " "Use mkswap -v1 %s\n", name); error = -EINVAL; goto bad_swap;バージョン1はもはやサポートされていません。
case 2: if (swab32(swap_header->info.version) == 1) { swab32s(&swap_header->info.version); swab32s(&swap_header->info.last_page); swab32s(&swap_header->info.nr_badpages); for (i = 0; i < swap_header->info.nr_badpages; i++) swab32s(&swap_header->info.badpages[i]); }ヘッダー情報のエンディアンにおける動作システムにあった修正を行います。これは他の異なったシステム下で作成されたスワップファイルを使用した場合を想定してかと・・・。
if (swap_header->info.version != 1) { printk(KERN_WARNING "Unable to handle swap header version %d\n", swap_header->info.version); error = -EINVAL; goto bad_swap;swap_header->info.versionは1でないとダメなようです。
} p->lowest_bit = 1; p->cluster_next = 1;lowest_bitは空きページスロットを検索の開始位置で、cluster_nextは検索中での次のスロットの検索開始位置です。
maxpages = swp_offset(pte_to_swp_entry(swp_entry_to_pte(swp_entry(0,~0UL)))) - 1; if (maxpages > swap_header->info.last_page) maxpages = swap_header->info.last_page; p->highest_bit = maxpages - 1;maxpagesはシステムでサポートできるページスロット数で、これは2つの要素に掛かるシステム依存です。1つはスワップアウトページ識別子のページスロットインデックス長さと、pteのサイズ(仮想メモリー空間)によるものです。maxpagesには対象のスワップファイルのページスロット数となります。なおhighest_bitは空きスロット検索を終了する位置です。-1は先頭のヘッダです。
if (!maxpages) goto bad_swap; if (swapfilesize && maxpages > swapfilesize) { printk(KERN_WARNING "Swap area shorter than signature indicates\n"); goto bad_swap;swapfilesizeはinodeから取得したファイルサイズ(サイズその物)で、スワップヘッダから取得したサイズの方が大きいと言うのは問題です。
} if (swap_header->info.nr_badpages && S_ISREG(inode->i_mode)) goto bad_swap;nr_badpagesは使用できないページスロット数です。通常ファイルの場合はダメな様です。
if (swap_header->info.nr_badpages > MAX_SWAP_BADPAGES) goto bad_swap;ブロックファイルの場合です。nr_badpagesはMAX_SWAP_BADPAGESを超えてはダメなようです。MAX_SWAP_BADPAGESは以下のように定義されています。
#define MAX_SWAP_BADPAGES ((__swapoffset(magic.magic) - __swapoffset(info.badpages)) / sizeof(int))
swap_map = vmalloc(maxpages * sizeof(short)); if (!swap_map) { error = -ENOMEM; goto bad_swap; } error = 0; memset(swap_map, 0, maxpages * sizeof(short));ページスロット数分swap_mapを確保します。この配列1つづつがページスロットに対応します。
for (i = 0; i < swap_header->info.nr_badpages; i++) { int page_nr = swap_header->info.badpages[i]; if (page_nr <= 0 || page_nr >= swap_header->info.last_page) error = -EINVAL; else swap_map[page_nr] = SWAP_MAP_BAD; }使用できないページスロット数nr_badpagesに対応するswap_mapにSWAP_MAP_BADを設定します。
nr_good_pages = swap_header->info.last_page - swap_header->info.nr_badpages - 1 /* header page */;last_pageはページスロットの終端です。nr_good_pagesは使用できるページスロット数です。
if (error) goto bad_swap; } if (nr_good_pages) { swap_map[0] = SWAP_MAP_BAD; p->max = maxpages; p->pages = nr_good_pages; nr_extents = setup_swap_extents(p, &span); if (nr_extents < 0) { error = nr_extents; goto bad_swap; } nr_good_pages = p->pages;swap_map[0]はヘッダの先頭ページスロットです。これはもちろん使用できません。setup_swap_extents()で実際の連続するページサイズのブロック単位のswap_extentを取得します。通常ファイルでしかもブロックサイズがページサイズより小さい場合、実際のページスロットで使える領域は、本関数で処理した後に決定します。
} if (!nr_good_pages) { printk(KERN_WARNING "Empty swap-file\n"); error = -EINVAL; goto bad_swap; } mutex_lock(&swapon_mutex); spin_lock(&swap_lock); if (swap_flags & SWAP_FLAG_PREFER) p->prio = (swap_flags & SWAP_FLAG_PRIO_MASK) >> SWAP_FLAG_PRIO_SHIFT; else p->prio = --least_priority;スワップファイルの優先順位を設定します。それぞれのマクロは以下の様になっています。
#define SWAP_FLAG_PREFER 0x8000
#define SWAP_FLAG_PRIO_MASK 0x7fff
#define SWAP_FLAG_PRIO_SHIFT 0
p->swap_map = swap_map; p->flags = SWP_ACTIVE; nr_swap_pages += nr_good_pages; total_swap_pages += nr_good_pages; printk(KERN_INFO "Adding %uk swap on %s. " "Priority:%d extents:%d across:%lluk\n", nr_good_pages<<(PAGE_SHIFT-10), name, p->prio, nr_extents, (unsigned long long)span<<(PAGE_SHIFT-10)); prev = -1; for (i = swap_list.head; i >= 0; i = swap_info[i].next) { if (p->prio >= swap_info[i].prio) { break; } prev = i; } p->next = i; if (prev < 0) { swap_list.head = swap_list.next = p - swap_info; } else { swap_info[prev].next = p - swap_info; }上記で設定したswap_infoを、swap_listをヘッドとするリストに優先順位順に挿入します。if (prev < 0)はこのスワップファイルの優先順位が一番高かった場合、または初めてスワップとリストするケースです。この処理は、優先順準にリストされている事を前提とした処理となっています。なお、swap_list.head/swap_list.next/swap_info[].nextに繋がれるノードは、ノードそのもののポインタでなく、swap_info[]配列のインデックスとなっています。
spin_unlock(&swap_lock); mutex_unlock(&swapon_mutex); error = 0; goto out; bad_swap: if (bdev) { set_blocksize(bdev, p->old_block_size); bd_release(bdev); } destroy_swap_extents(p); bad_swap_2: spin_lock(&swap_lock); p->swap_file = NULL; p->flags = 0; spin_unlock(&swap_lock); vfree(swap_map); if (swap_file) filp_close(swap_file, NULL); out: if (page && !IS_ERR(page)) { kunmap(page); page_cache_release(page); } if (name) putname(name); if (did_down) { if (!error) inode->i_flags |= S_SWAPFILE; mutex_unlock(&inode->i_mutex); } return error; }
備考
struct swap_info_struct swap_info[MAX_SWAPFILES]はスタティックな変数です。従ってスワップファイルの最大はMAX_SWAPFILESとなります。MAX_SWAPFILESは以下のように定義されていて、CONFIG_MIGRATIONが定義されてないなら32、定義されていれば30個までとなります。CONFIG_MIGRATIONが定義されていれば最後の2つのエントリは別の用途で使われるようです。#define MAX_SWAPFILES_SHIFT 5 #ifndef CONFIG_MIGRATION #define MAX_SWAPFILES (1 << MAX_SWAPFILES_SHIFT) #else /* Use last two entries for page migration swap entries */ #define MAX_SWAPFILES ((1 << MAX_SWAPFILES_SHIFT)-2)