读数据时,进行read函数调用,进入陷阱门调用到函数:
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count) // (/fs/read_write.c)
--> ret = __vfs_read(file, buf, count, pos);
-->if (file->f_op->read)
return file->f_op->read(file, buf, count, pos);
else if (file->f_op->read_iter)
return new_sync_read(file, buf, count, pos);
在linux3.16后文件系统的file_operations操作一般已经不使用操作read函数操作了,可以进入到源码/fs下面的文件系统的file.c里面看下。
分析new_sync_read –>
三个数据结构:
struct iovec iov = { .iov_base = buf, .iov_len = len };
//存放用户空间传入的数据和长度
struct kiocb kiocb;
// kiocb --> kernel i/o control block
struct iov_iter iter;
// struct iovec 的迭代器
迭代器原理 : https://lwn.net/Articles/625077/
内核空间和用户空间之间的数据传输,需要解除隔离操作
get_fs()和set_fs()解析 :
http://blog.chinaunix.net/uid-24237502-id-35023.html
ret = filp->f_op->read_iter(&kiocb, &iter);
-->generic_file_read_iter
分为DIRECT 和 使用缓存
ps: DIRECT,使用方式为open时使用O_DIRECT, 他与使用缓存的区别就是他不使用内核提供的缓存模式,绕过缓存模式,直接进行磁盘的读写操作。有特殊需要的可以使用这种模式进行操作。
分析缓存方式:
do_generic_file_read(file, &iocb->ki_pos, iter, retval)
ps :struct file_ra_state *_ra,预读参数,预读窗口大小,命中计数器,控制标志等参数
页高速缓存是linux内核实现的一种主要磁盘缓存,它主要用来减少对磁盘的IO操作,具体地讲,是通过把磁盘中的数据缓存到物理内存中,把对磁盘的访问变为对物理内存的访问,从而加快读写速度。
每个inode都有一个address_space对象,由该address_space来维护该inode所拥有的所有页缓存,即每个inode都需要一个address_space对象,每个inode维护一个基树。
如果假设系统维护一个大的基树,然后每个inode的基树都挂在上面,会有锁竞争,查找耗时等问题。
基数树分析:
http://blog.youkuaiyun.com/lonewolfxw/article/details/7953805
当我们读取一个文件的时间,首先会检查数据是否已经缓存,如果没有就会预读取,将数据加载入缓存页。
do_generic_file_read
--> page = find_get_page(mapping, index);
查找不到情况下:
page = __page_cache_alloc(gfp_mask);
add_to_page_cache_lru(page, mapping, offset, gfp_mask & GFP_RECLAIM_MASK)
这里写代码片最近最少使用(LRU)块高速缓存
(LRU的使用牵涉到缓存的回收处理)
LRU原理分析:http://flychao88.iteye.com/blog/1977653
LRU程序分析:http://blog.youkuaiyun.com/yunsongice/article/details/5850807
缓存页预读取
page_cache_sync_readahead
预读取过程代码分析:http://blog.youkuaiyun.com/vah101/article/details/7709820
最后调用
mapping->a_ops->readpages
或者
mapping->a_ops->readpage
进行读取操作
回到do_generic_file_read里下面调用copy_page_to_iter
从缓存页中拷贝数据到iter里
大概的画了一点流程图:
总结:读取文件过程的时候,先通过基数树去检查数据是否已经缓存到缓存页中了,如果没有则执行预读取过程将数据进行缓存处理,缓存完成后直接在缓存页中数据读取,如果有则直接读取。