这段时间想弄懂mmc驱动的读写原理,一直在goldfish的驱动代码中乱撞。某天突然灵光一闪,要从文件系统开始分析。眼前一篇豁然开朗。跟踪过程记录如下:
首先是file的读操作。因为sd卡是fat32文件系统,找到fat32的file_operations:
const struct file_operations fat_file_operations = {
.llseek = generic_file_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = generic_file_aio_read,
.aio_write = generic_file_aio_write,
.mmap = generic_file_mmap,
……
}
read操作先调用do_sync_read,然后在do_sync_read中调用generic_file_aio_read。
generic_file_aio_read中先判断是否可以direct to BIO。如果不能,进入读取页面的循环,这个循环中主要的函数是:
do_generic_file_read(filp, ppos, &desc, file_read_actor);
这个函数也是读文件主要的操作函数。
进入do_generic_file_read,首先在缓存中找要读的页面。查找结果有三种情况:1.找到了,而且内容一致。2.找到了,但是内容不一致。3.没找到该缓存页面。
对于第一种情况,跳转到page_ok,直接复制缓存页面中的内容。
第二种情况,跳转到page_not_up_to_date,等待一会,在次检查。如果还不行,就readpage。
第三种情况,直接跳转到readpage,开始从设备读页面:
/* Start the actual read. The read will unlock the page. */
error = mapping->a_ops->readpage(filp, page);
if(unlikely(error)){
……
具体的读取函数由a_ops的readpage指定,在fat32文件系统中就是fat_readpage,fat32 a_ops的成员如下:
static const struct address_space_operations fat_aops = {
.readpage = fat_readpage,
.readpages = fat_readpages,
.writepage = fat_writepage,
.writepages = fat_writepages,
.sync_page = block_sync_page,
.write_begin = fat_write_begin,
.write_end = fat_write_end,
.direct_IO = fat_direct_IO,
.bmap = _fat_bmap
};
文件系统在操作设备的时候,有一个预读取的动作。一般我们需要的数据是通过预读取读进内存的,这个时候调用的就是fat_readpages操作函数。
再来看fat_readpages函数,它直接返回mpage_readpages函数:
static int fat_readpages(struct file *file, struct address_space *mapping,
struct list_head *pages, unsignednr_pages)
{
return mpage_readpages(mapping, pages, nr_pages, fat_get_block);
}
注意他的最后一个参数fat_get_block是一个函数指针。他的作用是建立从文件块号到设备扇区号的映射。
函数mpage_readpages做了一些初始化,然后循环调用do_mpage_readpage来读取每一个页面。
bio = do_mpage_readpage(bio, page,
nr_pages - page_idx,
&last_block_in_bio, &map_bh,
&first_logical_block,
get_block);
do_mpage_readpage函数比较长。具体的也没分析,就看两个重要的地方。
一个是get_block的调用,前面已近提到过了,是为了得到要读取数据在设备上的块号。
一个是提交io请求: bio = mpage_bio_submit(READ, bio);这个函数将要读写命令交给块设备驱动来处理。这样,就完成了从文件系统层到块设备驱动层的转换。
块设备驱动层的操作下篇分析。