【Linux】C语言缓冲区、缓冲区的实现

news2024/10/2 6:30:53

目录

一、C语言缓冲区

1.1 什么是缓冲区

1.2 缓冲区刷新

1.3 问题解释

1.4 stdout与stderr

1.5 perror的实现 

二、模拟实现缓冲区

2.1 接口展示

2.2 函数实现

2.3 测试与源代码


一、C语言缓冲区

1.1 什么是缓冲区

缓冲区本质是一块C语言提供的内存空间,这块内存区用来缓存待处理的数据。

缓冲区的刷新策略:

  1. 立即刷新。(IO次数多,效率低)
  2. 行刷新 (\n)。
  3. 满刷新(全缓冲),即写满缓冲区再刷新。(效率最高)

但是还有一些特殊的刷新策略

  1. 用户强制刷新( fflush )
  2. 进程退出

对于不同文件有着不同的刷新策略

显示器对应的刷新策略为行缓冲 (为了兼顾用户体验)

而磁盘文件倾向为全缓冲的刷新策略。

其实所有的设备都倾向于全缓冲,因为全缓冲这种刷新策略可以减少IO的次数,也就是减少外设的访问次数,这种方式效率是最高的。

1.2 缓冲区刷新

关于为什么上篇文章中实现重定向功能,最后要使用fflush进行刷新缓冲区这个问题我们进行讲解,我们先来看看下面这段代码,分析了下面这段代码自然就理解了这个没有解决的问题 了。

但是,如果使用重定向操作,奇怪的现象就出现了。

C语言提供的打印函数都执行了两次,而系统调用(write)就输出了一次。

这必然和上面我们所说的缓冲区对显示器和磁盘文件的刷新策略不同导致的。

具体原因解释如下:

1.如果我们向显示器打印,则刷新策略则是行刷新,那么最后执行fork时——函数已经执行结束,内容已经被刷新至系统。(此时 fork 无意义)

2.如果将该程序进行重定向至文件进行打印,则让原本向显示器进行的行刷新变为了向磁盘文件的满刷新,此时\n无意义。

执行到fork时,函数执行结束,数据仍存在于对应进程的C标准库缓冲区中,没有被刷新至系统。

3.此时缓冲区的数据属于父进程的数据,fork创建子进程,子进程暂时使用父进程内的数据。

4.当执行至return 0时,父子进程准备相继退出时进行缓冲区内容的刷新,因为刷新数据属于一种写入操作,所以发生写时拷贝,父子进程相继将缓冲区中的数据刷新至系统,系统再输入至磁盘文件中。

其实很好证明我们上面的观点,我们只要在fork之前进行fflush,刷新缓冲区,就可以做到无论输出到文件还是显示器,结果都一样。

结果如下:

此时又引申一个问题,我们fflush函数中只是传入了stdout,就刷新了缓冲区?

原因如下:

在文件  /usr/include/libio.h 中,我们可以找到 FILE 结构体的定义

1.3 问题解释

再昨天讲述输出重定向的原理时,留下的一个问题就是什么一定要在关闭文件前进行进行缓冲区的刷新。

有了上面的讲解其实这个问题也就很好解释了。

因为我们关闭了 stdout 对应的文件描述符1,此时log.txt对应的文件描述符 fd 就是1。然后我们调用printf 向 stdout( fd==1 ) 的缓冲区中存放数据,如果不调用fflush刷新stdout的缓冲区,则该文件直接被close关闭,缓冲区中的数据也就丢失了。

1.4 stdout与stderr

首先我们来看一段代码

int main()
{
	int fd = open("log.txt", O_RDONLY);
	if (fd < 0)
	{
		myperror("open");
		return 1;
	}
	//stdout -> 1
	printf("hello printf 1\n");
	fprintf(stdout,"hello fprintf 1\n");
	// stderr -> 2

	perror("hello perror 2"); //stderr

	const char *s1 = "hello write 1\n";
	write(1, s1, strlen(s1));

	const char *s2 = "hello write 2\n";
	write(2, s2, strlen(s2));

	// cout -> 1
	std::cout << "hello cout 1" << std::endl;
	// cerr -> 2
	std::cerr << "hello cerr 2" << std::endl;
    return 0;
}

