Linux 简单模拟实现C语言文件流
🌇前言
在
C语言
的文件流中,存在一个FILE
结构体类型,其中包含了文件的诸多读写信息以及重要的文件描述符fd
,在此类型之上,诞生了C语言
文件相关操作,如fopen
、fclose
、fwrite
等,这些函数本质上都是对系统调用的封装,因此我们可以根据系统调用和缓冲区相关知识,模拟实现出一个简单的C语言
文件流。
注意: 本文实现的只是一个简单的 demo
,重点在于理解系统调用及缓冲区
🏙️正文
1、FILE 结构设计
在设计 FILE
结构体前,首先要清楚 FILE
中有自己的缓冲区及冲刷方式
#pragma once
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<stdio.h>
#define flush_no 1 //无缓冲
#define flush_line 2 //行缓存
#define flush_all 4 //满了在缓冲
#define SIZE 1024typedef struct IO_FILE
{int fileno;int _flush;char inbuffer[SIZE];char outbuffer[SIZE];int out_pos;
}_FILE;void _fseek(_FILE*stream,int num,int f);_FILE* _fopen(const char*filename,const char*flag);int _fwrite (_FILE*fp,const char*s ,int len);void _fclose(_FILE* fp);void _fflush(_FILE* fp);size_t _fread(void*ptr,size_t size,size_t num, _FILE*stream );
2、函数使用及分析
主要实现的函数有以下几个:
fopen
打开文件fclose
关闭文件fflush
进行缓冲区刷新fwrite
对文件中写入数据fread
读取文件数据
实现的大致思路:
C语言文件流的过程。
FILE 结构体封装了用户级缓冲区(输入,输出)和文件指针,指向下一个读写的位置,还有文件标识符,等等。而fopen 是对open的封装
通过传入的读写方式打开对应的文件,在创建一个对应的结构体,初始化对应结构体的文件标识符,刷新方式。fwrite是对write的封装,它先是将要写入的数据写道FILE结构体封装的缓冲区里面,在判断是是否要刷新,如果要刷新就通过write写到对应的文件之中。fread封装的是read,它先是通过read将数据从文件中读到用户级缓冲区,然后在利用memcpy拷贝到目标数组中,这样可以减少拷贝次数。当fopen打开时采用“w+”的方式时,如果先向一个文件中写入一段数据,在调用fread读取的时候会发生问题,因为再写入时文件的指针在末尾,此时如果在去读的话,文件指针后面没有数据了,就会造成读取失败。在C语言中用户需要手动调用rewind() 或 fseek()函数将实现指针移动。fseek()是将lseek()函数进行封装,我们创建一个_fseek函数将系统接口 lseek()函数封装其中,将文件指针移动到文件的开始,也可以解决这个问题了。fclose在底层调用了close。在程序结束之前要对用户及缓冲区进行检查是否为空,不为空进行刷新。然后才可以将文件关闭。fflush函数封装的是write,它将缓冲区的数据写入文件中去。就这样我们可以实现一个简单的C语言文件流。实际上write写入应该写入到内核级缓冲区,但是我们无法实现这个,而且fflush可以对内核缓冲区进行刷新。
代码:
#include"mystdio.h"void _fseek(_FILE*stream,int num,int f)
{lseek(stream->fileno,num,f);
}_FILE* _fopen(const char* filename,const char* flag)
{assert(filename&&flag);//不能传空串int fd=0;int f=0;//打开方式if(strcmp(flag,"r")==0)//读{f=O_RDONLY;//系统级打开的参数fd=open(filename,f,0666);}else if(strcmp(flag,"r+")==0)//读写{f=(O_RDONLY|O_WRONLY);fd=open(filename,f,0666);}else if(strcmp(flag,"w")==0)//两者相等返回0{f= (O_CREAT|O_WRONLY|O_TRUNC);fd=open(filename,f,0666);}else if(strcmp(flag,"w+")==0)//读写{//printf("w+\n");f=(O_RDWR | O_CREAT | O_TRUNC);fd=open(filename,f,0666);}else if(strcmp(flag,"a")==0)//追加写{f=(O_CREAT|O_WRONLY|O_APPEND);fd=open(filename,f,0666);}else if(strcmp(flag,"a+")==0)//追加读写{f=(O_CREAT|O_WRONLY|O_APPEND|O_RDONLY); fd=open(filename,f,0666); }else if(strcmp(flag,"r")==0)//只读{f=O_RDONLY;fd=open(filename,f);}else return NULL;if(fd==-1) return NULL;_FILE* fdd=(_FILE*)malloc(sizeof(_FILE));if(!fdd)//创建失败{perror("eror:\n");exit(-1);}memset(fdd, 0, sizeof(_FILE));//初始化用户及缓冲区fdd->fileno=fd; //printf("fdd->fileno: %d\n", fdd->fileno)fdd->_flush=flush_line;fdd->out_pos=0;//printf("fdd->fileno: %d\n", fdd->fileno);return fdd ;
}int _fwrite(_FILE*fp,const char* s,int len)
{memcpy(&fp->outbuffer[fp->out_pos],s,len);//先写到用户及缓冲区,再看刷新方式fp->out_pos+=len;if(fp->_flush==flush_no)//不缓存{write(fp->fileno,fp->outbuffer,fp->out_pos);fp->out_pos=0;}else if(fp->_flush==flush_line)//行缓存 abcd\n 5 {if(s[len-1]=='\n'){// printf("行\n");write(fp->fileno,fp->outbuffer,fp->out_pos);fp->out_pos=0;}}else{//全缓冲,满了才刷到文件中if(fp->out_pos==SIZE){write(fp->fileno,fp->outbuffer,fp->out_pos);fp->out_pos=0;}} return len;
}void _fclose(_FILE* fp)
{assert(fp);if(fp->out_pos>0) _fflush(fp);//关闭之前先看用户及缓冲区close(fp->fileno); free(fp);
}void _fflush(_FILE* fp)//可以刷新内核级缓冲区
{if(fp->out_pos>0){write(fp->fileno,fp->outbuffer,fp->out_pos);//刷新就是写入fp->out_pos=0;//lseek(fp->fileno, 0, SEEK_SET);}
}size_t _fread(void*ptr,size_t size,size_t num, _FILE*stream)
{if(stream->out_pos>0) _fflush(stream);size_t readsize= size*num;//ptr的大小size_t buffersize=SIZE;//缓冲区的大小size_t total=0;if(buffersize>=readsize){ ssize_t ret=read(stream->fileno,stream->inbuffer,readsize);if(ret<0) {printf("不对\n");perror("ret\n");}memcpy(ptr,stream->inbuffer,ret);*((char*)ptr+ret)='\0';total=ret;//printf("%s\n",ptr);}else{while(total<readsize){size_t ret=read(stream->fileno,stream->inbuffer,buffersize);if(ret<0) break;memcpy((char*)ptr+total,stream->inbuffer,ret);total+=ret;*((char*)ptr+total)='\0';} }return total;
}
main.c
#include "mystdio.h"
#include<stdio.h>
int main()
{_FILE* fp=_fopen("log.txt","w+");//printf("file: %d", fp->fileno);const char* str="hello,world\n";_fwrite(fp,str,strlen(str));_fseek(fp,0,SEEK_SET);char ptr[10]="";_fread(ptr,1,5,fp);printf("%s\n",ptr);_fclose(fp);return 0;
}