Linux输入-输出(I/O)-错误流and setvbuf

前言

因为一个文件的运行错误,导致我以为是setvbuf的问题,各种找文章了解,后来发现是一个设置的问题,太寄了,看都看差不多了,那就总结一下。

(I/O)基本概念

  • I/O重定向通常与FD有关,shell的FD通常为10个(FD:文件标识符—->系统为每个打开的文件指定一个文件标识符以便系统对文件进行跟踪,文件标识符是一个数字,不同数字代表不同的含义)
  • 常用的FD有三个,为0(stdin ,标准输入),1(stdout ,标准输出)、2(stderr, 标准错误输出)

直接I/O,绕过内核缓冲

  • 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” 可以查看磁盘分区块的大小。

文件I/O(内核缓冲)

1)文件描述符:对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符通常是一个小的非负整数,内核用它标识一个特定进程正在访问的文件。当内核打开一个已有文件或创建一个新文件时,它返回一个文件描述符。

2)按照惯例,UNIX系统shell使用文件描述符0(STDIN_FILENO)与进程的标准输入相关联,文件描述符1(STDOUT_FILENO)与标准输出相关联,文件描述符2(STDERR_FILENO)与标准出错输出相关联。这是各种shell以及很多应用程序使用的惯例,而与UNIX内核无关。尽管如此,如果不遵照这种惯例,那么很多UNIX系统应用程序就不能正常工作。

3)可用的文件I/O函数——打开文件,读文件,写文件等。UNIX系统中的大多数文件I/O只需用到5个函数:open、read、write、lseek以及close。它们是不带缓冲的I/O,都使用文件描述符。在使用read和write函数时,选定不同大小的缓冲区(保存读和写的数据),效率是不同的。存在一个最佳效率的缓冲区大小,就是缓冲区大小等于文件系统的块长。

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

  • write后将数据从用户空间拷贝到内核空间缓冲区,拷贝完成之后就返回了。之后再后面 的某一个时刻内核会将其缓冲区中的数据写入到磁盘设备中。

  • 读文件时候,内核从磁盘设备中读取数据到内核的缓冲区中,之后调用read函数读取数据时候,read()调用将从内核缓冲区中读取数据到用户空间缓冲区。

上面可以看到,系统调用和磁盘的操作并不是同步的

  • 好处1:我们把上面提到的内核缓冲区称作文件IO的内核缓冲。因为磁盘的读写操作是很缓慢的,我们加入了IO的内核缓冲区是为了不需要等待磁盘的操作,让文件IO的速度和效率提高。

  • 好处2:因为我们有缓冲区,我们可以积累到一定的文件大小来一把写入,大大降低了对磁盘操作的次数

标准I/O

1)对于标准I/O库,它们的操作则是围绕流进行的。当用标准I/O库打开或创建一个文件时,使用一个流与一个文件相关联。当打开一个流时,标准I/O函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构,它包含了标准I/O库为管理该流所需要的所有信息,包括:用于实际I/O的文件描述符,指向用于该缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等。

2)预定义了三个标准I/O流,分别为三个文件指针stdin,stdout和stderr。

3)标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数。它对每个I/O流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。标准I/O提供三种类型的缓冲:全缓冲、行缓冲和不带缓冲。

4)后者是在前者的基础上扩充而来的,在大多数情况下,用后者。

两者的区别

1)前者属于低级IO,后者是高级IO。

2)前者返回一个文件描述符(用户程序区的),后者返回一个文件指针。

3)前者无缓冲,后者有缓冲。
4)前者与 read, write 等配合使用, 后者与 fread, fwrite等配合使用。

(I/O)缓冲