执行结果如下:

然后我们进行重定向(就会出现差别了):

进行重定向时,默认将fd为1处的指向进行了覆盖,在C语言中就是覆盖了stdout。

本质上,1和2对应的都是显示器文件,但是他们两个是不同的,可以认为stdout和stderr是同一个显示器文件被打开了两次。

这样的好处就在于,我们可以将stdout的内容存放至运行日志,将stderr的内容存放至错误日志,有利于程序的排错与测试。向下图这样:

 当然我们可以将stdout、stderr的内容都重定向到一个文本中。

这里的原理是,结合文件描述中dup2的理解,先将fd为1处的指向改为指向log.txt,然后取出1处的数据再放入到fd为2处,这样fd为1、fd为2处都指向了log.txt。本质其实还是重定向。

1.5 perror的实现 

perror 不同于printf,perror是向2号文件描述符写入,printf是向1号文件描述符写入。

perror 本质就是在读取程序中的 errno 的值,然后打印处对应的错误信息。

我们可以引入头文件<errno.h>来修改程序的错误码,让perror打印对应的错误信息,这就是perror的原理。

 接下来就模拟实现一下

我们要使用strerror函数,这个函数的功能就是输入程序的错误码从而打印对应的错误信息。

实现与结果如下:

二、模拟实现缓冲区

2.1 接口展示

为了加深我们对C标准库中缓冲区的理解,我们打算模拟实现一下缓冲区。

接下来是我们打算实现的接口以及测试的方式:

//缓冲区的大小
#define NUM 1024

typedef struct MyFILE {
	int fd;
	char buffer[NUM];
	int end;
}MyFILE;

MyFILE* _fopen(const char* pathname, const char* mode)
{}

	
void _fputs(const char* message, MyFILE* fp)
{}

void _fflush(MyFILE* fp)
{}

void _fclose(MyFILE* fp)
{}

int main()
{
	MyFILE* fp = _fopen("log.txt", "w");
	if (fp == NULL)
	{
		printf("open file error");
		return 0;
	}
	_fputs("hello world1\n", fp);
	_fputs("hello world2\n", fp);
	_fputs("hello world3\n", fp);
	_fflush(fp);
	_fclose(fp);
	return 0;
}

我们在主函数中的测试代码其功能为:

使用我们的_fopen函数以写的方式打开文件log.txt,用MyFILE指针接受其返回值,用fputs往fp指向的文件中输入信息,因为是往磁盘文件进行写入文件,所以我们使用_fflush刷新缓冲区,将内容输入至内核中,再由操作系统写入至log.txt中,最后使用_fclose关闭文件。

2.2 函数实现

1._fopen

判断参数正确性后,接着我们要判断用户是以什么方式打开文件(使用strcmp进行判断)。

然后调用open函数打开文件,传入对应的选项。如果文件打开成功,申请MyFILE结构体,返回该结构体的地址即可后序进行访问文件。

MyFILE* _fopen(const char* pathname, const char* mode)
{
	assert(pathname && mode);
	MyFILE* fp=NULL;
    //因为stdin的fd为0.
	int fd = -1;
	if (strcmp(mode, "r") == 0)
	{
		fd = open(pathname,O_RDONLY);
	}
	else if (strcmp(mode, "w") == 0)
	{
		//打开文件,传入3个选项:只读、创建、清空,设置权限为0666
		int fd = open(pathname, O_WRONLY | O_TRUNC | O_CREAT, 0666);
	}
	else if (strcmp(mode, "a") == 0)
	{
		fd = open(pathname, O_WRONLY | O_APPEND | O_CREAT, 0666);
	}
	if (fd >= 0)
	{
		//申请一个FILE结构体
		fp = (MyFILE*)calloc(1, sizeof(MyFILE));
		fp->fd = fd;
	}
	return fp;
}

2._fputs

判断参数正确性后,我们将数据拷贝到我们的缓冲区中,并调整对应 fp 中的数据个数(end)。

注意:向C语言默认打开的3个文件流(stdin、stdout、stderr)中进行打印时,要进行特殊的缓冲区刷新处理。比如向显示器中打印时刷新策略会变为行刷新,例如语句:_fputs(stdout,"hello\n");

