操作系统文件读取


系统调用

操作系统的主要工作是为管理硬件资源和为应用程序开发人员提供友好的环境;处理器有两种模式:”用户模式“与”内核模式“;一些容易发生安全问题的操作都被限制在只有内核模式下才可以执行,例如IO操作,修改基址寄存器内容等,而链接用户模式与内核模式的接口称之为系统调用;

应用程序代码运行在用户模式下,当应用程序需要实现内核模式下的指令时,需要先向操作系统发送调用请求,操作系统收到请求后,执行系统调用接口,使处理器进入内核模式,当处理器处理完系统调用操作后,操作系统会让处理器返回用户模式,继续执行用户代码;

进程的虚拟地址空间可分为内核空间和用户空间:内核空间中存放的是内核代码和数据,用户空间存放的是用户程序的代码和数据;不管是内核空间还是用户空间,他们都处于虚拟空间中,都是对物理地址的映射;

用用程序中实现对文件的操作过程就是典型的系统调用过程;

虚拟文件系统

一个应用操作系统(如windows、Linux)可以支持多种不同的底层文件系统(如FAT、NTFS、ext3、ext4、xfs等),为了给内核和用户进程提供统一的文件系统视图,下文以Linux系统为例;

Linux在用户进程和底层文件系统之间加入了一个抽象层,即虚拟文件系统(Virtual File System,简称VFS),进程所有的文件操作都通过VFS,由VFS来适配各种底层不同的文件系统,完成实际的文件操作;

通俗来说,VFS就是定义了一个通用文件系统的接口层和适配层,一方面为用户进程提供了一组统一的访问文件、目录和其他对象的统一方法,另一方面又要和不同的底层文件系统进行适配;

虚拟文件系统主要模块

  1. 超级块(super_block):用于保存一个文件系统的所有元数据,相当于这个文件系统的信息库,为其它模块提供信息;因此一个超级块可以代表一个文件系统;文件系统的任意元数据修改都要修改超级块;超级块对象是常驻内存并被缓存的;
  2. 目录项模块:用于管理路径的目录项,比如一个路径/home/semon/hello.txt,那么目录项中包含homesemonhello.txt;目录项的块,存储的是这个目录下的所有文件的inode号和文件名等信息,其内部是树形结构,操作系统检索一个文件,都是从根目录开始,按层次解析路径中的所有目录,直到定位到文件;
  3. inode模块:管理一个具体的文件,是文件的唯一标识,一个文件对应一个inode;通过inode可以方便的找到文件在磁盘扇区的位置;同时inode模块可以链接到address_space模块,方便查找自身数据是否已经缓存;
  4. 打开文件列表模块:该模块包含所有内核已经打开的文件;已经打开的文件对象由open系统调用在内核中创建,也叫文件句柄;打开文件列表模块中包含一个列表,每个列表表项是一个结构体struct file,结构体中的信息用来标示打开一个文件的各种状态参数;
  5. file_operations模块:该模块维护一个数据结构,是一系列函数指针的集合,其中包含所有可以使用的系统调用函数,如openreadwritenmap等;每个打开文件都可以链接到file_operations模块,从而对任何已打开文件,通过系统调用函数,实现各种操作;
  6. address_space模块:它表示一个文件在页缓存中已经缓存了的物理页,它是页缓存和外部设备中文件系统的桥梁;如果将文件系统理解为数据源,那么address_space可以说关联了内存系统和文件系统;
image-20210811114344512

