JJJ-Linux file read

Linux file read
发表于 2020-03-26 更新于 2020-08-20 所属分类 linux 阅读次数: Valine: 0
本文基于linux kernel 3.19.8版本梳理一下读文件的过程,从vfs开始,到发送请求给block层结束。

普通模式 (1)
这里的普通是指没有设置O_DIRECT(也没设置O_SYNC,但它只对write起作用,read肯定是同步的,所以不提)。

vfs_read()
这个函数接收4个参数:

struct file *file:读取的目标文件;
char __user *buf:用户态buffer的地址;
size_t count:用户态buffer的大小;
loff_t *pos:目标文件中读取的起始位置;
首先做一些检查,包括:

读权限检查,FMODE_READ和FMODE_CAN_READ;
用户态指针检查,即检查应用程序提供的buffer是否合法。注意access_ok(VERIFY_WRITE, buf, count)里面的VERIFY_WRITE是从buffer的角度出发的,读完数据要往buffer里写嘛。
调用rw_verify_area检查:1. 参数检查,*pos是否小于0,*pos+count是否小于0;2. 是否有mandatory lock冲突;3. security permission;
若这些检查都通过,则调用__vfs_read(),把自己的4个参数传过去。

__vfs_read()
这个函数从vfs_read()接收那4个参数,直接传入filesystem相关的函数file->f_op->read。这是一个函数指针,对于ext4和xfs来说它都指向new_sync_read。

new_sync_read()
到现在,我们手上还是那4个参数(只是参数名略不同):

struct file *filp;
char __user *buf;
size_t len;
loff_t *ppos;
这个函数用这4个参数,初始化3个结构体:

struct iovec iov = { .iov_base = buf, .iov_len = len };:即用户态buffer;
struct iov_iter iter;:指向用户态buffer的迭代器,用于fill用户态buffer;
struct kiocb kiocb;:这个结构体对于同步read和libaio有不同用法,我们当前讨论的是同步read,主要用到里面的这5个字段:ki_ctx=NULL; ki_filp=filp;ki_obj.tsk=current; ki_pos=*ppos; ki_nbytes=len;其中ki_ctx=NULL表明这是一个同步read而不是libaio;
初始化完成之后,再次调用filesystem相关的函数filp->f_op->read_iter,传入两个参数:&kiocb和&iter,前者代表从哪里读;后者代表读到的输入放到哪里。filp->f_op->read_iter对于ext4指向的是generic_file_read_iter;对于xfs指向的是xfs_file_read_iter。xfs_file_read_iter做一些处理之后也最终调用generic_file_read_iter,所以我们看这个函数。

generic_file_read_iter()
如前所述,这个函数两个参数是struct kiocb *iocb和struct iov_iter *iter。它从iocb中取出:

struct file *file = iocb->ki_filp;
loff_t *ppos = &iocb->ki_pos;
如果O_DIRECT没有设置(正是我们讨论的case),直接调用do_generic_file_read,传入file, ppos, iter和retval(值为0)。

do_generic_file_read()
这个函数就比较复杂了。它从前面generic_file_read_iter()得到的参数是:

struct file *filp:目标文件;
loff_t *ppos:目标文件中读取的起始位置;
struct iov_iter *iter:指向用户态buffer;
ssize_t written:值为0;
首先是初始化一些变量:

struct address_space *mapping = filp->f_mapping;:mapping里面的page_tree就是pagache;
struct inode *inode = mapping->host;: mapping的owner inode。1. 对于普通文件来说,就是filp->f_dentry->d_inode;2. 对于block device file来说,就是bdev special filesystem里面的inode;filp就是/dev/sdc这样的文件,所属文件系统是devtmpfs;这个文件只是bdev special filesystem里面的文件的”指针”;bdev special filesystem没有mount,所以不可见。”指针”是这样实现的:devtmpfs文件系统中的inode有两个字段:i_bdev和i_rdev;其中i_bdev是一个指向struct block_device的指针,而struct block_device嵌套在struct bdev_inode之内(可以通过struct block_device的地址得到外面struct bdev_inode的地址)。struct bdev_inode是什么呢?它就是bdev special filesystem内的inode。所以说,可以认为devtmpfs文件系统中的inode的i_bdev指向bdev special filesystem内的inode。当这个i_bdev为NULL的时候,就用到i_rdev,它是主设备号和从设备号,可以通过它去打开struct block_device,然后把返回的地址存入i_bdev。见blkdev_open()函数。所以说,devtmpfs文件系统中的文件是bdev special filesystem里面的文件的”指针”或”快捷方式”。这里拿到的inode就是bdev special filesystem里的inode,它是pagacache的owner,也是服务读请求的主体。
index:把文件看做是一个连续的page(通常是4KiB)数组,index就是read请求的第1字节所在的page号,也就是这个page在pagecache中的位置;
last_index:read请求的最后1字节所在page的后面一个page的号(注意不是最后1字节所在的那个page的号),也就是那个page在pagecache中的位置。这样形成一个前闭后开的区间[index, last_index);
offset:read请求的第1字节在它所在的page内的偏移。虽然内核是按page从底层读数据的,但返回给用户必须精确到字节,offset就是用来标记从第1个page的哪个位置开始往iter(指向用户态buffer)拷贝数据。当read请求区间跨page时,对于后续的page,offset被更新为0,表示从page的开头开始拷贝。
还有read ahead相关的prev_index和prev_offset,暂且不看它们。
然后就开始一个大循环。假如read ahead完全没有起到作用,一次循环从block层读取一个page并拷贝到iter(用户态buffer),即page-by-page的读;否则,read ahead一下获取了很多page,一次循环只是拷贝一个page到iter(用户态buffer)。值得一提的是,read ahead完全起不到作用的情况极少发生,即使用户通过posix_fadvise设置了POSIX_FADV_RANDOM,比较新的内核也会走force_page_cache_readahead流程,详见read ahead;

首先还是先看一下page-by-page的读吧,虽然它极少发生。

调用find_get_page(mapping, index)在pagecache中找index指向的page;
没有找到,从而调用page_cache_sync_readahead(对于read ahead被完全禁止的情况,ra->ra_pages=0,所以这个函数什么都不做直接返回),然后再调用find_get_page;
当然还是找不到,从而goto no_cached_page;在这里分配一个内存page,添加到pagacache中,然后goto readpage;
在readpage处,调用mapping->a_ops->readpage;它是一个文件系统相关的函数指针,对于ext4来说,指向ext4_readpage。ext4_readpage再调用mpage_readpage,构造一个bio,发到block层。返回之后,goto page_ok;
在page_ok处,把page的数据拷贝到iter(用户态buffer)
continue进行下一轮循环;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值