void _fputs(const char* message, MyFILE* fp)
{
	assert(message && fp);
	//将字符串写入到缓冲区中
	strcpy(fp->buffer + fp->end, message); //adcde\0
	fp->end += strlen(message); 

	//如果是标准输入输出
	if (fp->fd == 0)   //暂不实现 
	{}
	else if (fp->fd == 1)
	{
		//输出到显示器 刷新策略改变为行刷新
		if (fp->buffer[fp->end - 1] == '\n')
		{
			write(fp->fd, fp->buffer, fp->end);
			fp->end = 0;
		}
	}
	else if (fp->fd == 2) {} //暂不实现 
	else {}  //暂不实现 
}

3._fflush

_fflush的其实就是将C语言中缓冲区的数据调用write接口传入系统内核的缓冲区中,所以当我们检测到如果C缓冲区不为空,则调用write进行刷新。

注意:此时调用write接口只是将数据传入系统中,这里我们还可以将内核中的数据直接刷新到磁盘中。调用的接口是syncfs,直接将fd传入即可。

void _fflush(MyFILE* fp)
{
	assert(fp);
	if (fp->end != 0)
	{
		//暂且将数据写入至内核
		write(fp->fd, fp->buffer, fp->end);
		//将数据从内核刷新至外设
		syncfs(fp->fd);     
		fp->end = 0;
	}
}

4._close

_close的实现就是调用close接口即可,只不过在调用之前要先进行缓冲区的刷新。

void _fclose(MyFILE* fp)
{
	//close之前,先进行刷新
	assert(fp);
	_fflush(fp);
	close(fp->fd);
	free(fp);
}

2.3 测试与源代码

测试代码如下:

执行结果: 

全部代码:

#include <stdio.h>                                                                     
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>

//缓冲区的大小
#define NUM 1024

typedef struct MyFILE {
	int fd;
	char buffer[NUM];
	int end;
}MyFILE;

MyFILE* _fopen(const char* pathname, const char* mode)
{
	assert(pathname && mode);
	MyFILE* fp=NULL;
	int fd = -1;
	if (strcmp(mode, "r") == 0)
	{
		fd = open(pathname,O_RDONLY);
	}
	else if (strcmp(mode, "w") == 0)
	{
		//打开文件,传入3个选项:只读、创建、清空,设置权限为0666
		int fd = open(pathname, O_WRONLY | O_TRUNC | O_CREAT, 0666);
	}
	else if (strcmp(mode, "a") == 0)
	{
		fd = open(pathname, O_WRONLY | O_APPEND | O_CREAT, 0666);
	}
	if (fd >= 0)
	{
		//申请一个FILE结构体
		fp = (MyFILE*)calloc(1, sizeof(MyFILE));
		fp->fd = fd;
	}
	return fp;
}
	
void _fputs(const char* message, MyFILE* fp)
{
	assert(message && fp);
	//将字符串写入到缓冲区中
	strcpy(fp->buffer + fp->end, message); //adcde\0
	fp->end += strlen(message); 

	//如果是标准输入输出
	if (fp->fd == 0)   //暂不实现 
	{}
	else if (fp->fd == 1)
	{
		//输出到显示器 刷新策略改变为行刷新
		if (fp->buffer[fp->end - 1] == '\n')
		{
			write(fp->fd, fp->buffer, fp->end);
			fp->end = 0;
		}
	}
	else if (fp->fd == 2) {} //暂不实现 
	else {}  //暂不实现 
}

void _fflush(MyFILE* fp)
{
	assert(fp);
	if (fp->end != 0)
	{
		//暂且将数据写入至内核
		write(fp->fd, fp->buffer, fp->end);
		//将数据从内核刷新至外设
		syncfs(fp->fd);     
		fp->end = 0;
	}
}

void _fclose(MyFILE* fp)
{
	//close之前,先进行刷新
	assert(fp);
	_fflush(fp);
	close(fp->fd);
	free(fp);
}

int main()
{
	MyFILE* fp = _fopen("log.txt", "w");
	if (fp == NULL)
	{
		printf("open file error");
		return 0;
	}
	_fputs("hello world1\n", fp);
	_fputs("hello world2\n", fp);
	_fputs("hello world3\n", fp);
    //直接往屏幕上输出(用于测试)
    
	_fflush(fp);
	_fclose(fp);
	return 0;
}

