参考文献:正点原子linuxC应用编程用户文档

文件IO的内核缓冲

read()、write()系统调用在进行文件读写操作时,不会直接访问磁盘设备,而仅在用户空间缓冲区内核缓冲区之间复制数据。

例如

write(fd, "hello", 5);

上述代码只是将字符串"hello"拷贝到内核缓冲区就返回,而不是等数据写入磁盘中才返回,这证明系统调用与磁盘操作并不同步。具体什么时间数据会写入磁盘并不确定,由内核相应的存储算法进行判断。

刷新文件IO的内核缓冲区

控制文件IO内核缓冲的系统调用

  • sync():将所有文件IO内核缓冲区的数据和元数据写入到磁盘中
  • syncfs()
  • fsync():将fd所指文件的数据和元数据写入到磁盘中
  • fdatasync():将fd所指文件的数据(不含元数据)写入到磁盘中

元数据:并不是文件内容,而是记录文件属性相关的数据信息

以上四个系统调用,都可以实现将文件IO内核缓冲区的数据写入到磁盘中,具体用法可以用 man 2 sync/syncfs/fsync/fdatasync来查看。

控制文件IO内核缓冲的标志

可以在使用系统调用open()时,加入O_DSYNC或O_SYNC标志。

  • O_DSYNC

    fd = open(filepath, O_WRONLY|O_DSYNC);

    效果相当于每次调用write()后调用fdatasync()。

  • O_SYNC

    fd = open(filepath, O_WRONLY|D_SYNC);

    效果相当于每次调用write()后调用fsync()。

对性能的影响:频繁调用sync或控制标志对性能的影响极大,大部分应用程序无此需求。

直接IO

绕过内核缓冲,从用户空间直接将数据传递到文件/磁盘设备,也称为direct I/O或raw I/O,这种方式在特定场景下使用,一般不常用。而且由于直接IO,有对齐限制(每次写的字节数、长度等),如果不满足条件,在调用write()时均返回:Invalid Arguments。

使用方法:

fd = open(filepath, O_WRONLY|O_DIRECT);

stdio缓冲

stdio缓冲简介

标准I/O(stdio)的性能要高于文件I/O,原因在于维护了自己的缓冲(stdio缓冲是用户空间的缓冲)。

可以通过setbuf()、setbuffer()、setvbuf()对stdio缓冲进行设置,三个函数的使用方法可以使用 man 3 setbuf/setbuffer/setvbuf来查看。

这里特别注意常用的scanf()和printf()函数,采用行缓冲,即在输入或输出中遇到'\n'时,才执行文件IO操作。以下是验证行缓冲的例程。

// sample 1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    printf("Hello world\n");
    printf("Hello world");

    while(1) {
        sleep(1);
    }
}

运行sample 1,会发现终端只输出了第一个Hello world,并没有输出第二个Hello world,这是由于printf采用行缓冲,并没有将第二行的Hello world从stdio缓冲区进行文件IO操作。

使用setvbuf()将stdout设置为无缓冲模式,再运行sample 2例程,会发现两个hello world全部输出了。

// sample 2
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    int ret;

    ret = setvbuf(stdout, NULL, _IONBF, 0);
    if(ret) {
        perror("setvbuf error");
        exit(0);
    }

    printf("hello world\n");
    printf("hello world");

    for(;;) {
        sleep(1);
    }
}

刷新stdio缓冲区

  • 强制刷新

可以使用 fflush(FILE *stream)函数来刷新stdio缓冲区,此函数可以将文件指针所指的文件数据从stdio缓冲区写入内核缓冲区。如果调用fflush(NULL),则代表刷新所有的stdio缓冲区。运行sample 3,可以发现两个hello world全部输出了。

// sample 3
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    printf("hello world\n");
    printf("hello world");

    fflush(stdout);

    for(;;) {
        sleep(1);
    }
}
  • 自动刷新

    • 关闭文件时自动刷新stdio缓冲区

    • 程序退出时自动刷新stdio缓冲区

    注意:使用exit()或return 0或不显式退出时会刷新stdio缓冲区,使用_exit()或_Exit()退出时,不会刷新stdio缓冲区。

混合使用文件IO与标准IO

系统调用使用的是文件IO,而c标准库函数使用的是标准IO,混合使用两种IO时,要特别注意IO缓冲引起的问题(可能会影响输出信息的顺序,从而影响正确性,请运行sample 4)。

// sample 4
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    printf("print");  //由于是行缓冲,不能加\n
    write(STDOUT_FILENO, "write\n", 6);
    exit(0);
}

可以发现,是write优先于print输出。

说点什么
请文明发言!
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...