1.文件I/O的内核缓冲
或许很多初学文件I/O时会认为read()或write()系统调用会直接对磁盘中的文件发起访问,其实不然,此俩个调用只会使用户空间缓冲区与内核的高速缓冲区之间复制数据。
write(fd,"12345");
例如上述操作,当write返回后,在之后的某个时刻内核会将该数据写入(刷新至)磁盘(所以系统调用其实并不与磁盘同步)如果在此期间,另一个程序试图读取这些字节,将会直接存内核高速缓存区读走
与此相同,对于从磁盘读,内核也会先读到内核缓冲区中,read调用将从这里读走数据。对于序列化的文件访问,内核通常会尝试预读,以确保在需要之前就将该文件的下一数据快读入缓冲区,采用这一设计,可以使read()和write()的操作加快
linux的内核缓冲区没有固定的上线,内核会分配尽可能多的内存,一般此举只受限于俩个因素。可用的物理内存总量和其他目的对内存的需求
2.缓冲区大小对I/O系统调用的影响
如果没有缓冲区,我们可以想象以下读写1000个字节,那么内核就得访问1000此磁盘,众所周知,内核访问磁盘的速度是非常慢的,那么缓冲区的大小会对操作时间有多大影响呢?
接下来这个表格摘自linux系统编程手册中,它是要通过设置不同大小的缓存区,来比较复制100M文件所用的时间
BUFFER_SIZE | 总用时(s) |
---|---|
1 | 107.43 |
2 | 54.16 |
4 | 31.72 |
8 | 15.59 |
16 | 7.5 |
32 | 3.76 |
64 | 2.19 |
128 | 2.16 |
256 | 2.06 |
512 | 2.06 |
1024 | 2.05 |
4096 | 2.05 |
65536 | 2.06 |
由上图可知缓冲区的大小对文件复制事件影响很大。当缓冲区大小为4096时,几乎达到了最优,超过这个值,效果并不显著
3.stdio库的缓冲
当操作磁盘时,缓冲大块数据以减少系统调用C函数库的I/O正是这么做的,因此使用C标准库I/O,使用者完全不必自己处理缓冲区
(1)设置一个stdio流的缓冲模式
#include<stdio.h>
int setvbuf(FILE *stream,char *buf,int mode,ssize_t size);
调用setvbuf函数可以控制stdio库的缓冲模式
.参数stream说明要修改哪个文件流的缓冲模式
.参数buf不为NULL时,stream的缓冲区大小为size,缓冲区由堆莱分配
.buf为NULL,缓冲区有stdio默认分配
.参数mode指定了缓冲类型
mode值 | 具体表现 |
---|---|
_IONBF | 不对I/O进行缓冲 |
_IOLBF | 采用行缓冲I/O |
_IOFBF | 采用全缓冲I/O |
#include<stdio.h>
void setbuf(FILE *stream,char *buf);
.buf为BULL时表示无缓冲区,否则采用buf大小的缓冲区
(2)刷新stdio的缓冲区
#include<stdio.h>
int fflush(FILE *stream);
若参数stream为NULL则将刷新所有的stdio缓冲区
4.控制文件I/O的内核缓冲
有时候我们需要强制刷新内核区到输出文件,例如数据库的日志进程,要确保在继续操作前将输出真正写入磁盘
(1)fsync()
#include<unistd.h>
int fsync(int fd);
该函数,只有当对磁盘设备的传递完成才返回,否则将处于阻塞
(2)sync()
#include<unistd.h>
void sync(void);
sync()调用会使包含文件跟新信息的所有内核缓存区刷新到磁盘上
(3)open()调用的O_SYNC标志
调用open后,若有O_SYNC标志,则每次write()调用都会自动将文件数据和元数据刷新到磁盘上
O_SYNC标志对性能的影响极大,下标摘自linux系统编程手册
BUF_SIZE | 无O_SYNC(s) | 有O_SYNC(s) |
---|---|---|
1 | 1030 | 98.8 |
16 | 65.0 | 0.40 |
256 | 4.07 | 0.03 |
4096 | 0.34 | 0.03 |
5.总结
文件I/O缓冲主要是先通过stdio库将用户数据传到stdio缓冲区,该缓冲区位于用户态内存区,当缓冲区填满时,stdio会调用write()系统调用,将数据传到内核告诉缓冲区,在一定的时间之后,内核发起磁盘操作,将数据传到磁盘。