文件 I/O 的内核缓存: 缓冲区高速缓存
read, write 系统调用在操作磁盘文件时不会直接发起磁盘访问,而是仅仅在用户空间缓冲区与内核缓冲区高速缓存【kernel buffer cache】之间复制数据。在后续的某个时刻,内核会将其缓冲区中的数据写入磁盘。若再次期间另一进程试图访问该文件的这几个字节,那么内核将自动从缓冲区高速缓存中提供这些数据。
Linux 内核对缓冲区高速缓存的大小没有固定上限。内核会尽可能多的分配缓冲区高速缓存,仅受限于以下两个因素:
- 可用的物理内存总量
- 出于其他目的对物理内存的需求【需要将正在运行的进程的文本和数据页保留在物理内存中】
如果与文件发生大量的数据传输,通过采用大块空间缓冲数据,以及执行更少的系统调用,可用提高 I/O 性能。
stdio 库的缓存
|
|
调用 setvbuf 函数,可以控制 stdio 库使用缓冲的形式。打开流后,必须在调用任何其他 stdio 库函数之前先调用 setvbuf,将影响后续在指定流上进行的所有 stdio 操作。
buf 和 size 针对参数 stream 要使用的缓冲区:
- buf 不为 NULL,那么其指向 size 大小的内存块作为 stream 的缓冲区
- 若 buf 为 NULL,那么 stdio 库会为 stream 自动分配一个缓冲区
参数 mode 制定了缓冲类型:
_IONBF
不对 I/O 进行缓冲_IOLBF
采用行缓冲 I/O_IOFBF
采用全缓冲 I/O
|
|
- setbuffer 函数类似于 setbuf 函数,但允许调用者指定 buf 缓冲区的大小【
setbuffer(fp,buf,size) = setvbuf(fp, buf, (buf != NULL) ? _IOFBF : _IONBF, size)
】 - fflush 函数强制将 stdio 输出流中的数据刷新到内核缓冲区中【若参数 stream 为 NULL,将刷新所有的 stdio 缓冲区】
控制文件 I/O 的内核缓存
可以强制刷新内核缓冲区到输出文件。
- Synchronized I/O file integrity completion
- Synchronized I/O data integrity completion
|
|
- fsync 系统调用将使缓存数据和与打开文件描述符 fd 相关的所有元数据都刷新到磁盘上。
- fdatasync 系统调用类似于 fsync,只是强制文件处于 synchronized I/O data integrity completion 状态【fdatasync 可能会减少对磁盘操作的次数,由 fsync 调用请求的两次变为一次】
- sync 系统调用会是包含更新文件信息的所有内核缓冲区刷新到磁盘上
调用 open 函数时如指定O_SYNC
标识,则会使所有后续输出同步。【fd = open(pathname, O_RWONLY | O_SYNC)
】
【采用O_SYNC
标识(或者频繁调用 fsync, fdatasync 和 sync)对性能的影响极大】
I/O 缓冲小结
向内核提出建议 - I/O 模式
|
|
posix_fadvise 系统调用允许进程就自身访问文件数据时可能采取的模式通知内核。【内核并不一定采纳】
- POSIX_FADV_NORMAL 无特别建议
- POSIX_FADV_SEQUENTIAL 进程预计会从低偏移量到高偏移量顺序读取数据
- POSIX_FADV_RANDOM 进程预计以随机顺序访问数据
- POSIX_FADV_WILLNEED 进程预计在不久的将来访问指定文件区域
- POSIX_FADV_DONTNEED 进程预计在不久的将来将不会访问指定的文件区域
- POSIX_FADV_NOREUSE 进程预计会一次性地访问指定区域,不再复用
绕过缓冲区高速缓存: 直接 I/O
Linux 2.4 后允许应用程序在执行磁盘 I/O 时绕过缓冲区高速缓存,从用户空间直接将数据传递到文件和设备磁盘【direct I/O, raw I/O】
内核为提高 I/O 性能,针对缓冲区高速缓存做了很多优化:按顺序预读取,在成簇磁盘块上执行 I/O,允许访问同一文件的多个进程共享高速缓存的缓冲区。
直接 I/O 的对齐限制【EINVAL 错误】
- 用于传递数据的缓冲区,其内存边界必须对齐为块大小的整数倍
- 数据传输的开始点,即文件和设备的偏移量,必须是块大小的整数倍
- 待传递数据的长度必须是块大小的整数倍
|
|
混合使用库函数和系统调用进行文件 I/O
在同一文件上执行 I/O 操作时,可以将系统调用和标准 C 语言库函数混合使用。
|
|
- 给定一个(文件)流,fileno 函数将返回相应的文件描述符
- 给定一个文件描述符,fdopen 函数将创建一个使用该描述符进行文件 I/O 的相应流
通常情况下,printf 函数的输出往往在 write 函数的输出后出现