mm/page_io.c

  首次接触swap设备/文件,简单介绍一下。

  首先是swap_entry的结构: 
/* Encode and de-code a swap entry */
/*  |......24bits offset ......|1bits|6bit type| present bit==0|   */
typedef struct {
	unsigned long val;
} swp_entry_t;

  当页面被交换到磁盘时,页表的entry(pte_t)被如上结构的swap entry所取代.
此时present bit被置0,cpu 认为页面不在内存,pte_t的其他部分按照swap entry的
规划由os解释.
  
  
  然后来看看交换设备紧密相关的一些数据结构. 交换设备可以是单独的交换分区,
也可以是一般的文件. 这一点可以看文件 mm/swapfile.c
           struct swap_info_struct swap_info[MAX_SWAPFILES];
和函数 get_swaphandle_info 即可得到印证.
void get_swaphandle_info(swp_entry_t entry, unsigned long *offset, 
			kdev_t *dev, struct inode **swapf)
{
  	unsigned long type;
	  struct swap_info_struct *p;

/*  |......24bits offset ......|1bits|6bit type| present bit==0|   */
	type = SWP_TYPE(entry);
	if (type >= nr_swapfiles) {
		printk("Internal error: bad swap-device\n");
		return;
	}

	p = &swap_info[type];
	*offset = SWP_OFFSET(entry);
	if (*offset >= p->max) {
		printk("rw_swap_page: weirdness\n");
		return;
	}
	if (p->swap_map && !p->swap_map[*offset]) {/*引用计数为零,尚未使用*/
		printk("VM: Bad swap entry %08lx\n", entry.val);
		return;
	}
	if (!(p->flags & SWP_USED)) {
		printk(KERN_ERR "rw_swap_page: "
			"Trying to swap to unused swap-device\n");
		return;
	}

	if (p->swap_device) { /* 如果是交换分区swap_device非空*/
		*dev = p->swap_device;
	} else if (p->swap_file) {/*否则是交换文件*/
		*swapf = p->swap_file->d_inode;
	} else {
		printk(KERN_ERR "rw_swap_page: no swap file or device\n");
	}
	return;
}

   先不说swap_info, 讨论一下交换设备的结构. 交换设备的空间被作为后备页面
来使用,尺寸等于cpu页面的大小.交换设备上的第一个页面被用于swap header,:

/*交换设备和header格式,大小为一个page */
union swap_header {
	struct 
	{
		char reserved[PAGE_SIZE - 10];
		char magic[10];
	} magic;
	struct 
	{
		char	     bootbits[1024];	/* Space for disklabel etc. */
		unsigned int version;
		unsigned int last_page; /* 交换设备上最后一个页面的nr*/
		unsigned int nr_badpages; /*多少页面是损坏的*/
		unsigned int padding[125];
		unsigned int badpages[1]; /*损坏页面的索引数组*/
	} info;
};

   记录了其大小,版本,损坏页面,magic等信息。在sys_swapon时转化为swap_info
中的信息,刚才已经简单提到了,现在完整的概述一下swap_info:
struct swap_info_struct {
	unsigned int flags;
	kdev_t swap_device;
	spinlock_t sdev_lock;
	struct dentry * swap_file;
	struct vfsmount *swap_vfsmnt;
	unsigned short * swap_map; /*记录交换设备上page 的引用计数(SWAP_MAP_MAX)*/
	                                           /* 数组大小为this->max */

  /*按簇分配算法变量,一个簇包含SWAPFILE_CLUSTER 个页面 */
	unsigned int lowest_bit;  /* 和highest_bit一起构成有可能空闲的页面的索引范围*/
	unsigned int highest_bit;
	unsigned int cluster_next; /*swap cluster 中下一个可分配页面*/
	unsigned int cluster_nr; /*本簇内剩余页面数量*/

	int prio;			/* swap priority */
	int pages;  /*nr_good_pages*/
	unsigned long max; /*来自swap_header 的last_page(和),见sys_swapon*/
	int next;			/* next entry on swap list */
};
  信息来自swap header以及交换设备,还有安cluster分配交换页面所需的一些结构。
仔细看看各个成分代表的含义,具体的代码很容易看懂的。
  暂时介绍到这里。
  
  看看mm/page_io.c所提供的接口:rw_swap_page,rw_swap_page_nolock,其实是swap和
设备驱动的一个接口,提供了对swap设备上文件的读写功能.
  这两个函数差别不大,rw_swap_page_nolock现在只用于sys_swapon(现在关注的范围内)
用于读取上述的swap_header.(见sys_swapon,一个临时页面).这个页面的特殊之处在于它
其实不属于swap cache(特殊的 page cache).不能直接使用rw_swap_page读取,所以做了一
些特殊处理:

/*
 * The swap lock map insists that pages be in the page cache!
 * Therefore we can't use it.  Later when we can remove the need for the
 * lock map and we can reduce the number of functions exported.
 */
void rw_swap_page_nolock(int rw, swp_entry_t entry, char *buf, int wait)
{
	struct page *page = virt_to_page(buf);
	
	if (!PageLocked(page))
		PAGE_BUG(page);
	if (PageSwapCache(page)) //在swap cahce 就不对了
		PAGE_BUG(page);
	if (page->mapping) //不能属于某个map
		PAGE_BUG(page);
	/* needs sync_page to wait I/O completation */
	page->mapping = &swapper_space; //借用一下swap space
	if (!rw_swap_page_base(rw, entry, page, wait))
		UnlockPage(page);
	page->mapping = NULL; 
}
  借用swaper_space的原因是,rw_swap_page_base,需要wait_on_page,而wait_on_page
