进程读写文件过程
流程数据结构示意图
- 初始情况我们先忽视task_struct2和task_struct3所附带的关系
- 基本的流程是每一个进程的task_struct 中有一个对象 files_struct 专门存储files相关的信息,包括熟知的fd文件描述符等。我们调用系统调用返回的int类型的fd即指向的fd_array数组中存放的实际的 file 对象,这个对象相当于我们和实际的文件实体 inode 的一个消息传递者,也就是存在在多进程同时操作一个实际文件实体(task_struct和task_struct3)。
基本数据结构简述
- struct files_struct *files 进程中使用这个对象来管理所有的文件
- struct fdtable fdtab 管理进程内的fd位图索引,能够指向到fd_array中实际的file结构
- struct file *fd_array[NR_OPEN_DEFAULT]; 存储的进程所关联的所有file结构的数组对象
- file对象存在引用,如果存在一个fd指向它,那么引用计数会增加,情况所属是fork一个子进程时fd都是指向同一个file
- dentry对象是一个目录项,由操作系统维护,利用的是LRU的cache,file寻找一个inode需要经过它去寻找。存在引用,如果一个file指向它,那么引用计数会增加,所属情况是多进程同时打开同一个文件
- inode对象是操作系统对磁盘上的文件系统进行缓存映射,对于具体的数据block缓存在address_space对象的缓存空间的,也就是内核的IO缓存区
- 上述对象都有对应的特定的operator操作
进程打开读写文件流程简述
进程打开文件
- 根据目录名称去调用dentry_cache是否存在对应的目录项,如果有需要对权限进行判断,如果没有则需要建立inode等对象生成新的dentry
- 进程是否有对该inode的权限匹配
- 创建新的file结构加入到task_struct的files中
进程读写文件
- 对于VFS是经过内核的缓冲区的(除了直接IO等操作,不经过内核的缓冲)
- inode对象存放的address_space即它所对应的缓存区,缓冲区由一颗radix的树构成,单位是按页进行缓冲
- 正常系统的读写文件是指,调用系统调用read()和write(),只经过内核一个缓冲区,如果调用C库那么存在用户空间还多了一个自带的缓存区。
不带用户空间缓存的读写文件
读文件
- inode对象计算需要的页数
- address_space的树中判断是否存在制定的页,否则找到磁盘制定页写入缓存
- 将缓存复制拷贝到用户空间中
写文件
- inode对象计算需要的页数
- address_space的树中判断是否存在制定的页,否则找到磁盘制定页写入缓存
- 将用户空间的内容写入到缓存中
- 落盘根据操作系统的脏页读写规则有关
调用C库使得用户空间存在缓存的IO读写文件
- 涉及的函数都带有f前缀,例如 fopen,fread,fwrite,fget,fput,fgets,fputs,fscanf,fprintf 等等
- 目的降低内核空间和用户空间的切换,减少read和write函数的调用次数
- 用户空间的缓存存在全缓存和行缓存,简单来说前者是缓存空间占满再调用系统调用,后者是按照行来标示调用系统调用
子进程或者多进程调用的文件关系
- 父进程fork操作使得子进程所带的struct_file对象内容一致,这意味着file对象都一致,共同使用偏移量等
- 多进程共同打开同一个文件时,根据上述情况,它们公用的是inode对象但是各自的file对象不一致
- 解决方式是例如使用O_APPEND类似标志或者是pwrite等使得lseek和write函数得到原子性操作,即获取当前偏移量以及写操作两者是一个事务
mmap函数
- 简单来说就是将IO实际内容映射到用户空间,不经过内核的缓冲区
- 具体流程如下:
- 进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域
- 调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系
- 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
参考:
linux系统编程之基础必备(三):文件描述符file descriptor与inode的相关知识
inode and file descriptor table Interaction