重铸自身根基头像
关注
【linux】linux基础IO(四)(模拟实现c语言文件标准库fopen,fclose,fwrite,fflush)封面图

【linux】linux基础IO(四)(模拟实现c语言文件标准库fopen,fclose,fwrite,fflush)

小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
linux系列专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
在这里插入图片描述



前言

【linux】linux基础IO(三)(用户缓冲区概念与深刻理解)——书接上文 详情请点击<——,本文会在上文的基础上进行讲解,所以对上文不了解的读者友友请点击前方的蓝字链接进行学习
本文由小编为大家介绍——【linux】linux基础IO(四)(模拟实现c语言文件标准库fopen,fclose,fwrite,fflush)

本文是一个综合性比较强的文章,本文会综合以下三篇文章的知识进行讲解,如果不了解的读者友友请点击下面蓝色链接进行学习,通过本文,会将下面三篇文章的理论知识进行运用,帮助大家巩固知识,带来更好的学习效果

  1. 【linux】linux基础IO(一)(c语言文件接口、文件系统调用open,write,close、文件fd)详情请点击<——
  2. 【linux】linux基础IO(二)(文件的重定向,dup2的使用,给shell程序添加重定向,如何理解一切皆文件)详情请点击<——
  3. 【linux】linux基础IO(三)(用户缓冲区概念与深刻理解)详情请点击<——

一、模拟实现c语言文件标准库

接下来小编带领大家模拟实现以下c语言库的文件操作接口,分别有fopen,fclose,fwrite,fflush

准备工作

  1. 首先我们在当前路径下创建一个file目录文件,在这个目录文件中进行模拟实现c语言库的文件操作接口,并且在这个目录文件中创建出我们自己的main.c Mystdio.h Mystdio.c文件以及自动化构建工作makefile文件,我们在main.c文件中编写代码的主逻辑,在Mystdio.h中放函数的声明,在Mystdio.c中放函数的实现
    在这里插入图片描述
//makefile
myfile:main.c Mystdio.c
	gcc $^ -o $@ -std=c99

.PHONY:clean
clean:
	rm -f myfile
//main.c
#include "Mystdio.h"

int main()
{


  return 0;
}
  1. 值得我们养成的习惯上测试,完成一个模块的编写之后及时进行测试,这是一个好的代码编写的习惯,小编建议屏幕面前的读者友友也要及时养成,下面我们测试一下makefile是否可以正确完成项目的自动化构建的工作

运行结果如下,正确
在这里插入图片描述

main.c

  1. 这里小编为了将我们自己模拟实现的c语言文件操作接口和c语言标准库中的进行区分,所以小编会在函数前加上 _ 进行区分
    在这里插入图片描述
  2. 那么下面小编在mian.c中编写我们代码主逻辑的实现,即_fopen以只写的方式打开文件,循环式的间隔一秒(共3秒)通过_fwrite向文件中写入信息,_fclose关闭文件
#include "Mystdio.h"
#include <unistd.h>
#include <string.h>

#define filename "test.txt"

int main()
{
  _FILE* fp = _fopen(filename, "w");
  
  int cnt = 3;
  const char* message = "hello linux\n";

  while(cnt--)
  {
    _fwrite(message, strlen(message), 2, fp);
    sleep(1);
  }

  _fclose(fp);

  return 0;
}