由上图可以看出:

  1. 每个模块都维护了一个X_op指针指向它所对应的操作对象X_operations
  2. 超级块维护了一个s_files指针指向已打开文件列表模块,即内核所有的打开文件的链表,这个链表信息是所有进程共享的;
  3. 目录操作模块和inode模块多维护了一个X_sb指针指向超级块,从而可以获取整个文件系统的元数据信息;
  4. 目录项对象和inode对象各自维护了指向对方的指针,可以找到对方的数据;
  5. 已打开文件列表上每一个file结构体实例维护了一个f_entry指针,指向了它对应的目录项,从而可以根据目录项找到它对应的indoe信息;
  6. 已打开文件列表上每一个file结构体实例还维护了一个f_op指针,指向可以对这个文件进行操作的所有函数集合file_operations
  7. inode中不仅有和其它模块关联的指针,更重要的是它可以指向``address_space`模块,从而获取自身文件在内存中的缓存信息;
  8. address_space内部维护了一个树结构来指向所有的物理页结构page,同时维护了一个host指针指向inode来获取文件的元数据;

进程与VFS交互

  1. 内核使用task_struct来表示单个进程的描述符,其中包含维护一个进程的所有信息;task_struct结构体中维护了一个files指针来指向结构体files_structfiles_struct中包含文件描述符表和打开的文件对象信息;
  2. file_struct中的文件描述符表实际是一个file类型的指针列表,可以支持动态扩展,每一个指针指向虚拟文件系统中文件列表模块的某一个打开的文件;
  3. file结构一方面可以从f_dentry连接到目录项模块以及inode模块获取所有和文件相关的信息,另一方面链接file_operations子模块,其中包含所有可以使用的系统调用函数,从而最终完成对文件的操作;这样,从进程到进程的文件描述符表,再关联到已打开文件列表上对应的文件结构,从而调用其可执行的系统调用函数,实现对文件的各种操作;

进程/文件列表/Inode

  • 多个进程可以同时指向一个打开文件对象(文件列表表项),如父进程与子进程间共享文件对象;
  • 一个进程多次打开同一个文件,生成不同的文件描述符,每个文件描述符指向不同的文件列表表项,但这些文件列表表项指向同一个inode;但是由于是同一个文件,inode唯一

IO缓冲区

概念

IO缓冲区与高速缓存(cache)产生的原理类似,在IO过程中,磁盘读取速度相对内存读取速度要慢得多,为了能够加快数据处理速度,需要将已读取的数据缓存在内存中,这些缓存在内存中的数据就是高速缓冲区(buffer cache),简称buffer;具体来说,buffer是一个用于存储速度不同的设别或优先级不同的设备之间传输数据的区域;一方面,通过缓冲区,可以使进程间的相互等待变少,从而使从速度慢的设备读入数据时,速度快的设备操作进程不发生间断;另一方面,可以保护硬盘或减少网络IO的次数;

Buffer与Cache

buffercache是两个不同的概念:

  • cache是高速缓存,用于CPU和内存之间的缓冲;
  • buffer是IO缓冲,用于内存和硬盘的缓冲;

简单来说,cache是为了加速“读”,而buffer是缓冲“写”,前者解决读的问题,保存从磁盘上读出的数据,后者解决写问题,保存即将要写入到磁盘上的数据;

Buffer Cache与Page Cache

buffer cachepage cache都是为了处理设备与内存交互式高速访问问题;buffer cache称为块缓冲器,page cache称为页缓冲器;在Linux不支持虚拟内存机制前,没有页的概念,因此缓冲区以块为单位对设备进行;在Linux采用虚拟内存的机制来管理内存后,页是虚拟内存管理的最小单位,开始采用页缓冲机制来缓冲内存;Linux2.6之后内核将块缓冲器与页缓冲器中和,页与块可以相互映射,同时,页缓存面向的是虚拟内存,块缓存是面向块设备;需要强调的是,页缓存和块缓存对进程来说是同一个存储系统,进程不需要关注底层设备的读写;

buffer cachepage cache两者最大的区别是缓存的粒度;buffer cache面向的是文件系统的块,page cache处理性能更高,内核的内存管理组件采用了比文件系统的块更高级的抽象,因此和内存管理交互的缓存组件,都使用页缓存;

Page Cache

页缓存是面向文件、面向内存的,通俗来说,它位于内存和文件之间缓冲区,文件IO操作实际上只和page cache交互,不直接与内存交互;page cache可以用在所有以文件为单元的场景下,比如网络文件系统等等;page cache通过一系列数据结构,比如inodeaddress_spacestruct page,实现将一个文件映射到页的级别:

  1. struct page结构标志一个物理内存页,通过page + offset就可以将此页帧定位到一个文件中的具体位置;struct page具备以下重要参数:
    1. 标志位flags记录该页是否脏页,是否正在被写回等等;
    2. mapping指向地址空间address_space,表示这个页是一个页缓冲中页,与一个文件的地址空间对应;
    3. index记录这个页在文件中的页偏移量;
  2. 文件系统的inode实际维护了这个文件所有块(block)的块号,通过对文件偏移量offset取模可以很快定位到这个偏移量所在的文件系统的块号,磁盘的扇区号;同样,通过对文件偏移量offset进行取模可以计算出偏移量所在的页的偏移量;
  3. page cache缓存组件抽象了地址空间address_space这个概念来作为文件系统和页缓存的中间桥梁;地址空间address_space通过指针可以方便的获取文件inodestruct page的信息,可以方便地定位到一个文件的offset在各个组件中的位置,即通过:文件字节偏移量–>页偏移量–>文件系统块号–>磁盘扇区号;
  4. 页缓存实际上就是采用了一个基数树结构讲一个文件的内容组织起来存放在物理内存struct page中;一个文件inode对应一个地址空间address_space,而一个address_space对应一个页缓存基数树;

Address Space

Address Space是Linux内核中的一个关键抽象,它被作为文件系统和页缓存的中间适配器,用来指示一个文件在页缓存中已经缓存了的物理页;


文章作者: semon
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 semon !
评论
  目录