【Linux初阶】基础IO - FILE结构体中的缓冲区

文章探讨了库函数(如printf,fwrite)和系统函数(write)在处理缓冲区时的差异,以及缓冲区在内存、刷新策略和文件写入逻辑中的作用。缓冲区的存在是为了提高效率,减少直接IO操作。库函数的输出可能因缓冲区策略和fork进程的影响而出现多次输出。同时,文章介绍了自定义缓冲区的实现,并讨论了OS如何管理缓冲区以及fsync在确保数据完整性的角色。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

🌟hello,各位读者大大们你们好呀🌟
🍭🍭系列专栏:【Linux初阶】
✒️✒️本篇内容:库函数和系统函数的刷新差异,缓冲区的意义、刷新策略、存在位置、文件写入的逻辑,缓冲区的代码实现,缓冲区和OS的关系
🚢🚢作者简介:计算机海洋的新进船长一枚,请多多指教( •̀֊•́ ) ̖́-



一、为什么我的库函数输出两次,系统函数只输出一次?

在正式学习缓冲区知识前,我们用一个代码问题作为引入

#include <stdio.h>
#include <string.h>
int main()
{
   
	const char* msg0 = "hello printf\n";
	const char* msg1 = "hello fwrite\n";
	const char* msg2 = "hello write\n";
	printf("%s", msg0);
	fwrite(msg1, strlen(msg0), 1, stdout);
	write(1, msg2, strlen(msg2));
	fork();
	return 0;
}

运行出结果:

hello printf
hello fwrite
hello write

但如果对进程实现输出重定向呢?./hello > file, 我们发现结果变成了:

hello write
hello printf
hello fwrite
hello printf
hello fwrite

我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。

假如我们将 fork()代码注释掉,再运行上面的重定向指令,我们可以发现它会正常将 write、printf、fwrite三个信息重定向到 file文件中,因此我们可以推断出问题肯定和fork有关!

要理解上面问题出现的原因,就要涉及我们接下来要学习的缓冲区的知识了。

———— 我是一条知识分割线 ————

二、理解缓冲区问题

1.缓冲区存在的意义

在这里我先给出一条结论:缓冲区实际上就是一段内存

那么缓冲区是什么?谁提供的?为什么要存在呢?

内存的进程要将数据写入到磁盘的文件,这个动作的实现,首先需要在内存中开辟一块缓冲区,进程将数据拷贝到我们的缓冲区中,然后进程对应的拷贝函数就直接返回了,返回之后,进程就可以继续向后执行自己的代码了。

在进程执行代码期间,缓冲区中的数据会定期的发送到磁盘的文件中。
在这里插入图片描述

为什么进程不直接自己将数据写入到文件中呢?这是因为对于进程来说太消耗时间了,我们知道,进程写入磁盘文件的这个动作属于IO,涉及到外设,因此相对比较慢,如果进程自己执行这个动作,效率会很低。

至此,我们就上面的三个问题先做一个回答:

  • 缓冲区实际上就是一段内存
  • 缓冲区是用户级语言层面(语言库)给我们提供的(后面会将讲解);
  • 缓冲区的意义是什么呢?节省进程进行数据 IO的时间

那么问题又来了:你说我将信息拷贝给了缓冲区,我哪里有拷贝呢?

以 fwrite函数为例,与其我们将 fwrite理解为写入文件的函数,倒不如将 fwrite理解为拷贝函数!将数据从进程,拷贝到缓冲区或外设中。

———— 我是一条知识分割线 ————

2.缓冲区的刷新策略

假设我们有一块数据,我们是应该一次性写入到外设 还是 少量多次的写入到外设呢?

实际上,一次性写入到外设是效率最高的。在我们 IO的过程中,最耗费时间的并不是数据的拷贝时间,以 1秒为例,有可能 990毫秒都在等待外设准备就绪,10毫秒完成拷贝。因此,对于计算机来说,一次性写入效率是最高的。

缓冲区一定会根据具体的设备,定制自己的刷新策略。一般而言,我们有三种策略两种特殊情况

三种策略

  1. 立即刷新 - 无缓冲(不常见);
  2. 行刷新 - 行缓冲(显示器就是使用行缓存);
  3. 缓冲区满 - 全缓冲(磁盘文件就是全缓冲);

以显示器为例,显示器采用行缓冲,因为显示器需要满足人的阅读需求(及时高效反馈),使用行缓存可以在满足人的需求的同时,保证缓冲效率不会太低。

以磁盘文件为例,IO时采用全缓冲,等待磁盘响应的次数最少,可以保障最大的效率。

两种特殊情况

  1. 用户强制刷新;
  2. 进程退出 - 一般都要进行缓冲区刷新;

为什么有些程序在没有强制刷新要求的情况下,数据迟迟不刷新到显示器上?很大可能就是这个程序的进程还没有退出,缓冲区的数据还没有刷新。

———— 我是一条知识分割线 ————

3.缓冲区在哪里?

再次回到我们一开始的那个代码问题的运行结果:

hello write
hello printf
hello fwrite
hello printf
hello fwrite

告知:上面这个结果的出现,一定和缓冲区有关。

那么这个缓冲区是谁提供的呢?

通过对我们一开始的那个代码运行结果的分析,我们可以得出,缓冲区不在内核中,因为如果缓冲区在内核中,write也应该要被打印两次。

结论:我们之前谈论的缓冲区,都是用户语言层面(语言库)给我们提供的缓冲区。C语言的缓冲区是C标准库提供的。

缓冲区在哪里?

在我们进行文件读写过程中,涉及到的调用接口(stdout、stdin、stderr),都是 *FILE 类型的,*FILE会被 FILE结构体管理,在这个结构体中,就包含了 fd(文件描述符) & 一个缓冲区

总结:这个缓冲区,在 stdout、stdin、stderr -> *FILE -> FILE结构体 -> fd & 一个缓冲区

因此,我们强制刷新时,需要使用 fflush(文件指针),关闭文件时,需要 fclose(文件指针)

———— 我是一条知识分割线 ————

4.文件写入的底层逻辑(1)

如果有兴趣,可以看看FILE结构体:

typedef struct _IO_FILE FILE;/usr/include/stdio.h
/ usr / include / libio.h
struct _IO_FILE {
   
	int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

 //缓冲区相关
 /* The following pointers correspond to the C++ streambuf protocol. */
 /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
	char* _IO_read_ptr; /* Current read pointer */
	char* _IO_read_end; /* End of get area. */
	char* _IO_read_base; /* Start of putback+get area. */
	char* _IO_write_base; 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值