Mystdio.h

  1. 首先在这个头文件中我们使用宏#ifndef xxx #define xxx #endif 意思是如果没有定义了xxx,那么就定义一下xxx,即此时#ifndef 和#endif中间的这部分内容就被执行,但是如果已经定义了xxx那么就不会去定义xxx了,这个xxx可以取名为_MYSTDIO_H_也可以设置为其它内容,即此时#ifndef 和#endif中间的这部分内容就不会被重复执行,所以使用这种方式可以防止头文件被重复包含
  2. 我们知道缓冲区有刷新方式,即无缓冲,行缓冲,以及全缓冲,其实在真正的fopen中应该可以去识别要打开硬件文件究竟是显示器文件(对应行缓冲)还是普通文件(对应全缓冲),但是这里小编为了简便的让大家理解,就将刷新方式写死了,后面如果想要进行修改在_fopen中进行修改即可,同时我们让无缓冲,行缓冲,以及全缓冲宏定义为1,2,4方便设置标志
  3. 下面就是_FILE结构体的实现,在这个_FILE结构体中应该包含诸多信息,但是小编这里仅仅挑选几个重要的信息进行包含,包含有fileno封装的底层文件描述符fd,以及当前用户缓冲区的刷新方式的标志位flag,以及输入缓冲区in_buffer,输入缓冲区的当前位置in_pos,例如scanf等从键盘读取的时候就会用到输入缓冲区,输入缓冲区的当前位置,这里小编将其注释掉就不带领大家模拟实现了,感兴趣的读者友友可以自行尝试一下,同时还要有输出缓冲区out_buffer,输出缓冲区的当前大小out_pos,那么既然是缓冲区,缓冲区就要有大小,所以我们将其宏定义一下SIZE为1024
    在这里插入图片描述
  4. 由于我们这个是头文件,所以仅需要进行函数的声明即可,那么仿照上面的c语言库的函数声明编写一个我们自己的函数声明即可
#ifndef _MYSTDIO_H_
#define _MYSTDIO_H_ 

#define SIZE 1024

#define FLUSH_NOW (1 << 0)  //1
#define FLUSH_LINE (1 << 1) //2
#define FLUSH_ALL (1 << 2)  //4


typedef struct FILE_IO
{
  int fileno;
  int flag;

  //char in_buffer[SIZE];
  //int in_pos;

  char out_buffer[SIZE];
  int out_pos;
}_FILE;


_FILE* _fopen(const char* path, const char* mode);
int _fwrite(const char* ptr, int size, int nmemb, _FILE* stream);
int _fflush(_FILE* stream);
int _fclose(_FILE* fp);

#endif 

Mystdio.c

其实这些c语言库的文件操作接口的实现算比较简单,底层封装对应的系统调用接口完成对应的功能,那么接下来就由小编带量大家封装一下系统调用,完成对应的函数功能

准备工作
  1. 如下是需要用到包含的头文件,以及一个宏定义FILE_MODE为0666,用作_fopen以"w"或"a"进行打开文件,文件不存在的时候,创建普通文件的默认权限
#include "Mystdio.h"
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

#define FILE_MODE 0666
_fopen
  1. c语言库的fopen打开文件有三种方式分别是"r" “w” "a"对应只读,只写,追加写,那么我们在我们自己的_fopen根据mode进行判断即可
  2. 首先进行assert断言一下不为空,接着我们知道系统调用open会返回打开文件的文件描述符fd,那么我们提前定义一下,并且巧妙的将这个fd设置为-1,如果经过判断"r" “w” "a"之后fd仍然为-1,那么说明用户传入的mode打开方式不正确,我们返回NULL
  3. 那么接下来我们判断一下"r" “w” “a”,如果mode为"r" 那么就是以只读方式打开,这时候我们进行open即可,类似的判断"w" "a"进行对应的open打开即可,如果读者友友关于系统调用open不熟悉,可以点击后方蓝字链接进行学习详情请点击<——
  4. 接下来我们就需要malloc在堆上动态申请一个_FILE大小的空间作为当前打开文件的_FILE对象,如果申请失败,那么返回NULL
  5. 接下来设置_FILE对象中的文件描述符fileno,文件的刷新方式flag,这里我们将文件的刷新方式设置为行刷新,便于后续我们进行测试,然后由于没有此时仅仅是打开文件,此时没有数据要写入输出缓冲区,所以初始化输出缓冲区的当前大小out_pos为0即可,返回_FILE对象即可
