IO缓冲(Linux应用编程)

主要讨论文件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
/*
@	头文件:	#include <unistd.h> 
@	系统调用fsync()将参数 fd所指文件的内容数据和元数据写入磁盘,只有在对磁盘设备的写入操作完成之后,fsync()函数才会返回,
@	元数据并不是文件内容本身的数据,而是一些用于记录文件属性相关的数据信息,譬如文件大小、时间戳、权限等等信息,这里统称为文件的元数据,这些信息也是存储在磁盘设备中的(inode结构体)
@	参数fd表示文件描述符
@	函数调用成功将返回0,失败返回-1并设置errno以指示错误原因
*/
int fsync(int fd);

/*
@	fdatasync()函数 
@	头文件:	#include <unistd.h> 
@	fdatasync()仅将参数 fd 所指文件的内容数据写入磁盘,并不包括文件的元数据
@	只有在对磁盘设备的写入操作完成之后,fdatasync()函数才会返回
*/
int fdatasync(int fd); 

/*
@	头文件 	#include <unistd.h> 
@	系统调用sync()会将所有文件 I/O 内核缓冲区中的文件内容数据和元数据全部更新到磁盘设备中,该函数没有参数、也无返回值,意味着它不是对某一个指定的文件进行数据更新,而是刷新所有文件 I/O内核缓冲区
*/
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缓冲区进行相关的一些设置
/*
@	#include <stdio.h> 
@	调用 setvbuf()库函数可以对文件的 stdio 缓冲区进行设置,譬如缓冲区的缓冲模式、缓冲区的大小、起始地址等
@	stream:FILE指针,用于指定对应的文件,每一个文件都可以设置它对应的 stdio缓冲区
@	buf:
	如果参数buf不为NULL,那么buf指向size大小的内存区域将作为该文件的 stdio缓冲区,因为
stdio 库会使用buf指向的缓冲区,所以应该以动态(分配在堆内存,譬如 malloc,在 7.6小节介绍)或静态的方式在堆中为该缓冲区分配一块空间,而不是分配在栈上的函数内的自动变量(局部变量)。如果buf等于 NULL,那么 stdio 库会自动分配一块空间作为该文件的 stdio 缓冲区(除非参数 mode 配置为非缓冲模式)
@	mode:参数mode用于指定缓冲区的缓冲类型,
	_IONBF:不对I/O 进行缓冲(无缓冲)。
	_IOLBF:采用行缓冲 I/O=====默认模式
	_IOFBF: 采用全缓冲 I/O。
@	size:指定缓冲区的大小。 	
@	返回值:成功返回0,失败将返回一个非 0值,并且会设置 errno来指示错误原因。
@	需要注意的是,当stdio 缓冲区中的数据被刷入到内核缓冲区或被读取之后,这些数据就不会存在于缓冲区中了,数据被刷入了内核缓冲区或被读走了
*/
int setvbuf(FILE *stream, char *buf, int mode, size_t size); 

/*
@	#include <stdio.h> 
@	相当于: setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ); 
@	要么将 buf 设置为 NULL 以表示无缓冲,要么指向由调用者分配的 BUFSIZ 个字节大小的缓冲区
*/
void setbuf(FILE *stream, char *buf); 

/*
@	#include <stdio.h> 
@	etbuffer()函数类似于setbuf(),但允许调用者指定 buf缓冲区的大小
@	就相当于: setvbuf(stream, buf, buf ? _IOFBF : _IONBF, size);
*/
void setbuffer(FILE *stream, char *buf, size_t size); 


/*
@	刷新 stdio缓冲区 
@	论我们采取何种缓冲模式,在任何时候都可以使用库函数 fflush()来强制刷新(将输出到 stdio缓冲区中的数据写入到内核缓冲区,通过 write()函数)stdio 缓冲区,该函数会刷新指定文件的 stdio 输出缓冲区
@	#include <stdio.h> 
@	参数stream指定需要进行强制刷新的文件,如果该参数设置为NULL,则表示刷新所有的stdio缓冲区
@	函数调用成功返回0,否则将返回-1,并设置errno以指示错误原因
*/
int fflush(FILE *stream); 

/*
@	关闭文件时刷新stdio缓冲区 
*/
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()来完成。
/*
@	#include <stdio.h> 
@	库函数 fileno()可以 FILE* ⇒ fd
@	通过返回值得到文件描述符,如果转换错误将返回-1,并且会设置 errno来指示错误原因
*/
int fileno(FILE *stream); 

/*
@	FILE *fdopen(int fd, const char *mode); 
@	fdopen()则进行着  fd==>FILE*
@	若该参数与文件描述符 fd的访问模式不一致,则会导致调用 fdopen()失败
*/
FILE *fdopen(int fd, const char *mode); 
  • 当混合使用文件I/O和标准 I/O时,需要特别注意缓冲的问题,文件I/O会直接将数据写入到内核缓冲区进行高速缓存,而标准 I/O 则会将数据写入到stdio缓冲区,之后再调用write()将 stdio缓冲区中的数据写入到内核缓冲区。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栋哥爱做饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值