swaponシステムコール-その2
スワップファイルの実際の利用できるページスロット(PAGE_SIZE分のブロック数)は、sys_swapon()からコールされる設定するsetup_swap_extents()で行われます。スワップファイルがデバイスファイルの場合、先頭ブロックから最大ブロックまでとする領域を、そのまま一気に登録すればいい話です。全領域をページスロットとして使えるわけで、またデバイスのブロックサイズ如何にかかわらず、ブロックサイズをPAGE_SIZEとして処理するようになっているからです。(使用有無のページスロットの管理はswp_mapで行っていました。)
ところがスワップファイルが通常のファイルだとそうは行きません。ファイルが物理的に連続している保障が無いからです。またブロックサイズもPAGE_SIZEで処理することができないからです。通常ファイルの処理では、まず最初のブロックをPAGE_SIZEでアライメントした位置として求めます。例えばPAGE_SIZEを4K,ブロックサイズを1Kとした場合、ブロック位置が1,2,3だとダメだということです。これはスワップ処理のビット演算等での実装を効率的に行うためだと思います。なおソース内の記述もこのサイズを前提とします。
そして決定した位置からPAGE_SIZE分のブロックが連続しているかチェックし、連続しているならそのブロック分(上記では4ブロック)を1ページスロットとして登録していきます。この処理はadd_swap_extent()で行います。なおadd_swap_extent()では、先に登録したswap_extentと今登録しているswap_extentが連続しているなら、マージするようになっています。
probe_block(ファイル下でのブロック位置)/page_noは処理上のワーク変数で、last_blockがブロック数でしょうか?
add_swap_extent()でswap_extentを作成します。swap_info_struct->extent_listにリストします。ただしそのページスロットが前にリストしたswap_extentと連続しているなら、そのswap_extentにマージします。
ところがスワップファイルが通常のファイルだとそうは行きません。ファイルが物理的に連続している保障が無いからです。またブロックサイズもPAGE_SIZEで処理することができないからです。通常ファイルの処理では、まず最初のブロックをPAGE_SIZEでアライメントした位置として求めます。例えばPAGE_SIZEを4K,ブロックサイズを1Kとした場合、ブロック位置が1,2,3だとダメだということです。これはスワップ処理のビット演算等での実装を効率的に行うためだと思います。なおソース内の記述もこのサイズを前提とします。
そして決定した位置からPAGE_SIZE分のブロックが連続しているかチェックし、連続しているならそのブロック分(上記では4ブロック)を1ページスロットとして登録していきます。この処理はadd_swap_extent()で行います。なおadd_swap_extent()では、先に登録したswap_extentと今登録しているswap_extentが連続しているなら、マージするようになっています。
static int setup_swap_extents(struct swap_info_struct *sis, sector_t *span) { struct inode *inode; unsigned blocks_per_page; unsigned long page_no; unsigned blkbits; sector_t probe_block; sector_t last_block; sector_t lowest_block = -1; sector_t highest_block = 0; int nr_extents = 0; int ret; inode = sis->swap_file->f_mapping->host; if (S_ISBLK(inode->i_mode)) { ret = add_swap_extent(sis, 0, sis->max, 0); *span = sis->pages; goto done; }スワップファイルがデバイスファイルなら、ページスロットの最初から最後までその開始ブロック位置を0として一気にswap_extentを作成しています。デバイスファイルならこれで処理は終了です。以降は通常ファイルの場合です。
blkbits = inode->i_blkbits; blocks_per_page = PAGE_SIZE >> blkbits; probe_block = 0; page_no = 0; last_block = i_size_read(inode) >> blkbits;blkbitsはブロックサイズを2のべき乗の値で、blocks_per_pageはPAGE_SIZE分のブロック数です。ブロックサイズが1KでPAGE_SIZEが4Kなら、blkbitsは11でblocks_per_pageは4という訳です。)
probe_block(ファイル下でのブロック位置)/page_noは処理上のワーク変数で、last_blockがブロック数でしょうか?
while ((probe_block + blocks_per_page) <= last_block && page_no < sis->max) { unsigned block_in_page; sector_t first_block; first_block = bmap(inode, probe_block); if (first_block == 0) goto bad_bmap;ファイル下の注目するブロックのブロック位置(デバイスとしての位置)を求めます。bmap()はそれを求めるコールバック関数がinodeに設定されていなければ0で返すようです。
if (first_block & (blocks_per_page - 1)) { probe_block++; goto reprobe; }その位置がPAGE_SIZEアライメントされて位置でなければ、チェックするブロック位置を進めて再チェックです。
for (block_in_page = 1; block_in_page < blocks_per_page; block_in_page++) {ループ条件は以降3ブロックが連続かどうかのチェックしています。
sector_t block; block = bmap(inode, probe_block + block_in_page); if (block == 0) goto bad_bmap; if (block != first_block + block_in_page) { probe_block++; goto reprobe; }blockはチェックしているブロック位置で、その最初のブロック位置(PAGE_SIZEとしての)がfirst_blockです。これはPAGE_SIZE間でブロックが連続してない事になります。
} first_block >>= (PAGE_SHIFT - blkbits);ここまでくれば、first_block以降PAGE_SIZE分ブロックは連続しています。このブロック位置をPAGE_SHIFT単位に変換します。
if (page_no) { if (first_block < lowest_block) lowest_block = first_block; if (first_block > highest_block) highest_block = first_block; }lowest_blockには処理においての最小ブロック位置、highest_blockは最大ブロック位置です。if分は先頭ブロックはスワップヘッダと使用しているためです。
ret = add_swap_extent(sis, page_no, 1, first_block);ページスロットNO(処理毎に順次インクリメントされていきます。)で、1ページスロット分、そしてデバイスとしてそのブロック位置を引数としてadd_swap_extent()をコールします。
if (ret < 0) goto out; nr_extents += ret;add_swap_extent()の返り値をextents数として加算します。もしadd_swap_extent()でextentが新規に作成されてら1、マージされたら0を返すようになっています。
page_no++; probe_block += blocks_per_page;PAGE_SIZE分のブロック数を加算した位置を、次の検査対象ブロック位置としています。
reprobe: continue; } : : }
add_swap_extent()でswap_extentを作成します。swap_info_struct->extent_listにリストします。ただしそのページスロットが前にリストしたswap_extentと連続しているなら、そのswap_extentにマージします。
static int add_swap_extent(struct swap_info_struct *sis, unsigned long start_page, unsigned long nr_pages, sector_t start_block) { struct swap_extent *se; struct swap_extent *new_se; struct list_head *lh; lh = sis->extent_list.prev; if (lh != &sis->extent_list) {リストヘッダのprevが自分自身を指しているなら、まだこのヘッダにはリストされてない事です。従って以降はすでにswap_extentがリストされていると言う事です。
se = list_entry(lh, struct swap_extent, list); BUG_ON(se->start_page + se->nr_pages != start_page); if (se->start_block + se->nr_pages == start_block) { se->nr_pages += nr_pages; return 0;リストされてswap_extentの開始ブロック数のページスロット分が、次のswap_extentの開始ブロック位置ならこのページスロットは連続しています。リストされているswap_extentのページスロット数を加算すればいいだけ処理は終了です。この場合0を返します。
} }上記処理で新規にswap_extentを作成する場合、あるいは新しいswap_extentと前のswap_extentが連続で無い場合の処理です。
new_se = kmalloc(sizeof(*se), GFP_KERNEL); if (new_se == NULL) return -ENOMEM; new_se->start_page = start_page; new_se->nr_pages = nr_pages; new_se->start_block = start_block; list_add_tail(&new_se->list, &sis->extent_list); return 1;kmalloc()でメモリを確保し、それぞれの情報を設定し、sis->extent_listでリストしえいます。この場合新規に作成したswap_extentの数として、1を返しています。
}