最近一个项目做了一个模拟u盘的设备,但是在read虚拟u盘的内容时必须每次都从磁盘内读取,而不是从系统的cache中读取,由于这个问题,就查资料看了下read的系统调用,以及文件系统的一些内容。由于文件系统涉及面较广,例如虚拟文件系统(VFS),页缓存,块缓存,数据同步等内容,不可能全部分析到位,这里只记录和read有关的两种使用方式。cached IO和direct IO。
1. 什么是系统调用
首先系统调用能做那些事呢?概括来说,大概有下面这些事需要系统调用来实现。
- 控制硬件:系统调用往往作为硬件资源和用户空间的抽象接口,比如读写文件时用到的write/read调用。
- 设置系统状态或读取内核数据:因为系统调用是用户空间和内核的唯一通讯手段,所以用户设置系统状态,比如开/关某项内核服务(设置某个内核变量),或读取内核数据都必须通过系统调用。比如getpgid、getpriority、setpriority、sethostname
- 进程管理:用来保证系统中进程能以多任务在虚拟内存环境下得以运行。比如 fork、clone、execve、exit等
那为什么一定要用系统调用来访问操作系统的内容呢,其实这可以看做对内核的保护,linux分为用户空间和内核空间,而用户空间是不允许访问内核空间的数据的。那么在用户空间的程序需要访问内核空间的资源时就必须通过系统调用这个中间人来实现。这样可以对用户空间的行为进行限制,只有特定的得到许可的(事先规定的)用户空间行为才能进入内核空间。一句话,系统调用是内核给用户空间提供的一个可以访问内核资源的一个接口。
另外多说一句,从用户进程切换到内核进程只有两种方式,一种就是系统调用,另一种是中断。
要实现系统调用,首先要能从用户空间切换到内核空间,这个切换在IA-32系统上是用汇编指令int $0x80来引发软件中断实现的。这部分内容一般是在C标准库中实现的。进入内核空间后,系统调用中枢处理代码(所有的系统调用都由一处中枢代码处理)根据传递的参数(参数是有寄存器传递的包括唯一的系统调用号)和一个静态表分别执行不同的函数。例如read系统调用,0x80 中断处理程序接管执行后,先检查其系统调用号,然后根据系统调用号查找系统调用表,并从系统调用表中得到处理 read 系统调用的内核函数 sys_read ,最后传递参数并运行 sys_read 函数。至此,内核真正开始处理 read 系统调用(sys_read 是 read 系统调用的内核入口)。
2. read系统调用在内核空间的处理层次模型
如图所示为read 系统调用在核心空间中所要经历的层次模型。从图中看出:对于磁盘的一次读请求,首先经过虚拟文件系统层(vfs layer),其次是具体的文件系统层(例如 ext2),接下来是 cache 层(page cache 层)、通用块层(generic block layer)、IO 调度层(I/O scheduler layer)、块设备驱动层(block device driver layer),最后是物理块设备层(block device layer)。
- 虚拟文件系统层的作用:屏蔽下层具体文件系统操作的差异,为上层的操作提供一个统一的接口。正是因为有了这个层次,所以可以把设备抽象成文件,使得操作设备就像操作文件一样简单。
- 在具体的文件系统层中,不同的文件系统(例如 ext2 和 NTFS)具体的操作过程也是不同的。每种文件系统定义了自己的操作集合。关于文件系统的更多内容,请参见参考资料。
- 引入 cache 层的目的是为了提高 linux 操作系统对磁盘访问的性能。 Cache 层在内存中缓存了磁盘上的部分数据。当数据的请求到达时,如果在 cache 中存在该数据且是最新的,则直接将数据传递给用户程序,免除了对底层磁盘的操作,提高了性能。
- 通用块层的主要工作是:接收上层发出的磁盘请求,并最终发出 IO 请求。该层隐藏了底层硬件块设备的特性,为块设备提供了一个通用的抽象视图。
- IO 调度层的功能: