1.概述
文件io是通过系统调用实现的。每次调用read和write都会触发一次系统调用。
2.read
不论是java的FileInputStream,还是标准c的fread,最终都是调用的系统调用接口read。
ssize_t read(int filedes,void *buf,size_nbytes);
参数分别为文件描述符地址,返回内容的存放地址和请求的字节数,返回值为实际读取字节数(如果到达文件结尾返回0,出错返回-1)
read函数的调用过程如下:
read------> sys_read()------->generic_file_read()------->__generic_file_aio_read()------->do_generic_file_read()------>readpage();
其中read到sys_read为系统调用,从用户态切换到内核态
介绍下这几个函数的主要内容:
1.sys_read():
- 根据fd取得file对象(file对象是这个进程和这个文件关联的对象,包括inode的指针,偏移量等)
- 根据file对象检查读写权限,验证参数,检查文件锁,都通过则调用file->f_op->read
2.generic_file_read(filp,buf,count,ppos)
- 参数包括flip:file对象指针,buf存放返回数据的地址,count要读取的字节数,ppos偏移量
- 封装了kiocb描述符,iovec描述符,调用__generic_file_aio_read
3.__generic_file_aio_read(kiocb,iovec[],length,ppos)
-
验证一些参数
-
建立一个读操作描述符.
-
调用do_generic_file_read()
4.do_generic_file_read()
- 通过file对象的f_mapping指针获得address_space对象.(address_space是一个文件的页高速缓存核心数据结构,它有一个指向索引节点对的指针host,同时也是索引节点对象的一个属性)
- 用要读取的数据的偏移量ppos算出读取的数据在高速缓存中的index(*ppos/4096).通过这个index可以在address_space中找到对应的页(page)
- 传入计算出的index和ppos在第一页的页内偏移量,开始循环读取所有的page(直到读满所需字节数或者到文件尾)
- 如果需要预读,就调用page_cache_readahead()读取。
- 调用find_get_page(*address_space, index)查找是否在页高速缓存中(一个文件的多个page在页高速缓存中以树的方式存在,方便查找)
- 如果命中,则将返回数据拷贝到用户缓冲区(buf),(通过调用__copy_to_user(),如果用户态内存页不存在可能触发缺页异常来分配页),这里省略了一些分支逻辑。
- 如果不命中,调用address_space的readpage从硬盘把数据读入页高速缓存,再考到用户缓冲区,继续循环。
- 更新file对象的偏移量,返回结果。
5.readpage
- 通过address_space的host指针找到inode对象,通过inode对象算出页中的块数(一块可能是1024或者其他)以及第一块的文件块号。
- 调用get_block取得逻辑块号,调用bio_alloc()创建一个bio描述符,然后调用submit_bio(),提交io请求到io队列。阻塞线程等待读取完成的回调。
总的来说就是先从缓存取,取不到就读硬盘,拷入缓存,再从缓存读取。
3.write