「Linux」- 文件系统 I/O

问题描述

VFS 提供一组标准的文件访问接口。这些接口以系统调用的方式,提供给应用程序使用。比如 open() 打开文件,read() 读取文件,write() 写入文件。也就是说,我们操作的是 VFS 而不是实际的文件系统(ext4、xfs、……)

文件读写方式的各种差异,导致 I/O 的分类多种多样。接下来,我们就将进一步学习文件系统的 IO 类型;

解决方案

最常见的有,缓冲与非缓冲 I/O、直接与非直接 I/O、阻塞与非阻塞 I/O、同步与异步 I/O 等;

补充说明:部分概念也经常出现在网络编程中。比如非阻塞 I/O,通常会跟 select/poll 配合,用在网络套接字的 I/O 中;

缓冲与非缓冲(是否利用标准库缓存)

缓冲 I/O

是指利用标准库缓存来加速文件的访问,而标准库内部再通过系统调度访问文件;

注意,这里的缓冲是由标准库内部实现的缓冲。比如,有些程序遇到换行时才真正输出,而换行前的内容,其实就是被标准库暂时缓存;
但是,它们最终还是要经过系统调用来访问文件。当系统调用后,还会通过页缓存,来减少磁盘的 I/O 操作;

非缓冲 I/O

是指直接通过系统调用来访问文件,不再经过标准库缓存;

直接与非直接(是否利用操作系统的页缓存)

直接 I/O,是指跳过操作系统的页缓存,直接跟文件系统交互来访问文件。比如,指定 O_DIRECT 标志;

非直接 I/O,当文件读写时,先要经过系统的页缓存,然后再由内核或额外的系统调用,真正写入磁盘;

直接 I/O、非直接 I/O,本质上还是和文件系统交互。但是,在某些数据库等场景中,会跳过文件系统直接读写磁盘,也就是我们通常所说的裸 I/O

阻塞与非阻塞(应用程序是否阻塞自身运行)

Blocking I/O

阻塞 I/O,是指应用程序执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程,自然就不能执行其他任务;

Non-blocking I/O

非阻塞 I/O,是指:
1)应用程序执行 I/O 操作后,不会阻塞当前的线程,并继续执行其他的任务;
2)随后再通过 轮询事件通知 的形式,获取调用的结果。比如访问管道或者网络套接字时,设置 O_NONBLOCK 标志,就表示用非阻塞方式访问;

同步与异步(是否等待响应结果,即通知方式)

Synchronous I/O

是指应用程序执行 I/O 操作后,要一直等到整个 I/O 完成后,才能获得 I/O 响应;

在操作文件时,如果设置 O_SYNC 或 O_DSYNC 标志,就代表同步 I/O;
1)如果设置 O_DSYNC,就要等文件数据写入磁盘后,才能返回;
2)而 O_SYNC,则是在 O_DSYNC 基础上,要求文件元数据也被写入磁盘后,才能返回;

Asynchronous I/O

1)应用程序执行 I/O 操作后,不用等待完成和完成后的响应,而是继续执行就可以;
2)等到这次 I/O 完成后,响应会用事件通知的方式,告诉应用程序

在访问管道或者网络套接字时,设置了 O_ASYNC 选项后,相应的 I/O 就是异步 I/O。这样,内核会再通过 SIGIO 或者 SIGPOLL,来通知进程文件是否可读写;

Blocking vs. Synchronized

linux – Does “synchronized I/O” always mean “blocking I/O”? – Stack Overflow

”阻塞 / 非阻塞“和”同步 / 异步“是两个不同角度的 I/O 划分方式,它们描述的对象也不同:

1)阻塞 / 非阻塞,针对的是 I/O 调用者,即应用程序:“Blocking”只是告诉我们系统调用不会返回,直到内核记录将数据记录到某处(例如预写缓存),无法保证在意外断电或硬件故障的情况下该记录会持续存在;例如,当在预写缓存中时,阻塞调用可以在当时运行的其他进程可以看到写入的位置返回,但如果发生电源故障,该写入将丢失;

2)同步 / 异步,针对的是 I/O 执行者(即系统),是否真正的完成 IO 操作:O_SYNC 意义上的“Synchronized”告诉我们系统调用不会返回,直到数据实际持久化到硬件;

所以:所有 Synchronized I/O 都是阻塞的,但并非所有 Blocking I/O 都是同步的;

Asynchronous 与 Non-blocking 实际上是同义词;