_FILE* _fopen(const char* path, const char* mode)
{
  assert(path);
  assert(mode);

  int fd = -1;
  //"r""w" "a"
  if(strcmp(mode, "r") == 0)
  {
    fd = open(path, O_RDONLY);
  }
  else if(strcmp(mode, "w") == 0)
  {
    fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, FILE_MODE);
  }
  else if(strcmp(mode, "a") == 0)
  {
    fd = open(path, O_CREAT | O_WRONLY | O_APPEND, FILE_MODE);
  }

  if(fd == -1)
  {
    return NULL;
  }

  _FILE* fp = (_FILE*)malloc(sizeof(_FILE));
  if(fp == NULL)
  {
    return NULL;
  }
  
  fp->fileno = fd;
  fp->flag = FLUSH_LINE;
  fp->out_pos = 0;

  return fp;
}
_fwrite
  1. 同样的我们assert断言一下,我们要实现的_fwrite是写入nmemb个大小为size的内存块ptr到输出缓冲区中,所以这里的nmemb需要以循环的形式进行,同时fwrite的返回值是写入块空间的个数,所以需要我们返回nmemb,那么这里我们对其拷贝一下为_nmemb,对_nmemb进行修改,返回值nmemb即可
  2. 接下里就是向输出缓冲区中写入_nmemb次大小为size的内存块ptr即可,这里我们使用memcpy进行写入,由于原本的输出缓冲区中可能还有数据,所以不能从输出缓冲区的开头开始写,而是应该在输出缓冲区的当前位置的下一个位置开始写,由于数组下标是从0开始写的,所以从输出缓冲区的大小处就是输出缓冲区的当前位置的下一个位置,&stream->out_buffer[stream->out_pos]位置开始写即可,同时写入完成一次对out_pos进行更新一次
  3. 接下来根据_FILE对象stream中的文件刷新方式按位与&上对应的刷新方式的宏就可以得出当前的刷新方式,当刷新方式为无缓冲的时候判断输出缓冲区的当前大小out_pos应该大于0才进行对应调用系统调用write进行刷新,当刷新方式为行缓冲的时候判断输出缓冲区的当前大小out_pos - 1的位置处应该有’\n’才进行对应调用系统调用write进行刷新,当刷新方式为全缓冲的时候判断输出缓冲区的当前大小out_pos应该等于缓冲区的总大小SIZE才进行对应调用系统调用write进行刷新,同时调用完成之后,及时将输出缓冲区的当前大小out_pos置为0,如果读者友友关于系统调用write不熟悉,可以点击后方蓝字链接进行学习详情请点击<——
int _fwrite(const char* ptr, int size, int nmemb, _FILE* stream)
{
  assert(ptr);
  assert(stream);
  
  int _nmemb = nmemb;
  while(_nmemb--)
  {
    memcpy(&stream->out_buffer[stream->out_pos], ptr, size);
    stream->out_pos += size;
  }

  if(stream->flag & FLUSH_NOW)
  {
    if(stream->out_pos > 0)
    {
      write(stream->fileno, stream->out_buffer, stream->out_pos);
      stream->out_pos = 0;
    }
  }
  else if(stream->flag & FLUSH_LINE)
  {
    //abcd\n
    if(stream->out_buffer[stream->out_pos - 1] == '\n')//未进行异常处理
    {												   //不考虑局部'\n'的问题
      write(stream->fileno, stream->out_buffer, stream->out_pos);
      stream->out_pos = 0;
    }
  }
  else if(stream->flag & FLUSH_ALL)
  {
    if(stream->out_pos == SIZE)
    {
      write(stream->fileno, stream->out_buffer, stream->out_pos);
      stream->out_pos = 0;
    }
  }

  return nmemb;
}
_fflush
  1. 首先断言一下传入的_FILE对象不为NULL
  2. _fflush的作用是立即刷新缓冲区,当前输出缓冲区的大小out_pos大于0的时候,我们才进行刷新,否则返回1表示刷新失败,那么刷新的方式也很简单,调用系统调用write传入对应的参数进行刷新即可,同时刷新完成将当前输出缓冲区的大小out_pos置为0即可,此时刷新成功返回0
int _fflush(_FILE* stream)
{
  assert(stream);
  if(stream->out_pos > 0)
  {
    write(stream->fileno, stream->out_buffer, stream->out_pos);
    stream->out_pos = 0;

    return 0;
  }

  return 1;
}
_fclose
  1. 首先断言一下传入的_FILE对象不为NULL
  2. _fclose的作用是冲刷缓冲区,关闭文件打开对象,释放_FILE对象
  3. 那么如何冲刷缓冲区,此时我们复用fflush即可
  4. 接下来使用系统调用close关闭文件打开对象,同时使用变量ret接收close的返回值作为_fclose的返回值即可
  5. free释放_FILE对象
