函数 do_flip_open() 在执行的过程中会调用函数 nameidata_to_filp(),而 nameidata_to_filp() 最终会调用 __dentry_open() 函数,若进程指定了 O_DIRECT 标识符,则该函数会检查直接 I./O 操作是否可以作用于该文件 。下面列出了 __dentry_open() 函数中与直接 I/O 操作相关的代码 。
if (f->f_flags & O_DIRECT) {if (!f->f_mApping->a_ops ||((!f->f_mapping->a_ops->direct_IO) &&(!f->f_mapping->a_ops->get_xip_page))) {fput(f);f = ERR_PTR(-EINVAL);} }当文件打开时指定了 O_DIRECT 标识符,那么操作系统就会知道接下来对文件的读或者写操作都是要使用直接 I/O 方式的 。
下边我们来看一下当进程通过 read() 系统调用读取一个已经设置了 O_DIRECT 标识符的文件的时候,系统都做了哪些处理 。函数 read() 的原型如下所示:
ssize_t read(int feledes, void *buff, size_t nbytes) ;操作系统中处理 read() 函数的入口函数是 sys_read(),其主要的调用函数关系图如下:
sys_read()|-----vfs_read()|----generic_file_read()|----generic_file_aio_read()|--------- generic_file_direct_IO()?函数 sys_read() 从进程中获取文件描述符以及文件当前的操作位置后会调用 vfs_read() 函数去执行具体的操作过程,而 vfs_read() 函数最终是调用了 file 结构中的相关操作去完成文件的读操作,即调用了 generic_file_read() 函数,其代码如下所示:
ssize_t generic_file_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {struct iovec local_iov = { .iov_base = buf, .iov_len = count };struct kiocb kiocb;ssize_t ret;init_sync_kiocb(&kiocb, filp);ret = __generic_file_aio_read(&kiocb, &local_iov, 1, ppos);if (-EIOCBQUEUED == ret)ret = wait_on_sync_kiocb(&kiocb);return ret; }函数 generic_file_read() 初始化了 iovec 以及 kiocb 描述符 。描述符 iovec 主要是用于存放两个内容:用来接收所读取数据的用户地址空间缓冲区的地址和缓冲区的大小;描述符 kiocb 用来跟踪 I/O 操作的完成状态 。之后,函数 generic_file_read() 凋用函数 __generic_file_aio_read() 。该函数检查 iovec 中描述的用户地址空间缓冲区是否可用,接着检查访问模式,若访问模式描述符设置了 O_DIRECT,则执行与直接 I/O 相关的代码 。函数 __generic_file_aio_read() 中与直接 I/O 有关的代码如下所示:
if (filp->f_flags & O_DIRECT) {loff_t pos = *ppos, size;struct address_space *mapping;struct inode *inode;mapping = filp->f_mapping;inode = mapping->host;retval = 0;if (!count)goto out;size = i_size_read(inode);if (pos < size) {retval = generic_file_direct_IO(READ, iocb,iov, pos, nr_segs);if (retval > 0 && !is_sync_kiocb(iocb))retval = -EIOCBQUEUED;if (retval > 0)*ppos = pos + retval;}file_accessed(filp);goto out; }上边的代码段主要是检查了文件指针的值,文件的大小以及所请求读取的字节数目等,之后,该函数调用 generic_file_direct_io(),并将操作类型 READ,描述符 iocb,描述符 iovec,当前文件指针的值以及在描述符 io_vec 中指定的用户地址空间缓冲区的个数等值作为参数传给它 。当 generic_file_direct_io() 函数执行完成,函数 __generic_file_aio_read()会继续执行去完成后续操作:更新文件指针,设置访问文件 i 节点的时间戳;这些操作全部执行完成以后,函数返回 。函数 generic_file_direct_IO() 会用到五个参数,各参数的含义如下所示:
- rw:操作类型,可以是 READ 或者 WRITE
- iocb:指针,指向 kiocb 描述符
- iov:指针,指向 iovec 描述符数组
- offset:file 结构偏移量
- nr_segs:iov 数组中 iovec 的个数
static ssize_t generic_file_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov,loff_t offset, unsigned long nr_segs) {struct file *file = iocb->ki_filp;struct address_space *mapping = file->f_mapping;ssize_t retval;size_t write_len = 0;if (rw == WRITE) {write_len = iov_length(iov, nr_segs);if (mapping_mapped(mapping))unmap_mapping_range(mapping, offset, write_len, 0);}retval = filemap_write_and_wait(mapping);if (retval == 0) {retval = mapping->a_ops->direct_IO(rw, iocb, iov,offset, nr_segs);if (rw == WRITE && mapping->nrpages) {pgoff_t end = (offset + write_len - 1)>> PAGE_CACHE_SHIFT;int err = invalidate_inode_pages2_range(mapping,offset >> PAGE_CACHE_SHIFT, end);if (err)retval = err;}}return retval; }函数 generic_file_direct_IO() 对 WRITE 操作类型进行了一些特殊处理 。除此之外,它主要是调用了 direct_IO 方法去执行直接 I/O 的读或者写操作 。在进行直接 I/O 读操作之前,先将页缓存中的相关脏数据刷回到磁盘上去,这样做可以确保从磁盘上读到的是最新的数据 。这里的 direct_IO 方法最终会对应到 __blockdev_direct_IO() 函数上去 。__blockdev_direct_IO() 函数的代码如下所示:
推荐阅读
- Sql中Left Join、Right Join、Inner Join的区别
- 这些 Linux 指令你都掌握了吗
- phpstudy使用说明教程
- 在一个千万级的数据库查寻中,如何提高查询效率?
- 西红柿炒鸡蛋
- 恋爱中发现女朋友与前男友同居过 女友和前男友同居
- 中国版“苏格兰蛋”
- 美研签证拒签案例 美国拒签中国留学生
- 金骏眉制作流程 金骏眉制作工艺图
- 秋分时节谨防阴虚火旺 中医建议早睡早起