以前写C程序输入输出并没有特意关注过I/O缓冲区的问题。最近在学习setvbuf时,发现I/O缓冲区是需要关注的一个细节问题。I/O缓冲区是Unix支持的一项标准,并且得到ISO C标准的支持。

  • 其作用:

    标准I/O库提供缓冲的目的是尽可能减少外部设备数据读写read和write的次数,从而加快CPU的工作效率。

  • 缓冲区的大小:

    缓冲区的大小等于外部块设备的一个块长大小时效率最好。所谓块设备,就是以一块一块的数据存取的设备;字符设备是一次存取一个字符的设备。磁盘、内存都是块设备,字符设备如键盘和串口。如果不想使用系统默认的缓冲区大小,可以使用setvbuf函数来自定义缓冲区大小。也可以用setbuf函数开/关缓冲机制。

  • 缓冲区冲洗(flush):
    缓冲区是一个数据暂存区,那么冲洗的意思就是“清仓”,将缓冲区当前的内容全部写到外部设备上去。该操作可由标准I/O例程自动flush,或者由程序员调用fflush函数。程序遇到“\n”(仅限行缓冲),或是EOF,或是缓冲区满,或是文件描述符关闭,或是主动flush,或是程序退出,就会把数据刷出缓冲区。

  • 缓冲类型:

    标准I/O提供了三种类型,下面说下他们的概念和常用场合:

  1. 全缓冲

    在I/O操作时,只有当I/O缓冲区被填满时,才进行真正的I/O操作。对于全缓冲的缓冲区可由标准I/O例程自动刷新。还有一种方法就是调用函数fflush进行刷新。

    对于驻留在磁盘上的文件,通常由标准I/O库实施全缓冲。

  2. 行缓冲

    在I/O操作时,输入输出遇到换行符’\n’时进行,才进行真正的I/O操作。对于行缓冲,要注意量点:第一,标准I/O每一行缓冲区的最大长度是固定的,所以只要填满了缓冲区,即使没有遇到换行符也要冲洗缓冲区。第二,当使用行缓冲作输出缓冲区时,若输入流是不带缓冲区的流或者一个行缓冲区的流,那么要直接冲洗输出的行缓冲区。前者是因为不缓冲区的输入要求必须马上输出,后者是因为已经输入时做好了缓冲,不必重复缓冲。

    当流涉及到一个终端时,比如标准输入stdin和标准输出stdout,通常使用行缓冲。scanf、printf不直接调用系统调用,在用户空间维护一块行缓冲区,在适当的时候调用read、 write读写缓冲区。

  3. 不带缓冲

    底层函数数据读写函数read和write是不带缓冲区的,直接调用系统调用进行外部读写。标准出错流stderr是不带缓冲的,这样能使得错误信息尽快显示。

setvbuf()函数详解

为什么要使用setvbuf函数

如果你的内存足够大,可以把文件IO的BUF设置大一些,这样每次你用fopen/fread/fwrite/fscanf/fprintf语句的时候,都会在内存里操作,减少内存到磁盘IO读写的操作次数,提高系统效率。

如果你的程序的功能涉及到类似数据库、视频、音频、图像处理等大量需要爆发式磁盘到内存的IO情况下,可以考虑用setvbuf进行优化内存IO,其他情况下可以不考虑,LINUX/WINDOWS会自动处理这些问题。

setvbuf的用法

功 能: 把缓冲区与流相关
  用 法: int setvbuf(FILE *stream, char *buf, int type, unsigned size);
  参数:stream :指向流的指针 ;
  buf : 期望缓冲区的地址;
  type : 期望缓冲区的类型:
  _IOFBF(满缓冲):当缓冲区为空时,从流读入数据。或者当缓冲区满时,向流写入数 据。
  _IOLBF(行缓冲):每次从流中读入一行数据或向流中写入一行数据。
  _IONBF(无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。
  size : 缓冲区内字节的数量。

1
if (setvbuf(input, bufr, _IOFBF, 512) != 0) 

是设置 input这个文件流使用 bufr 所指的512个字节作为 input文件的buffer, 当你操作input文件时,数据都会暂存在 bufr 里面,每次读input时,系统会一次性读512字节到bufr里暂存。

1
if (setvbuf(output, NULL, _IOLBF, 132) != 0) 

是设置output文件的buffer,这个buffer会由系统自行调用malloc来申请,buffer是按行模式工作的,每行最大132个字节,也就是当你写output的时候,每写完一行(遇见\n),就会把整行真正的写到磁盘文件上,在遇到换行符前,都咱存在系统自动申请的buffer中,而不会写到真正的磁盘文件上。

IO缓冲总结

image-20221103221823898

主要部分是:

用户区,(标准IO)stdio缓冲区(文件IO)内核IO缓冲区,磁盘设备