int _fclose(_FILE* fp)
{
  assert(fp);

  _fflush(fp);
  int ret = close(fp->fileno);  
  free(fp);

  return ret;
}
测试一
  1. main.c中就是我们测试的主逻辑,此时测试的逻辑是行刷新,那么我们编译一下,运行myfile即可进行测试,同时我们复制ssh渠道,使用脚本命令检测test.txt文件即可
    在这里插入图片描述
//脚本命令
while :; do cat test.txt; sleep 1; echo "-----------------"; done
#include "Mystdio.h"
#include <unistd.h>
#include <string.h>

#define filename "test.txt"

int main()
{
  _FILE* fp = _fopen(filename, "w");
  
  int cnt = 3;
  const char* message = "hello linux\n";

  while(cnt--)
  {
    _fwrite(message, strlen(message), 2, fp);
    sleep(1);
  }

  _fclose(fp);

  return 0;
}

运行结果如下
在这里插入图片描述
行刷新无误,正确

测试二
  1. 那么我们将_fopen中的刷新方式修改为全缓冲FLUSH_ALL进行测试
_FILE* _fopen(const char* path, const char* mode)
{
  assert(path);
  assert(mode);

  int fd = -1;
  //"r""w" "a"
  if(strcmp(mode, "r") == 0)
  {
    fd = open(path, O_RDONLY);
  }
  else if(strcmp(mode, "w") == 0)
  {
    fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, FILE_MODE);
  }
  else if(strcmp(mode, "a") == 0)
  {
    fd = open(path, O_CREAT | O_WRONLY | O_APPEND, FILE_MODE);
  }

  if(fd == -1)
  {
    return NULL;
  }

  _FILE* fp = (_FILE*)malloc(sizeof(_FILE));
  if(fp == NULL)
  {
    return NULL;
  }
  
  fp->fileno = fd;
  //fp->flag = FLUSH_LINE;
  fp->flag = FLUSH_ALL;
  fp->out_pos = 0;

  return fp;
}
  1. main.c中的测试主逻辑保持不变
#include "Mystdio.h"
#include <unistd.h>
#include <string.h>

#define filename "test.txt"

int main()
{
  _FILE* fp = _fopen(filename, "w");
  
  int cnt = 3;
  const char* message = "hello linux\n";

  while(cnt--)
  {
    _fwrite(message, strlen(message), 2, fp);
    sleep(1);
  }

  _fclose(fp);

  return 0;
}

运行结果如下
在这里插入图片描述
前3秒test.txt中无数据,3秒后虽然缓冲区没有满,但是由于此时执行了_fclose会刷新缓冲区的数据,正确

测试三
  1. 此时我们仍然保持_fopen中的刷新方式为全缓冲不变,但是main函数中的循环我们每写入一次就手动调用_fflush刷新缓冲区,观察现象
#include "Mystdio.h"
#include <unistd.h>
#include <string.h>

#define filename "test.txt"

int main()
{
  _FILE* fp = _fopen(filename, "w");
  
  int cnt = 3;
  const char* message = "hello linux\n";

  while(cnt--)
  {
    _fwrite(message, strlen(message), 2, fp);
    _fflush(fp);
    sleep(1);
  }

  _fclose(fp);

  return 0;
}

运行结果如下
在这里插入图片描述
此时循环中每写入一次,虽然刷新方式仍然为全缓冲,但是我们手动将输出缓冲区的数据刷新一次,无误

测试四
  1. 保持_ffopen中的刷新方式为全缓冲不变,并且main函数中循环仍然是_fwrite写入数据后我们手动调用_fflush进行刷新数据,此时我们修改一下mian函数中打开文件的方式,修改为追加"a",并且将test.txt中的内容修改为xxx,观察是否可以正常追加
#include "Mystdio.h"
#include <unistd.h>
#include <string.h>

#define filename "test.txt"

