【Linux】模拟实现FILE以及认识缓冲区

刷新缓冲逻辑图

在这里插入图片描述

自定义实现

mystdio.h
#pragma once 
#include <stdio.h>

#define NUM 1024
#define BUFF_NOME 0x1
#define BUFF_LINE 0x2
#define BUFF_ALL 0x4

typedef struct _MY_FILE
{
  int fd;//接受描述符的值
  int flags;//用来记录打开方式
  char outputbuffer[NUM];//缓冲区保存
  int current;//记录缓冲区有多少字符
}MY_FILE;

MY_FILE* my_fopen(const char* path,const char* mode);
size_t my_fwrite(const void* ptr,size_t size,size_t nmemb,MY_FILE* stream);
int my_fclose(MY_FILE* fp);
int my_fflush(MY_FILE* fp);
mystdio.c
#include "mystdio.h"
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>

MY_FILE* my_fopen(const char* path,const char* mode)
{
  //1.识别标志位,打开方式
  int flag = 0;
  if(strcmp(mode,"r") == 0) flag |= O_RDONLY;
  else if(strcmp(mode,"w") == 0) flag |= (O_CREAT | O_WRONLY | O_TRUNC);
  else if(strcmp(mode,"a") == 0) flag |= (O_CREAT | O_WRONLY | O_APPEND);
  else if(strcmp(mode,"r+") == 0) flag |= (O_WRONLY | O_RDONLY);
  else if(strcmp(mode,"w+") == 0) flag |= (O_CREAT | O_WRONLY | O_RDONLY | O_TRUNC);
  else if(strcmp(mode,"a+") == 0) flag |=(O_CREAT | O_WRONLY | O_RDONLY | O_APPEND);
  //2.尝试打开文件
  mode_t m = 0666;
  int fd = 0;
  if(flag | O_CREAT)
  {
    fd = open(path,flag,m);
  }
  else 
  {
    fd = open(path,flag);
  }

  if(fd < 0) return NULL;

  //3.给用户返回MY_FILE对象,需要先进行构建
  MY_FILE *mf = (MY_FILE*)malloc(sizeof(MY_FILE));
  if(mf == NULL)
  {
    close(fd);
    return NULL;
  }

  //4.初始化MY_FILE对象
  mf->fd = fd;
  mf->flags = 0;
  mf->flags |= BUFF_LINE;
  memset(mf->outputbuffer,'',sizeof(mf->outputbuffer));
  mf->current = 0;

  //5.返回打开的文件
  return mf;
}

//冲刷缓冲区
int my_fflush(MY_FILE* fp)
{
  assert(fp);
  //将用户缓冲区的数据,通过系统调用接口,冲刷给os
  write(fp->fd,fp->outputbuffer,fp->current);
  fp ->current = 0;

  //fsync(fp-fd);
  return 0;
}

//这里返回的是字节数,不是模拟实现的输入的、个数nmemb
size_t my_fwrite(const void* ptr,size_t size,size_t nmemb,MY_FILE* stream)
{
  //1、缓冲区如果已经满了,就直接写入
  if(stream->current == NUM)my_fflush(stream);

  //2.根据缓冲区剩余情况,进行数据拷贝即可
  size_t user_size = size * nmemb;
  size_t my_size = NUM - stream->current;

  size_t write = 0;
  if(my_size >= user_size) 
  {
    memcpy(stream->outputbuffer + stream->current,ptr,user_size);
   //3.更新计数器字段
    stream->current += user_size;
    write = user_size;
  }
  else 
  {
    //如果缓冲区内存不够存放的话,指挥存放它的最大值
    memcpy(stream->outputbuffer+stream->current,ptr,my_size);
    stream->current += my_size;
    write = my_size;
  }
  //4.开始计划刷新,他们高效体现在哪里? --- TODO
  //不发生刷新的本质,不进行写入,就是不进行IO,不进行调用系统调用,所以my_fwrite函数调用会非常快,数据会暂时保存在缓冲区中
  //可以在缓冲区中挤压多份数据,统一进行刷新写入,本质:就是一次IO可以IO更多的数据,提高IO效率
  if(stream->flags & BUFF_ALL)
  {
    if(stream->current == NUM) my_fflush(stream);
  }
  else if(stream->flags & BUFF_LINE)
  {
    if(stream->outputbuffer[stream->current-1] =='n')
    {
      my_fflush(stream);
    }
  }
  else 
  {
    //TODO
  }
  return write;
}


int my_fclose(MY_FILE* fp)
{
  assert(fp);
  //1.冲刷缓冲区
  if(fp->current > 0) my_fflush(fp);

  //2.关闭文件
  close(fp->fd);

  //3.释放堆空间
  free(fp);

  //4.指针置NULL --- 可以设置
  fp = NULL;

  return 0;
}
main.c
#include "mystdio.h"
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#define MYFILE "log.text"
int main()
{
  MY_FILE* fp = my_fopen(MYFILE,"w");
  if(fp == NULL) return 1;

  const char* str = "hello my my_fwrite";
  int cnt = 10;
  //操作文件
  while(cnt)
  {
    char buffer[1024];
    snprintf(buffer,sizeof(buffer),"%s:%dn",str,cnt--);
    size_t size = my_fwrite(buffer,strlen(buffer),1,fp);
    sleep(1);
    printf("当前成功写入:%lu个字节n",size);
  }
    my_fclose(fp);

    return 0;
}

如何强制刷新内核缓冲区

根据文件描述符进行强制刷新

main.c
#include "mystdio.h"
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#define MYFILE "log.text"
int main()
{
  MY_FILE* fp = my_fopen(MYFILE,"w");
  if(fp == NULL) return 1;

  const char* str = "hello my my_fwrite";
  int cnt = 10;
  //操作文件
  while(cnt)
  {
    char buffer[1024];
    snprintf(buffer,sizeof(buffer),"%s:%dn",str,cnt--);
    if(cnt % 5 == 0)
    {
    	//当cnt是五的倍数的时候就会强制刷新一次
    	my_fwrite(buffer,strlen(buffer),1,fp);
    }
  }
    my_fclose(fp);

    return 0;
}

在这里插入图片描述

例子

像我们进行scanf输入的时候,其实本身我们输入的是一串字符串,将这个字符串读入对应的缓冲区buff后,然后通过分解工作,进一步传入系统,系统,系统在通过一些指令输入输出想要的结果