Linux--缓冲区--用户级缓冲区的模拟--1017

1. 引入

#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

注意

  • 当我们fork()的时候,上面的函数已经被执行完了。但并不代表着数据被刷新出来了。

将文件输出重定向到 log.txt文件

bash: ./myfile.c>log,txt

bash: cat log.txt

 输出结果

hello write

hello printf

hello fwrite

hello printf

hello fwrite

同样的一个程序,向显示器打印时输出3行文本,向普通文件(磁盘)上打印时,变成了5行,其中C语言IO接口打印两次,系统IO接口打印一次。

分析

一定与fork()有关。

缓冲区一定不是操作系统提供维护的,如果是,上面的hello write也应该被刷新两次。


2. 缓冲区

什么是缓冲区?就是一段内存空间。

为什么要有缓冲区?避免频繁调用系统IO操作(更少次的外设的访问,和外设预备IO的过程是最耗费时间的),提高整机效率,提高用户的相应速度。

2.1 缓冲区的刷新策略

1、立即刷新

2、行刷新(行缓冲)   即\n

3、满刷新(全缓冲)

4、特殊情况:用户强制刷新(fflush)。进程退出。

一般而言,行缓冲的设备文件——显示器。全缓冲的设备文件——磁盘文件。

调用close(fd)就会直接关闭文件,而不会先刷新出缓冲区的数据。

2.2 原因

缓冲区绝对不是操作系统维护的,如果是,write的部分也该写入两次。

程序进行了重定向,要向磁盘文件打印。刷新策略由行刷新更新为满刷新,\n失去作用。

我们是在最后调用的fork(),此时函数绝对已经执行完成了,只是结果没有刷新到文件上。刷新是写入吗?是的。

在fork()之后,子进程中也保有父进程执行的数据,因为在进程结束时需要将缓冲区内容刷新到相应文件,此时发生了写时拷贝。父子进程都执行刷新操作,所以写入了两次。

而向显示器输出时,为行刷新,轮到fork()的时候,缓冲区内没有数据内容,也就不需要写入。所以C语言接口仅写入一次。

write本身就是系统调用接口,没有(用户级)缓冲区(但是有内核级缓冲区,进入该缓冲区,数据已经成为了内核数据,与进程无关),直接进行了刷新。

2.3 缓冲区的维护

上述操作不变,在代码行中,在fork()之前添加fflush(stdout);

打印结果又和输出内容到显示屏一样了。可这里的stdout是FILE*类型,虽然封装了fd,但是他怎么可以刷新缓冲区?

结论:我们上述所提及到的缓冲区是C标准库维护的!

struct FILE结构体不仅封装了文件描述符,同样包含了fd对应的语言层的缓冲区结构。

2.4 总结

  • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
  • printf fwrite 库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
  • 而我们放在缓冲区中的数据,没有被立即刷新。
  • 进程退出之后,缓冲区会统一刷新,写入文件。
  • 刷新也是写入的过程,所以fork后,子进程写入发生写时拷贝,子进程有了同样的 一份数据,随即产生两份数据。
  • write 没有变化,说明没有所谓的缓冲。

3. 用户级缓冲区的实现

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#define NUM 1024
struct Myfile 
{
	int fd;
	char buffer[1024];
	int end;//当前缓冲区的结尾
};
typedef struct Myfile Myfile;
Myfile* fopen_(const char* pathname, const char* mode)
{
	//保证 pathname 和mode 都不为空
	assert(pathname);
	assert(mode);
	Myfile* fp = NULL;//初始化fp
	if (strcmp(mode, "r") == 0)
	{
	}
	else if (strcmp(mode, "r+") == 0)
	{
	}
	else if (strcmp(mode, "w") == 0)
	{
		//先使用系统调用接口打开文件
		int fd = open(pathname, O_WRONLY | O_TRUNC | O_CREAT,0666);
		if (fd > 0)
		{
			//申请空间
			fp = (Myfile*)malloc(sizeof(Myfile));
			//初始化空间为0
			memset(fp, 0, sizeof(Myfile));
			fp->fd = fd;
		}
		//小于0 打开失败不进行任何操作 最后返回空指针
	}
	else if (strcmp(mode, "w+") == 0)
	{
	}
	else if (strcmp(mode, "a") == 0)
	{
	}
	else if (strcmp(mode, "a+") == 0)
	{
	}
	else
	{	
		//未知指令 什么都不做
	}
	return fp;
}
void fputs_(const char* message, Myfile* fp)
{
	assert(message);
	assert(fp);
	//从buffer的结束位置拷贝message,buffer内容逐次增长
	strcpy(fp->buffer + fp->end, message);
	fp->end += strlen(message);
	
	//打印给自己看的
	//不用写 显示每次输出的值是否正确 且连续
	printf("%s\n", fp->buffer);


	//判断输入有没有/n
	//这里部分暂时无法实现 只能体现刷新策略
	//刷新策略是用户通过执行C标准库中的代码逻辑,来完成刷新动作的
	if (fp->fd == 0)
	{
		//向 标准输入 的buffer输入
		//这里不写打开显示器的相关代码
	}
	else if (fp->fd == 1)
	{
		//向 标准输出 的buffrt输入
		if (fp->buffer[fp->end - 1] == '\n')// 123412/n/0
		{
			//有换行符号
			write(fp->fd, fp->buffer, fp->end);
			fp->end = 0;
		}
	}
	else if (fp->fd == 2)
	{
		//向 标准错误 的buffer输入
	}
	else
	{
		//其他文件
	}

}

void fflush_(Myfile* fp)
{
	assert(fp);
	if (fp->end != 0)
	{
		//有数据才要刷新
		write(fp->fd, fp->buffer, fp->end);
		syncfs(fp->fd);//将数据写入磁盘 
		fp->end = 0;//刷新好了 下次从头写入buffer缓冲区
	}
}
void fclose_(Myfile* fp)
{
	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 1;
	}
	fputs_("1:hello world ", fp);
	fputs_("2:hello world ", fp);
	fputs_("3:hello world ", fp);
	//这里会向屏幕打印 因为fputs_加了printf
	//只是为了给自己显示的看看
	//1:hello world 
	//1:hello world 2:hello world
	//1:hello world 2:hello world 3:hello world 
#if 0
	bash:cat log.txt
	//写入了 1 : hello world 2 : hello world 3 : hello world
#endif

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值