最后就是模拟向显示器打印,从而改变刷新策略从满刷新到行刷新的测试。

 结果如下:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/127985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

边缘计算网关的应用有哪些

边缘计算网关&#xff08;又叫物联网边缘计算网关&#xff09;&#xff0c;简称 Edge-Gateway&#xff0c;是一种可以在设备上运行本地计算、消息通信、数据缓存等功能的工业智能网关&#xff0c;可以在无需联网的情况实现设备的本地联动以及数据处理分析。 其具备接口丰富&…

无线蓝牙耳机什么牌子的好?好的无线蓝牙耳机推荐

无线蓝牙耳机因为携带更便捷&#xff0c;连接也越来越迅速而受到了不少用户的喜爱。在目前的耳机市场中&#xff0c;蓝牙耳机所占的比例越来越高&#xff0c;无线蓝牙耳机牌子也逐渐增多。那么&#xff0c;什么牌子的无线蓝牙耳机好&#xff1f;下面&#xff0c;我给大家盘点了…

迁移学习的 PyTorch 实现

什么是迁移学习&#xff1f;迁移学习是一种用于机器学习的技术&#xff0c;它使用预先训练好的模型来创建新的模型。这可以减少新创建模型所需的训练时间&#xff0c;因为它将继承以前学习的特性&#xff0c;这也将提高其准确性。但是迁移学习能带来多大的不同呢&#xff1f;为…

销售促单话术

销售过程中&#xff0c;销售不仅可以运用一些销售技巧来吸引客户的兴趣&#xff0c;也可以通过一些话术来获得客户的认同。 ​ 前言 销售过程中&#xff0c;销售不仅可以运用一些销售技巧来吸引客户的兴趣&#xff0c;也可以通过一些话术来获得客户的认同。很多销售一定遇到过…

【观察】全面赋能汽车行业数字化转型,亚马逊云科技的决心与诚意

毫无疑问&#xff0c;新一轮科技革命正在驱动整个汽车产业发生颠覆性的重大变革&#xff0c;传统的行业游戏规则也将在数字化时代被改写&#xff0c;而未来以电动化、智能化、互联化、共享化为特征的“新四化”&#xff0c;也正在成为整个汽车产业技术创新和商业模式进化的新方…

一文带你走进Python文件操作

