分类: LINUX
2012-05-26 21:14:49
对于文件的操作进行最多的就是文件的读写操作。因此上有必要了解文件的读写执行过程。
文件的读:
文件的读操作以页为单位进行。内核每次会传送几页(文件的预读)。用户发出read()系统调用后,内核先查看要操作的文件是否在缓存中,如果在缓存之中就给进程的用户空间拷贝一份,若所要读的文件的页不在缓冲区,则会在换中区中分配一个页框,然后把相应的页框加入到页高速缓存之中,然后从磁盘读取相应的页到缓冲区并给进程的用户空间拷贝一份。以上就是一个文件的读操作的大体流程。
接下来我们可以看看内核是如何处理文件的读操作的。其主要步骤如下:(2.6.18内核)
1、通过系统调用进入内核,然后获取相应文件的文件对象。
2、在获得文件对象之后,就要获取文件当前的偏移量。
3、调用vfs_read()函数,其主要做如下处理:
检查文件的访问权限;
检查文件对象操作是否定义了file->f_op_read()或file->f_op->aio_read();
然后调用access_ok()对文件参数进行粗略验证;
调用rw_rerify_area()检查文件是否有冲突的强制锁;
如果内核定义了读函数则调用,否则调用通用读的处理函数generic_file_read()实现文件的读操作。
4、更新文件的位置
5、释放文件对象。
接下来我们主要对genneric_file_read()进行学习。
genneric_file_read()原型如下:
generic_file_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
其中filp为文件对象的地址;
buf为文件要存放的线性区;
count为要读取的文件的字符个数;
ppos中存放读操作开始出文件偏移量。
首先函数中涉及两个结构体iovec和kiocb,接着对两个结构体初始化,其中iovec中定义的两个字段:iov_base指向一个用户地址空间的缓冲区,而iov_len则定义了文件的读取长度。其原型如下:
接着kiocb用于跟踪运行的同步或异步I/O操作的完成状态。然后调用了__generic_file_aio_read(),__generic_file_aio_read()是所有文件系统实现同步和异步操作的通用例程。这个函数的读就使用到了页高速缓存。所谓页高速缓存就是一种对数据页进行操作的磁盘高速缓存,几乎所有的文件的读写都使用到了文件的高速缓存。有一种例外就是直接I/O传送(用户态地址空间中的页与磁盘之间直接传送数据)时绕过页高速缓存。对于页高速缓存来说比较重要的数据结构是address_space对象。它实现了磁盘文件与缓冲区中页的一种链接关系,且在对缓冲区进行管理时使用到了基树,基树的使用可以快速的实现数据页的查找。
上述函数主要做了如下工作:
1、对传入的数据长度进行判断
2、实现了对通用块层的操作
3、在缓冲区长度不为0的情况下,获取一个读操作描述符:read_descriptor_t,其用来存放与单个用户缓冲相关的文件读操作的当前状态。
4、接着调用了do_generic_file_read()函数
do_generic_file_read()函数的实现又调用了 do_generic_mapping_read()函数。而do_generic_mapping_read()函数的实现过程才真正使用到了缓存。
其中mmap指向“地址空间”。
_ra则是文件的预读状态
文件的预读是一种技术。它是在请求前读取普通文件或块设备文件的相邻数页。预读可以极大的提高磁盘的性能,使得在顺序读取文件时不需要等待请求的数据。但是预读对于随机访问文件来说是没有用的。
filp:则是要读取的文件
ppos:当前文件的位置
desc:读描述符
actor:读取的方法
这个函数的主要操作有通过文件的偏移量获得缓存中的索引inode。然后通过一个循环实现文件的读取。
1、判断inode是否越界
2、调用cond_resched()检查当前进程的标志。
3、如果有预读则调用page_cache_readahead()进行预读。
4、然后主要是判断缓存中是否存在要查找的页,如果不在缓存则要先进行页框分配然后再加入到页高速缓存中。
5、真正读操作的实现要调用readpage,而readpage通常不会直接编写代码实现,而是通过内核的标准函数执行。如mpage_readpage和mpage_readpages等。它可以有效的激活从物理磁盘到页高速缓存的I/O数据传输。
最后就是对相关文件的更新操作等。
具体的读操作所经过如下处理层次: