主要讨论文件I/O和标准I/O这两种I/O方式的数据缓冲问题。
1、文件I/O的内核缓冲
-
read()和write()系统调用在进行文件读写操作的时候并不会直接访问磁盘设备。而是仅仅在用户空间缓冲区和内核缓冲区之间复制数据。 1、write后将数据从用户空间拷贝到内核空间缓冲区,拷贝完成之后就返回了。之后再后面 的某一个时刻内核会将其缓冲区中的数据写入到磁盘设备中。 2、 读文件时候,内核从磁盘设备中读取数据到内核的缓冲区中,之后调用read函数读取数据时候,read()调用将从内核缓冲区中读取数据到用户空间缓冲区。 上面可以看到,系统调用和磁盘的操作并不是同步的 -
好处1:我们把上面提到的内核缓冲区称作文件IO的内核缓冲。因为磁盘的读写操作是很缓慢的,我们加入了IO的内核缓冲区是为了不需要等待磁盘的操作,让文件IO的速度和效率提高。 -
好处2:因为我们有缓冲区,我们可以积累到一定的文件大小来一把写入,大大降低了对磁盘操作的次数
2、将内核缓冲区的数据强制更新到磁盘上
- 强制将文件I/O内核缓冲区中缓存的数据写入(刷新)到磁盘设备中
- 当我们在Ubuntu 系统下拷贝文件到U盘时,文件拷贝完成之后,通常在拔掉 U盘之前,需要执行sync命令进行同步操作,这个同步操作其实就是将文件 I/O 内核缓冲区中的数据更新到 U 盘硬件设备,所以如果在没有执行 sync 命令时拔掉 U 盘,很可能就会导致拷贝到 U 盘中的文件遭到破坏!
- 控制文件IO内核的系统调用API
int fsync(int fd);
int fdatasync(int fd);
void sync(void);
- 在程序中频繁调用 fsync()、fdatasync()、sync(),对性能的影响极大,大部分的应用程序是没有这种需求的
3、直接IO,绕过内核缓冲区
-
fd = open(filepath, O_WRONLY | O_DIRECT); //O_DIRECT直接IO标志 -
从用户空间直接将数据传递到文件或磁盘设备,把这种操作也称为直接 I/O(direct I/O)或裸 I/O(raw I/O)。 -
对于大多数应用程序而言,使用直接 I/O可能会大大降低性能。因为没有缓冲区的优化。场景:测试磁盘设备的读写速率。,不经过内核缓冲区直接怼上磁盘。 -
因为直接I/O 涉及到对磁盘设备的直接访问,所以在执行直接 I/O时,必须要遵守以下三个对齐限制要求: 1、应用程序中用于存放数据的缓冲区,其内存起始地址必须以块大小的整数倍进行对齐;(内存是以字节读写的,而磁盘是以块为大小来读写单位的) 2、写文件时,文件的位置偏移量必须是块大小的整数倍 3、写入到文件的数据大小必须是块大小的整数倍。 常见的块大小(512字节、1024字节、2048字节以及4096字节) 使用命令行 tune2fs -l /dev/sda1 | grep “Block size” 可以查看磁盘分区块的大小。
4、标准IO缓冲stdio
- 虽然标准 I/O是在文件I/O基础上进行封装而实现(譬如 fopen内部实际上调
用了 open、fread内部调用了 read等),但在效率、性能上标准 I/O要优于文件I/O,其原因在于标准I/O 实现维护了自己的缓冲区,我们把这个缓冲区称为stdio 缓冲区。 - 前面提到了文件 I/O 内核缓冲,这是由内核维护的缓冲区,而标准 I/O 所维护的 stdio 缓冲是用户空间的缓冲区
- 为了减少调用系统调用的次数,标准 I/O函数会将用户写入或读取文件的数据缓存在 stdio 缓冲区,然后再一次性将 stdio 缓冲区中缓存的数据通过调用系统调用 I/O(文件I/O写入到文件 I/O内核缓冲区或者拷贝到应用程序的 buf中。
- C语言提供了一些库函数可用于对标准I/O的stdio缓冲区进行相关的一些设置
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
void setbuf(FILE *stream, char *buf);
void setbuffer(FILE *stream, char *buf, size_t size);
int fflush(FILE *stream);
fclose(stdout);
5、IO缓冲总结 主要部分是: 用户区、(标准IO)stdio缓冲区(文件IO)内核IO缓冲区、磁盘设备
6、fd文件描述符与FILE*文件指针
- 之前说过,文件IO利用系统调用API围绕着fd转,
标准IO利用标准库函数围着FILE*指针转 - 在应用程序中,在同一个文件上执行I/O操作时,还可以将文件 I/O(系统调用 I/O)与标准I/O 混合使用,这个时候我们就需要将文件描述符和 FILE 指针对象之间进行转换。借助于库函数 fdopen()、fileno()来完成。
int fileno(FILE *stream);
FILE *fdopen(int fd, const char *mode);
- 当混合使用文件I/O和标准 I/O时,需要特别注意缓冲的问题,文件I/O会直接将数据写入到内核缓冲区进行高速缓存,而标准 I/O 则会将数据写入到stdio缓冲区,之后再调用write()将 stdio缓冲区中的数据写入到内核缓冲区。
|