则使用sync_page:
static inline int sync_page(struct page *page)
{
	struct address_space *mapping = page->mapping;

	if (mapping && mapping->a_ops && mapping->a_ops->sync_page)
		return mapping->a_ops->sync_page(page);
	return 0;
}
  故此需要这样一个辗转的过程。现在来看看这两个接口共同的逻辑部分:
/*
 * Reads or writes a swap page.
 * wait=1: start I/O and wait for completion. wait=0: start asynchronous I/O.
 *
 * Important prevention of race condition: the caller *must* atomically 
 * create a unique swap cache entry for this swap page before calling
 * rw_swap_page, and must lock that page.  By ensuring that there is a
 * single page of memory reserved for the swap entry, the normal VM page
 * lock on that page also doubles as a lock on swap entries.  Having only
 * one lock to deal with per swap entry (rather than locking swap and memory
 * independently) also makes it easier to make certain swapping operations
 * atomic, which is particularly important when we are trying to ensure 
 * that shared pages stay shared while being swapped.
 */

static int rw_swap_page_base(int rw, swp_entry_t entry, struct page *page, int wait)
{
	unsigned long offset;
	int zones[PAGE_SIZE/512];
	int zones_used;
	kdev_t dev = 0;
	int block_size;
	struct inode *swapf = 0;

	/* Don't allow too many pending pages in flight.. */
	if ((rw == WRITE) && atomic_read(&nr_async_pages) >
			pager_daemon.swap_cluster * (1 << page_cluster))
		wait = 1;

	if (rw == READ) {
		ClearPageUptodate(page);
		kstat.pswpin++;
	} else
		kstat.pswpout++;

       /*从swap entry找到交换设备或者交换文件
        *以及back page  在交换介质上的偏移
        */
       /*  |......24bits offset ......|1bits|6bit type| present bit==0|   */
	get_swaphandle_info(entry, &offset, &dev, &swapf);
	if (dev) {
		zones[0] = offset;
		zones_used = 1;
		block_size = PAGE_SIZE;
	} else if (swapf) {
		int i, j;
		unsigned int block = offset
			<< (PAGE_SHIFT - swapf->i_sb->s_blocksize_bits);

		block_size = swapf->i_sb->s_blocksize;
		for (i=0, j=0; j< PAGE_SIZE ; i++, j += block_size)
			if (!(zones[i] = bmap(swapf,block++))) {
				printk("rw_swap_page: bad swap file\n");
				return 0;
			}
		zones_used = i;
		dev = swapf->i_dev;
	} else {
		return 0;
	}
 	if (!wait) {
 		SetPageDecrAfter(page);
 		atomic_inc(&nr_async_pages);
 	}

 	/* block_size == PAGE_SIZE/zones_used */
 	brw_page(rw, page, dev, zones, block_size);

 	/* Note! For consistency we do all of the logic,
 	 * decrementing the page count, and unlocking the page in the
 	 * swap lock map - in the IO completion handler.
 	 */
 	if (!wait)
 		return 1;

 	wait_on_page(page);
	/* This shouldn't happen, but check to be sure. */
	if (page_count(page) == 0)
		printk(KERN_ERR "rw_swap_page: page unused while waiting!\n");

	return 1;
}
  
  对于这个函数首先要说的是全局变量nr_async_pages:
/* rw_swap_page_base: inc nr_async_pages    end_buffer_io_async:dec this one*/
/*异步io 状态就是提交读/写后不等待页面get unlocked*/
atomic_t nr_async_pages = ATOMIC_INIT(0); /*所有在异步io状态的页面总数*/
  
  还有pager_daemon.swap_cluster * (1 << page_cluster)):
其中pager_daemon.swap_cluster是最多容许预读的页面cluster个数,而page_cluster
则是每个cluster拥有的页面个数:2^page_cluster。所以rw_swap_page_base中相关的
判断就是不让在异步io状态的页面超过所容许的总的预读页面数量。

  说到这里,page_cluster是某个cluster的大小,上面还提到了SWAPFILE_CLUSTER也是
某个cluster的大小,这连个cluster的区别在于:
  page_cluster所指的是的簇用于filemap相关的预读,以及swap in的时候所做的预读。
而普通文件的预读情况参考do_generic_file_read的相关分析。
  SWAPFILE_CLUSTER则是用于swap设备上的swap页面分配时的分配策略使用的簇,相关
代码参考:mm/swapfile.c 函数scan_swap_map.到时再论.

  其次要说的是bmap这个函数,简单看,如果swap设备是一个普通文件,则以ext2文件为例
bmap-> {inode->i_mapping->a_ops->bmap(inode->i_mapping, block)}->ext2_bmap->
generic_block_bmap->ext2_get_block. 核心函数是ext2_get_block,实际上是文件内的
连续的block nr到文件所在的设备的block号的一个 查找/分配 的过程.具体的这里就不
再细述,等到分析ext2文件系统再论不迟.
  看rw_swap_page_base相关部分,如果是一个交换分区,这个复杂过程就可以省略了,所以
交换分区的效率还是值得肯定的.

  另外简单提一下这个调用:
  brw_page(rw, page, dev, zones, block_size);
  如果仔细看看rw_swap_page_base,可能会担心block size是否会大于page size?据我所
知对于ext2文件系统,blocksize不会大于4k,见ext2_read_super.
  brw_page在这个page上创建buffers,设置buffer对应的设备block nr,提交设备驱动读取
相应block的内容.细节部分等到分析设备驱动的时候再续.

  The end.