文件缓冲区和stdio.h文件的模拟实现

缓冲区

背景知识认识

缓冲区就是一部分内存,由谁提供? -----(系统,C语言malloc,用户声明的变量)任何都能提供缓冲区
printf(“hello linux”);
sleep(3);
执行完sleep后才会输出printf,这段期间没有打印的信息就在缓冲区
1.为什么要这样做?

提高使用者的效率(丢给缓冲区就认为是完成了)
比如快递员将包裹放在快递站而不是送到家门口

    (使用者)      (缓冲区)

2.积累一部分再发送,可以提高效率

刷新策略(方式):

1.无缓冲(立即刷新)
2.行缓冲(行刷新)
3.全缓冲(缓冲区满了,再刷新)

特殊情况:

1.强制刷新
2.进程退出时,一般都要刷新

注:一般的显示文件-----行刷新
磁盘文件-----全刷新

样例及理解

请看两段代码和运行结果

#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main()
{
	fprintf(stdout, "fprintf:hello linux\n");
	printf("printf:hello linux\n");
	fputs("fputs:hello linux\n", stdout);

	const char* tmp = "write:hello linux\n";
	write(1, tmp, strlen(tmp));
	
	//fork();                                                                                       


	return 0;
}

结果:
在这里插入图片描述

#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main()
{
	fprintf(stdout, "fprintf:hello linux\n");
	printf("printf:hello linux\n");
	fputs("fputs:hello linux\n", stdout);

	const char* tmp = "write:hello linux\n";
	write(1, tmp, strlen(tmp));


	fork();                                                                                       


	return 0;
}

结果:
在这里插入图片描述

发现执行子进程后,C语言接口的指令被两次重定向到log.txt
而系统指令的显示只有一次

结论:
1.都是显示文件信息,所以都是文件行刷新,都是字符串文件,都有\n,在fork()之前都会刷新,包括systemcall
2.重定向到(磁盘)文件,是写入到文件,显示方式已经变成全刷新
3.全缓冲意味着缓冲区变大,当前数据是不足以让缓冲区写满,fork()调用的时候,数据仍然在缓冲区
4.现在谈的缓冲区和操作系统没有关系,底层调用的都是系统的write,但是现在这个问题却是跟C语言的函数有关
5.c/c++提供的缓冲区,里面一定保存的是用户的数据,属于当前进程运行时自己的数据.
6.如果把数据交给了OS,这个数据就属于OS,不属于用户
7.当进程退出后,都要进行缓冲区刷新,这个刷新也算写入操作,即在调用fork()时,任意一个进程在退出的时候,刷新缓冲区,就要发生写实拷贝
write系统调用,直接写入操作系统,不属于进程,也不发生写实拷贝.

总结:什么叫做刷新?

对于一个字符语句要写入文件时,将数据从语言写入操作系统
将数据通过语言直接写入OS代价高,效率低,引入缓冲区就能提高效率

在这里插入图片描述

从C语言至今,了解的缓冲区概念都是语言级别的缓冲区,即用户缓冲区
刷新就是把数据从语言缓冲区拷贝到操作系统的文件缓冲区中
所以:

样例中,C语言的打印信息出现两次是由于父子进程发生写实拷贝,父进程创建子进程时,会将数据拷贝给子进程,这样就会有两次打印的数据,而系统的打印是直接将数据传给文件缓冲

用户缓冲区,VS内核缓冲区

上述的缓冲区是c/c++语言提供的语言缓冲区(用户缓冲区)

怎么验证?这个缓冲区在哪里?

观察所有的C语言提供的文件操作都离不开FILE*这个结构体指针
这个FILE里面包含了fd,它里面也有一段缓冲区,即任何一个用C语言接口操作的文件都会被C语言提供一个语言缓冲区

.h文件中有声明

在这里插入图片描述

所以printf就能实现格式化输出scanf 格式化输入
键盘输入其实都是字符 %c %d是将数据格式化到需要的类型变量中
所以键盘,字符串这些叫做字符设备

C库函数的模拟实现

myFILE结构体的模拟实现

#pragma once

#define SIZE 4096
#define FLUSH_NONE 1
#define FLUSH_LINE (1<<1)
#define FLUSH_ALL  (1<<2)

typedef struct _myFILE
{
    int fileno;
    int flag;
    char buffer[SIZE];
    int end;
}myFILE;


extern myFILE *my_fopen(const char *path, const char *mode);
extern int my_fwrite(const char *s, int num, myFILE *stream);
extern int my_fflush(myFILE *stream);
extern int my_fclose(myFILE*stream);

my_fopen等函数的模拟实现

#include "mystdio.h"
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

#define DFL_MODE 0666

myFILE *my_fopen(const char *path, const char *mode)
{
    int fd   = 0;
    int flag = 0;
    if(strcmp(mode, "r") == 0)
    {
        flag |= O_RDONLY;
    }
    else if(strcmp(mode, "w") == 0)
    {
        flag |= (O_CREAT | O_TRUNC | O_WRONLY);
    }
    else if(strcmp(mode, "a") == 0)
    {
        flag |= (O_CREAT | O_WRONLY | O_APPEND);
    }
    else{
        // Do Nothing
    }
    if(flag & O_CREAT)
    {
        fd = open(path, flag, DFL_MODE);
    }
    else
    {
        fd = open(path, flag);
    }

    if(fd < 0)
    {
        errno = 2;
        return NULL;
    }

    myFILE *fp = (myFILE*)malloc(sizeof(myFILE));
    if(!fp) 
    {
        errno = 3;
        return NULL;
    }
    fp->flag = FLUSH_LINE;
    fp->end = 0;
    fp->fileno = fd;
    return fp;
}
int my_fwrite(const char *s, int num, myFILE *stream)
{
    // 写入
    memcpy(stream->buffer+stream->end, s, num);
    stream->end += num;

    // 判断是否需要刷新, "abcd\nefgh"
    if((stream->flag & FLUSH_LINE) && stream->end > 0 && stream->buffer[stream->end-1] == '\n')
    {
        my_fflush(stream);
    }

    return num;
}
int my_fflush(myFILE *stream)
{
    if(stream->end > 0)
    {
        write(stream->fileno, stream->buffer, stream->end);
        //fsync(stream->fileno);
        stream->end = 0;
    }

    return 0;
}
int my_fclose(myFILE*stream)
{
    my_fflush(stream);
    return close(stream->fileno);
}

演示

#include "mystdio.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
    myFILE *fp = my_fopen("./log.txt", "w");
    if(fp == NULL)
    {
        perror("my_fopen");
        return 1;
    }
    int cnt = 20;
    const char *msg = "haha, this is my stdio lib";
    while(cnt--){
        my_fwrite(msg, strlen(msg), fp);
        sleep(1);
    }
    my_fclose(fp);
    return 0;
}

欢迎三连,喜欢请留言讨论!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

温有情

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

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

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

打赏作者

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

抵扣说明:

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

余额充值