回到do_mpage_readpage中,从mpage_readpage传进来的buffer_head类型的map_bh参数的b_bdev、b_blocknr和b_size字段就被赋上值了,然后245~271行对这个map_bh进行一系列的检查,检查可能发生的异常条件。具体有这几种情况:当一些块在磁盘上不相邻时,或某块落如“文件洞”内时,或一个块缓冲区已经由get_block函数写入时。那么跳到confused标号处,用一次读一块的方式读该页。有关“文件的洞”的相关内容,请查阅ULK-3的相关章节。
273-282行,很重要,为内部变量blocks[MAX_BUF_PER_PAGE]赋值。由于一页面包含4个块,所以blocks[0]~ blocks[3]就存放着磁盘上是相邻连续4个块的逻辑块号。page_block内部变量也从0变成3了。
如果函数运行至286行,说明页中的所有块在磁盘上是相邻的。然而,它可能是文件中的最后一页,因此页中的一些块可能在磁盘上没有映像。如果这样的话,288行将页中相应的块缓冲区填上0;如果不是这样,298行将页描述符的标志PG_mappedtodisk置位。
继续走,前面看到,我们传递进来的bio是NULL的,所以来到308行的条件分支。309行调用调用mpage_alloc触发bio_alloc()函数分配包含单一段的一个新bio描述符,并且分别用块设备描述符地址和页中第一个块的逻辑块号blocks[0]来初始化bi_bdev字段和bi_sector字段。这两个信息已在上面的get_block函数中得到。
通常,bio结构是由slab分配器分配的,但是,当内存不足时,内核也会使用一个备用的bio小内存池。内核也为bio_vec结构分配内存池——毕竟,分配一个bio结构而不能分配其中的段描述符也是没有什么意义的。bio_alloc就通过bio_alloc_bioset函数在bs->bvec_pool中给他的bio_vec分配。相应地,bio_put()函数减少bio中引用计数器(bi_cnt)的值,如果该值等于0,则释放bio结构以及相关的bio_vec结构。
这个bio的bdev来自buffer_head的b_bdev字段;bi_destructor被设置为bio_fs_destructor函数,作为当bio上的I/O操作完成时所执行的完成程序的地址;bi_io_vec通过bvec_alloc_bs函数初始化成数组,表示若干个bio待传输的段;同时bi_flags和bi_max_vecs字段也被设置了;bi_sector是blocks[0]左移(blkbits - 9)位,得到该块对应磁盘扇区号,因为BIO更关心的是扇区号而不是块号,后面谈通用块处理的时候会重点讨论它。另外min_t(int, nr_pages, bio_get_nr_vecs(bdev))表示这个bio中的iovec个数,后面讲到通用块层的时候再来详细说这些东西,很重要的。
最后,bio初始化成功了,do_mpage_readpage成功返回这个bio,mpage_readpage就调用mpage_bio_submit(READ, bio)处理请求。
static struct bio *mpage_bio_submit(int rw, struct bio *bio) { bio->bi_end_io = mpage_end_io_read; if (rw == WRITE) bio->bi_end_io = mpage_end_io_write; submit_bio(rw, bio); return NULL; } |
我们看到mpage_bio_submit 函数将bio的bi_end_io字段赋值成 mpage_end_io_read函数,随后调用 submit_bio 函数处理该请求:
void submit_bio(int rw, struct bio *bio) { int count = bio_sectors(bio);
BIO_BUG_ON(!bio->bi_size); BIO_BUG_ON(!bio->bi_io_vec); bio->bi_rw |= rw; if (rw & WRITE) count_vm_events(PGPGOUT, count); else count_vm_events(PGPGIN, count);
if (unlikely(block_dump)) { char b[BDEVNAME_SIZE]; printk(KERN_DEBUG "%s(%d): %s block %Lu on %s/n", current->comm, current->pid, (rw & WRITE) ? "WRITE" : "READ", (unsigned long long)bio->bi_sector, bdevname(bio->bi_bdev,b)); }
generic_make_request(bio); } |
函数最终将请求传递给函数 generic_make_request ,并由 generic_make_request 函数将请求
提交给通用块层处理,到此为止,页高速缓存层的处理结束。
看到do_mpage_readpage函数的confused标号处,如果页中含有的块在磁盘上不连续,则函数跳到这里。如果页是最新的,调用block_read_full_page函数,这里牵涉到块设备文件的readpage方法。
在前面ext2_read_inode函数中,我们看到init_special_inode函数用来建立设备的索引节点。该函数把索引节点对象的i_rdev字段初始化为设备文件的主设备号和次设备号,而把索引节点对象的i_fop字段设置为def_blk_fops或者def_chr_fops文件操作表的地址(根据设备文件的类型);把索引节点的i_data对应的a_ops字段设置为def_blk_aops(字符驱动不需要页高速缓存)操作表地址。因此,open()系统调用的服务例程也调用dentry_open()函数,后者分配一个新的文件对象并把其f_op字段设置为i_fop中存放的地址,即再一次指向def_blk_fops或def_chr_fops的地址。正是这两个表的引入,才使得在设备文件上所发出的任何系统调用都将激活设备驱动程序的函数而不是基本文件系统的函数。
我们这里是块设备文件,所以关注的是def_blk_fops全局变量,来自fs/block_dev.c:
const struct file_operations def_blk_fops = { .open = blkdev_open, .release = blkdev_close, .llseek = block_llseek, .read = generic_file_read, .write = blkdev_file_write, .aio_read = generic_file_aio_read, .aio_write = blkdev_file_aio_write, .mmap = generic_file_mmap, .fsync = block_fsync, .unlocked_ioctl = block_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = compat_blkdev_ioctl, #endif .readv = generic_file_readv, .writev = generic_file_write_nolock, .sendfile = generic_file_sendfile, .splice_read = generic_file_splice_read, .splice_write = generic_file_splice_write, }; |
块设备文件对应页高速缓存的函数来自def_blk_aops全局变量,也位于同一文件中:
const struct address_space_operations def_blk_aops = { .readpage = blkdev_readpage, .writepage = blkdev_writepage, .sync_page = block_sync_page, .prepare_write = blkdev_prepare_write, .commit_write = blkdev_commit_write, .writepages = generic_writepages, .direct_IO = blkdev_direct_IO, }; |
在bdev特殊文件系统中,块设备使用address_space对象,该对象存放在对应块设备索引节点的i_data字段。不像普通文件(在address_space对象中它的readpage方法依赖于文件所属的文件系统的类型),块设备文件的readpage方法总是相同的。它是由blkdev_readpage()函数实现的,该函数调用block_read_full_page():
int blkdev_readpage(struct file * file, struct * page page)
{
return block_read_full_page(page, blkdev_get_block);
}
正如你看到的,这个函数又是一个封装函数,这里是block_read_full_page()函数的封装函数。block_read_full_page函数,来自fs/buffer.c:
2063int block_read_full_page(struct page *page, get_block_t *get_block) 2064{ 2065 struct inode *inode = page->mapping->host; 2066 sector_t iblock, lblock; 2067 struct buffer_head *bh, *head, *arr[MAX_BUF_PER_PAGE]; 2068 unsigned int blocksize; 2069 int nr, i; 2070 int fully_mapped = 1; 2071 2072 BUG_ON(!PageLocked(page)); 2073 blocksize = 1 << inode->i_blkbits; 2074 if (!page_has_buffers(page)) 2075 create_empty_buffers(page, blocksize, 0); 2076 head = page_buffers(page); 2077 2078 iblock = (sector_t)page->index << (PAGE_CACHE_SHIFT - inode->i_blkbits); 2079 lblock = (i_size_read(inode)+blocksize-1) >> inode->i_blkbits; 2080 bh = head; 2081 nr = 0; 2082 i = 0; 2083 2084 do { 2085 if (buffer_uptodate(bh)) 2086 continue; 2087 2088 if (!buffer_mapped(bh)) { 2089 int err = 0; 2090 2091 fully_mapped = 0; 2092 if (iblock < lblock) { 2093 WARN_ON(bh->b_size != blocksize); 2094 err = get_block(inode, iblock, bh, 0); 2095 if (err) 2096 SetPageError(page); 2097 } 2098 if (!buffer_mapped(bh)) { 2099 void *kaddr = kmap_atomic(page, KM_USER0); 2100 memset(kaddr + i * blocksize, 0, blocksize); 2101 flush_dcache_page(page); 2102 kunmap_atomic(kaddr, KM_USER0); 2103 if (!err) 2104 set_buffer_uptodate(bh); 2105 continue; 2106 } 2107 /* 2108 * get_block() might have updated the buffer 2109 * synchronously 2110 */ 2111 if (buffer_uptodate(bh)) 2112 continue; 2113 } 2114 arr[nr++] = bh; 2115 } while (i++, iblock++, (bh = bh->b_this_page) != head); 2116 2117 if (fully_mapped) 2118 SetPageMappedToDisk(page); 2119 2120 if (!nr) { 2121 /* 2122 * All buffers are uptodate - we can set the page uptodate 2123 * as well. But not if get_block() returned an error. 2124 */ 2125 if (!PageError(page)) 2126 SetPageUptodate(page); 2127 unlock_page(page); 2128 return 0; 2129 } 2130 2131 /* Stage two: lock the buffers */ 2132 for (i = 0; i < nr; i++) { 2133 bh = arr[i]; 2134 lock_buffer(bh); 2135 mark_buffer_async_read(bh); 2136 } 2137 2138 /* 2139 * Stage 3: start the IO. Check for uptodateness 2140 * inside the buffer lock in case another process reading 2141 * the underlying blockdev brought it uptodate (the sct fix). 2142 */ 2143 for (i = 0; i < nr; i++) { 2144 bh = arr[i]; 2145 if (buffer_uptodate(bh)) 2146 end_buffer_async_read(bh, 1); 2147 else 2148 submit_bh(READ, bh); 2149 } 2150 return 0; 2151} |
block_read_full_page函数的第二个参数也指向一个函数,该函数把相对于文件开始处的文件块号转换为相对于块设备开始处的逻辑块号。不过,对于块设备文件来说,这两个数是一致的;因此,blkdev_get_block()函数就比较简单了,来自:
109blkdev_get_block(struct inode *inode, sector_t iblock, 110 struct buffer_head *bh, int create) 111{ 112 if (iblock >= max_block(I_BDEV(inode))) { 113 if (create) 114 return -EIO; 115 116 /* 117 * for reads, we're just trying to fill a partial page. 118 * return a hole, they will have to call get_block again 119 * before they can fill it, and they will get -EIO at that 120 * time 121 */ 122 return 0; 123 } 124 bh->b_bdev = I_BDEV(inode); 125 bh->b_blocknr = iblock; 126 set_buffer_mapped(bh); 127 return 0; 128} |
blkdev_get_block函数112行检查页中第一个块的块号是否超过块设备的最后一块的索引值(通过max_block函数将存放在bdev->bd_inode->i_size中的块设备大小除以存放在bdev->bd_block_size中的块大小得到该索引值;bdev指向块设备描述符)。如果超过,那么对于写操作它返-EIO,而对于读操作它返回0。(超出块设备读也是不允许的,但不返回错误代码,内核可以对块设备的最后数据试着发出读请求,而得到的缓冲区页只被部分映射)。
124行设置缓冲区首部的b_dev字段为b_dev。125行设置缓冲区首部的b_blocknr字段为文件块号,它将被作为参数传给本函数。最后126行把缓冲区首部的BH_Mapped标志置位,以表明缓冲区首部的b_dev和b_blocknr字段是有效的。
回到函数block_read_full_page(),这个函数以一次读一块的方式读一页数据。正如我们已看到的当读块设备文件或磁盘上块不相邻的普通文件时都使用该函数。它执行如下步骤:
首先2074行通过检查页描述符的标志PG_private,如果置位,则说明该页与描述组成该页的块的缓冲区首部链表相关;否则,调用create_empty_buffers()来为该页所含所有块缓冲区分配缓冲区首部。页中第一个缓冲区的缓冲区首部地址存放在page->private字段中。每个缓冲区首部的b_this_page字段指向该页中下一个缓冲区的缓冲区首部:
void create_empty_buffers(struct page *page, unsigned long blocksize, unsigned long b_state) { struct buffer_head *bh, *head, *tail;
head = alloc_page_buffers(page, blocksize, 1); bh = head; do { bh->b_state |= b_state; tail = bh; bh = bh->b_this_page; } while (bh); tail->b_this_page = head;
spin_lock(&page->mapping->private_lock); if (PageUptodate(page) || PageDirty(page)) { bh = head; do { if (PageDirty(page)) set_buffer_dirty(bh); if (PageUptodate(page)) set_buffer_uptodate(bh); bh = bh->b_this_page; } while (bh != head); } attach_page_buffers(page, head); spin_unlock(&page->mapping->private_lock); } |
2078行,从相对于页的文件偏移量(page->index字段)计算出页中第一块的文件块号。2084行进入一个循环,对该页中每个缓冲区的缓冲区首部,执行如下子步骤:
a) 2085行如果标志BH_Uptodate置位,则跳过该缓冲区继续处理该页的下一个缓冲区。
b) 2088行如果标志BH_Mapped未置位,并且该块未超出文件尾,则调用作为参数传递进来的blkdev_get_block函数。对于块设备文件,不同的是blkdev_get_block函数把文件块号当作逻辑块号。不管是普通文件还是块设备文件,函数都将逻辑块号存放在相应缓冲区首部的b_blocknr字段中,并将标志BH_Mapped置位(访问普通文件时,如果一个数据块处于“文件洞”中,get_block函数就可能找不到这个块。此时,函数用0填充这个块缓冲区并设置缓冲区首部的BH_Uptodate标志。)。
c) 2098行再检查标志BH_Uptodate,因为依赖于文件系统的get_block函数可能已触发块I/O操作而更新了缓冲区。如果BH_Uptodate置位,则继续处理该页的下一个缓冲区。
d) 2114行将缓冲区首部的地址存放在局部数组arr中,继续该页的下一个缓冲区。
2117行假如上一步中没遇到“文件洞”,则2118行将该页的标志PG_mappedtodisk置位。
现在局部变量arr中存放了一些缓冲区首部的地址,与其对应的缓冲区的内容不是最新的。如果数组,即2120行判断nr为0为空,那么页中的所有缓冲区都是有效的,因此,该函数设置页描述符的PG_uptodate标志,调用unlock_page()对该页解锁并返回。
局部数组arr非空。对数组中的每个缓冲区首部,block_read_full_page()再执行下列子步骤:
a) 2134行将BH_Lock标志置位。该标志一旦置位,函数将一直等到该缓冲区释放。
b) 2135行将缓冲区首部的b_end_io字段设为end_buffer_async_read()函数的地址(见下面),并将缓冲区首部的BH_Async_Read标志置位。
2143-2149行对局部数组arr中的每个缓冲区首部调用submit_bh(),将操作类型设为READ。就像我们在前面看到的那样,该函数触发了相应块的I/O数据传输。
函数end_buffer_async_read()是缓冲区首部的完成方法。对块缓冲区的I/O数据传输一结束,它就执行。假定没有I/O错误,函数将缓冲区首部的BH_Uptodate标志置位而将BH_Async_Read标志清0。那么,函数就得到包含块缓冲区的缓冲区页描述符(它的地址存放在缓冲区首部的b_page字段中),同时检查是否页中所有块是最新的;如果是,函数将该页的PG_uptodate标志置位并调用unlock_page():
static void end_buffer_async_read(struct buffer_head *bh, int uptodate) { unsigned long flags; struct buffer_head *first; struct buffer_head *tmp; struct page *page; int page_uptodate = 1;
BUG_ON(!buffer_async_read(bh));
page = bh->b_page; if (uptodate) { set_buffer_uptodate(bh); } else { clear_buffer_uptodate(bh); if (printk_ratelimit()) buffer_io_error(bh); SetPageError(page); }
/* * Be _very_ careful from here on. Bad things can happen if * two buffer heads end IO at almost the same time and both * decide that the page is now completely done. */ first = page_buffers(page); local_irq_save(flags); bit_spin_lock(BH_Uptodate_Lock, &first->b_state); clear_buffer_async_read(bh); unlock_buffer(bh); tmp = bh; do { if (!buffer_uptodate(tmp)) page_uptodate = 0; if (buffer_async_read(tmp)) { BUG_ON(!buffer_locked(tmp)); goto still_busy; } tmp = tmp->b_this_page; } while (tmp != bh); bit_spin_unlock(BH_Uptodate_Lock, &first->b_state); local_irq_restore(flags);
/* * If none of the buffers had errors and they are all * uptodate then we can set the page uptodate. */ if (page_uptodate && !PageError(page)) SetPageUptodate(page); unlock_page(page); return;
still_busy: bit_spin_unlock(BH_Uptodate_Lock, &first->b_state); local_irq_restore(flags); return; } |