Py文件操作 一: """ 1.找到这个文件,双击打开 ​ open(文件路径,mode"",encoding"") mode>读/写 encoding>文件的编码集是什么 ​文件路径:1.绝对路径d:Tools/Tool/python/a.txt 不安全2.相对路径相对于当前你的程序所在的文件夹…

回首来时路,未来皆坦途.......

不觉间&#xff0c;又到了年末&#xff0c;这一年似乎并没有什么特别的经历和值得一提的业绩&#xff0c;就是这样平淡的过着每一天&#xff0c;这大概就是普通人的人生吧..... 今年最大的改变就是跨行业成长&#xff0c;从事了售前工程师的工作&#xff0c;由于之前并没有相关…

20221230英语学习

今日单词 agreeable adj.惬意的&#xff0c;宜人的&#xff1b;可接受的&#xff0c;适合的&#xff1b;欣然同意的 gleam v.发微光&#xff1b;闪烁&#xff1b;隐约闪光&#xff1b;&#xff08;在眼中&#xff09;闪现&#xff1b;表露 hypothesis n.[复数hypotheses] 假…

CSS3 filter滤镜

filter 顾名思义就是过滤的意思 使用 filter属性为元素(常用于<img>)带来视觉的效果 filter描述none默认值&#xff0c;没有效果。blur(px)给图像设置模糊效果。默认是0&#xff1b;这个参数可设置css长度值&#xff0c;但不接受百分比值。brightness(%)调整图像的对比…

main 方法演示 spring 注解方式获取 bean

文章目录一、环境准备1、创建 Java 工程&#xff0c;引入 spring 相关 jar 包2、构建 path二、开发1、准备一个 User 类2、编写配置类 BeanConfig3、编写测试类 SpringTest4、验证结果本文章使用 JavaSE 方式&#xff0c;创建含有 main 方法的类&#xff0c;演示如何用简单的方…

用天翎低代码做办公协同软件,简单高效

编者按&#xff1a;在数字办公流行的现在&#xff0c;办公协同软件是企业必备。本文分析了办公协同软件的作用&#xff0c;并指出了基于低代码平台实现的办公协同软件可以很好地满足企业需求&#xff0c;最后进行了相关案例展示。 关键词&#xff1a;私有化部署&#xff0c;数…

从云原生实战峰会,透视2023年云原生发展趋势

CSDN&#xff1a;如果用两个关键词来总结 2022 年云原生的发展态势&#xff0c;您会有哪些评价&#xff1f;——繁荣和普惠。“繁荣”代表当前云原生的技术和产品蓬勃发展&#xff1b;“普惠”代表云原生技术从互联网走向金融、零售、政企等行业&#xff0c;普惠千行百业构建丰…

MySQL学习笔记(十五)行锁表锁间隙锁临键锁共享锁排他锁死锁

行锁表锁间隙锁临键锁共享锁排他锁死锁手动提交行锁&#xff08;记录锁Record Locks&#xff09;表锁间隙锁&#xff08;Gap Locks&#xff09;临键锁&#xff08;Next-Key Locks&#xff09;共享锁&#xff0c;排他锁死锁查看事物&#xff0c;锁的命令死锁的4个必要条件模拟死…

odoo学习之security, menu, tree, form

security 在ir.model.access.csv中 id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink可以设置成 access_org_info,org.info,model_org_info,,1,1,1,1其中&#xff1a;access_org_info&#xff1a;access_为固定开头&#xff0c;org_info为模块…

Mol2Image: Improved Conditional Flow Models for Molecule to Image Synthesis

Mol2Image: 连接药物分子与细胞显微图像的条件流模型【2021CVPR】 Mol2Image: Improved Conditional Flow Models for Molecule to Image Synthesis paper&#xff1a;Improved Conditional Flow Models for Molecule to Image Synthesis | Papers With Code code&#xff1…

谷粒商城之高级篇(3)

2 商城业务 2.7 订单服务 2.7.22 创建业务交换机&队列 这里承接 知识补充篇 6 RabbitMQ 订单分布式主体逻辑 订单超时未支付触发订单过期状态修改与库存解锁 创建订单时消息会被发送至队列order.delay.queue&#xff0c;经过TTL的时间后消息会变成死信以order.release…

如何配置 SLO

前言 无论是对外提供 IaaS PaaS SaaS 的云公司&#xff0c;还是提供信息技术服务的乙方公司&#xff0c;亦或是金融 制造等各行各业的数据中心、运维部门&#xff0c;我们的一个非常重要的合同承诺或考核评估指标就是&#xff1a;SLA&#xff08;即&#xff1a;Service-Level …

蓝桥杯算法训练:FBI树(c++实现+图文详解)

文章目录FBI树C代码示例FBI树 题目要求 我们可以把由“0”和“1”组成的字符串分为三类&#xff1a;全“0”串称为B串&#xff0c;全“1”串称为I串&#xff0c;既含“0”又含“1”的串则称为F串。 FBI树是一种二叉树&#xff0c;它的结点类型也包括F结点&#xff0c;B结点和I…

某公司高管疾呼:底层程序员离职率太高,有人入职不到半年就走!建议把恶意离职加入征信,让年轻人对公司有起码的尊重!...

许多人入职后才发现公司有各种难以忍受的问题&#xff0c;入职时间很短就被迫离职&#xff0c;这本是无可奈何的事&#xff0c;但一位公司高管却对此非常愤怒&#xff0c;指出“目前基层程序员离职率太高&#xff0c;有人入职不到半年就走了”&#xff0c;还建议把恶意离职加入…

MVC开发模式

1、JSP演变历史 1、早期只有servlet&#xff0c;只能使用response输出标签数据&#xff0c;非常麻烦 2、后来又有了jsp&#xff0c;简化了Servlet的开发&#xff0c;但如果过度使用jsp&#xff0c;在jsp中即写大量的Java代码&#xff0c;又有HTML&#xff0c;会造成难以维护&am…