int main()
{
  _FILE* fp = _fopen(filename, "w");
  
  int cnt = 3;
  const char* message = "hello linux\n";

  while(cnt--)
  {
    _fwrite(message, strlen(message), 2, fp);
    _fflush(fp);
    sleep(1);
  }

  _fclose(fp);

  return 0;
}

运行结果如下
在这里插入图片描述
可以在原来xxx的基础上进行追加写入,正确

二、源代码

makefile

myfile:main.c Mystdio.c
	gcc $^ -o $@ -std=c99

.PHONY:clean
clean:
	rm -f myfile

main.c

#include "Mystdio.h"
#include <unistd.h>
#include <string.h>

#define filename "test.txt"

int main()
{
  _FILE* fp = _fopen(filename, "a");
  
  int cnt = 3;
  const char* message = "hello linux\n";

  while(cnt--)
  {
    _fwrite(message, strlen(message), 2, fp);
    _fflush(fp);
    sleep(1);
  }

  _fclose(fp);

  return 0;
}

Mystdio.h

#ifndef _MYSTDIO_H_
#define _MYSTDIO_H_ 

#define SIZE 1024

#define FLUSH_NOW (1 << 0)  //1
#define FLUSH_LINE (1 << 1) //2
#define FLUSH_ALL (1 << 2)  //4


typedef struct FILE_IO
{
  int fileno;
  int flag;

  //char in_buffer[SIZE];
  //int in_pos;

  char out_buffer[SIZE];
  int out_pos;
}_FILE;


_FILE* _fopen(const char* path, const char* mode);
int _fwrite(const char* ptr, int size, int nmemb, _FILE* stream);
int _fflush(_FILE* stream);
int _fclose(_FILE* fp);

#endif

Mystdio.c

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

#define FILE_MODE 0666

_FILE* _fopen(const char* path, const char* mode)
{
  assert(path);
  assert(mode);

  int fd = -1;
  //"r""w" "a"
  if(strcmp(mode, "r") == 0)
  {
    fd = open(path, O_RDONLY);
  }
  else if(strcmp(mode, "w") == 0)
  {
    fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, FILE_MODE);
  }
  else if(strcmp(mode, "a") == 0)
  {
    fd = open(path, O_CREAT | O_WRONLY | O_APPEND, FILE_MODE);
  }

  if(fd == -1)
  {
    return NULL;
  }

  _FILE* fp = (_FILE*)malloc(sizeof(_FILE));
  if(fp == NULL)
  {
    return NULL;
  }
  
  fp->fileno = fd;
  //fp->flag = FLUSH_LINE;
  fp->flag = FLUSH_ALL;
  fp->out_pos = 0;

  return fp;
}

int _fwrite(const char* ptr, int size, int nmemb, _FILE* stream)
{
  assert(ptr);
  assert(stream);
  
  int _nmemb = nmemb;
  while(_nmemb--)
  {
    memcpy(&stream->out_buffer[stream->out_pos], ptr, size);
    stream->out_pos += size;
  }

  if(stream->flag & FLUSH_NOW)
  {
    if(stream->out_pos > 0)
    {
      write(stream->fileno, stream->out_buffer, stream->out_pos);
      stream->out_pos = 0;
    }
  }
  else if(stream->flag & FLUSH_LINE)
  {
    //abcd\n
    if(stream->out_buffer[stream->out_pos - 1] == '\n')
    {
      write(stream->fileno, stream->out_buffer, stream->out_pos);
      stream->out_pos = 0;
    }
  }
  else if(stream->flag & FLUSH_ALL)
  {
    if(stream->out_pos == SIZE)
    {
      write(stream->fileno, stream->out_buffer, stream->out_pos);
      stream->out_pos = 0;
    }
  }

  return nmemb;
}


int _fflush(_FILE* stream)
{
  assert(stream);
  if(stream->out_pos > 0)
  {
    write(stream->fileno, stream->out_buffer, stream->out_pos);
    stream->out_pos = 0;

    return 0;
  }

  return 1;
}


int _fclose(_FILE* fp)
{
  assert(fp);

  _fflush(fp);
  int ret = close(fp->fileno);  
  free(fp);

  return ret;
}

总结

以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2301_80751958/article/details/150774875

文章来源crawl

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--