【Linux】文件基础IO

1.关于文件的共识原理

1.文件=内容+属性
2.文件分为打开的文件和没打开的文件
3.打开的文件:
文件被打开必须先被加载到内存,所以本质是研究进程和文件的关系,一个进程可以打开多个文件。操作系统内部一定存在大量被打开的文件,要进行管理就一定要先描述再组织,所以一个被打开的文件都必须有自己的文件打开对象,包含文件的很多属性
4.没打开的文件:
在磁盘上存放的,我们关心的问题是没有被打开的文件非常多如何被分门别类的放置好,如何进行高效的存储和增删查改

2.回顾C文件接口

w:写入之前会对文件进行清空处理,从头开始写
a:也是写入,在文件结尾追加写
C程序默认在启动时会打开三个标准输入输出流,stdin键盘文件、stdout显示器文件和stderr显示器文件

    1. 文件打开与关闭

fopen:打开文件,返回文件指针(FILE*)

在这里插入图片描述
filename:文件名(含路径)
mode:打开模式(常用:“r” 读,“w” 写,“a” 追加,“rb” 二进制读,“wb” 二进制写等)
返回值:成功返回文件指针,失败返回 NULL

fclose:关闭文件

在这里插入图片描述
stream:fopen 返回的文件指针
返回值:成功返回 0,失败返回非 0

    1. 文本文件读写

1.fgetc / fputc:读写单个字符
在这里插入图片描述

2.fgets / fputs:读写一行字符串
在这里插入图片描述

3.fscanf / fprintf:格式化读写
在这里插入图片描述

  • 3.二进制文件读写

fread / fwrite:块读写
在这里插入图片描述
ptr:数据缓冲区地址
size:每个元素的大小(字节)
nmemb:元素个数
返回值:成功读写的元素个数(nmemb)

  • 文件定位

fseek:移动文件指针
在这里插入图片描述
offset:偏移量(字节)
whence:基准位置(SEEK_SET 开头,SEEK_CUR 当前位置SEEK_END 末尾)

ftell:获取当前文件指针位置
在这里插入图片描述

rewind:将文件指针移到开头
在这里插入图片描述

    1. 错误处理

ferror:检查文件操作是否出错
在这里插入图片描述

feof:检查是否到达文件末尾
在这里插入图片描述

3.系统文件IO

  • 比特位方式的标志位传递方式
  1 #include<stdio.h>2 3 #define ONE (1<<0) //14 #define TWO (1<<1) //25 #define THREE (1<<2) //46 #define FOUR (1<<3)  //87 8 void show(int flags)9 {10     if(flags&ONE) printf("hellow function1\n");11     if(flags&TWO) printf("hellow function2\n");12     if(flags&THREE) printf("hellow function3\n");13     if(flags&FOUR) printf("hellow function4\n");14 }                                                                           15 16 int main()17 {18     printf("--------------------\n");19     show(ONE);// 只传 ONE → 000120     printf("--------------------\n");21     show(TWO); // 只传 TWO → 001022     printf("--------------------\n");23     show(ONE|TWO);// 传 ONE|TWO → 0011(同时开 ONE、TWO)24     printf("--------------------\n");25     show(ONE|TWO|THREE);// 0111(开 ONE、TWO、THREE)26     printf("--------------------\n");27     show(THREE|FOUR);//110028     printf("--------------------\n");29 }

1.宏定义部分:
每个宏对应一个独立的二进制位,可视为一个 “开关”(0 关,1 开)。
2.标志位的组合:show调用部分
用一个整数传递多个布尔状态,避免了用多个参数或结构体传递的繁琐。
3.标志位检查:&运算
判断某个标志位是否被设置(即是否为 1)。只要结果非 0,说明该位是 1。

优势:
1.高效简洁:
用一个整数代替多个布尔变量,节省参数传递成本(尤其适合大量标志位的场景)。组合和检查都通过位运算完成,执行速度极快(CPU 原生支持)。
2.可扩展性强:
新增标志位只需定义 (1<<n)(如 FIVE (1<<4)),无需修改 show 函数的参数或逻辑。支持任意组合(如 ONE|THREE|FOUR),无需额外代码。

3.1常用接口介绍

  • open

1.头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
2.函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
2.1参数:
pathname: 要打开或创建的目标文件路径,不加路径默认为当前路径
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
mode:
设置文件权限(八进制)
3.返回值:
成功:新打开的文件描述符
失败:-1
注意:open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。

  • read与write

定义在 <unistd.h> 头文件中
在这里插入图片描述
参数说明
fd:文件描述符(非负整数),标识要读取的文件 / 设备(如通过 open 打开的文件、管道、套接字等)。
buf:指向用户空间的内存缓冲区,用于存储读取到的数据(需预先分配足够空间)。
count:期望读取的字节数(size_t 类型,无符号整数)。一般为sizeof()-1
返回值
正数:实际读取到的字节数(可能小于 count,如文件剩余数据不足、非阻塞模式下数据未就绪等)。
0:到达文件末尾(无更多数据可读取)。
-1:读取失败,同时设置 errno 标识错误原因(如 EBADF 表示 fd 无效,EIO 表示 I/O 错误)。
关键特性与注意事项
1.无缓冲 I/O
read 和 write 是系统调用,直接与内核交互,不经过 C 标准库的缓冲区(与 fread/fwrite 不同)。每次调用都会陷入内核,性能开销相对较高。
2.文件指针自动移动
成功读写后,文件指针(维护在内核中)会自动向后移动,移动的字节数等于实际读写的字节数。例如:写入 10 字节后,指针指向第 10 字节位置,下次读取会从该位置开始。
3.对不同文件类型的行为差异
普通文件:读写按字节流顺序进行,可通过 lseek 调整指针位置实现随机访问。
管道 / 套接字:属于 “流式设备”,数据读写后会被消耗,无法通过 lseek 调整指针。
终端设备:通常为行缓冲模式,read 可能等待用户输入换行符后才返回。
4.错误处理
必须检查返回值,尤其是 -1 的情况,通过 perror 或 strerror(errno) 打印具体错误信息。

  • 测试代码
  1 #include<stdio.h>2 #include<unistd.h>3 #include<sys/types.h>4 #include<sys/stat.h>5 #include<fcntl.h>6 #include<string.h>7 8 int main()9 {10     umask(0);11     int fd=open("log.txt",O_RDWR|O_CREAT,0644);12     if(fd<0){13         perror("open");14         return 1;                                                           15     }16     const char*msg="hellow linux\n";17     write(fd,msg,strlen(msg));18 19     //将文件指针移回文件开头20     lseek(fd,0,SEEK_SET);21     char buf[1024];22     while(1)23     {24         ssize_t s=read(fd,buf,strlen(msg));                                 25          // 注意:read 不会自动添加 '\0',需要手动确保字符串结束26         if(s>0) buf[s]='\0',printf("%s",buf);27         else break;28     }29 30     close(fd);31     return 0;32 }

得出结论:
几乎所有的库只要是访问硬件设备,必定要封装系统调用,类似printf/scanf/fread/fwrite等语言层面的接口内部必定封装了系统调用接口
在这里插入图片描述
FILE类型是C库自己封装的结构体,里面一定封装了文件描述符

4.访问文件的本质

4.1文件描述符fd

在这里插入图片描述
文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体(文件描述符表)。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2.0,1,2对应的物理设备一般是:键盘,显示器,显示器。可通过代码验证,输入输出还可采取以下方式

  1 #include<stdio.h>2 #include<unistd.h>3 #include<sys/types.h>4 #include<sys/stat.h>5 #include<fcntl.h>6 #include<string.h>7                                                                             8 int main()9 {10     char buf[1024];//接收键盘的输入11     ssize_t s=read(0,buf,sizeof(buf));12     if(s>0)13     {14         buf[s]=0;15         write(1,buf,strlen(buf));16         write(2,buf,strlen(buf));17     }18     return 0;19 }

1.其中buf[s]=0;手动为read输入的字符串添加\0结束标志,避免strlen的使用错误(找到\0停止计算长度),read读取到什么输入什么不会自动添加\0.
2.再通过write输出流和错误流分别向显示器中输出,二者是不同的,可通过以下代码证明

在这里插入图片描述
在这里插入图片描述
1.该段代码关闭了标准输出流,导致三个printf语句都失败,因为printf默认输出到stdout
2.fileno函数是C标准库提供的获取流对应的文件描述符的标准写法
3.将 printf 函数的返回值存储到变量 n 中。printf 的返回值是成功打印的字符总数(包括数字、字母、标点、换行符等),最终输出12是因为printf认为自己打印成功,但标准输出流关闭所以没有显示其printf语句
4.fprintf(stderr, …) 向标准错误流输出,其文件描述符 2 未被关闭,因此能正常打印。
5.一个被打开的文件信息中要维护引用计数,可能多个fd指向一个文件如1、2都指向显示器,当引用计数变为0时说明没人用了就将文件中的数据刷新到磁盘中去然后关闭
通过对比,发现虽然标准输出流和错误流都是向显示器输出,但互相独立

结论:
1.已知C程序在启动时,会打开三个标准输入输出流文件,但并不是C语言本身的特性,而是操作系统的特性,因为进程会默认打开键盘、显示器、显示器,而所有语言都是运行在操作系统之上的,所以为了符合操作系统特性并在其上运行,各个语言也必须支持从而程序启动时打开三个标准输入输出流文件
2.新建的文件其文件描述符总是从3开始

4.2重定向本质

文件描述符分配规则:
从下标0开始寻找最小的没有被使用的数组位置,它的下标就是新文件的文件描述符。一般是close掉哪个文件描述符就给新建文件分配哪个,若没关闭正常创建就按顺序分配fd。

  • dup2系统调用接口实现重定向

dup2 是用于复制文件描述符的系统调用,定义在 <unistd.h> 头文件中,主要功能是将一个文件描述符 “重定向” 到另一个文件描述符,常用于实现输入输出重定向、管道等功能。在这里插入图片描述

参数说明
oldfd:已存在的文件描述符(源文件描述符),必须是有效的(未关闭的),否则调用失败。
newfd:目标文件描述符,即要被复制 / 重定向到的文件描述符。
核心功能
让 newfd 指向 oldfd 所对应的文件 / 设备,在files_struct中改变数组下标fd所对应的指针指向的内容,注意这个new和old并不是fd生成的先后关系
1.如果 newfd 已经打开,会先自动关闭 newfd(相当于先执close(newfd))。
2.将 newfd 复制为 oldfd 的副本,即 newfd 和 oldfd 指向同一个文件 / 设备,共享文件状态(如文件指针、权限等)。
3.成功后,操作 newfd 和操作 oldfd 会对同一个文件 / 设备生效。
返回值:
成功:返回 newfd(即目标文件描述符)。
失败:返回 -1,并设置 errno 标识错误(如 oldfd 无效时为 EBADF,newfd 超出合法范围时为 EINVAL)。
注意事项:
newfd 与 oldfd 相等时:dup2 不做任何操作,直接返回 newfd(因为无需复制)。
原子操作(不可被中断的操作):dup2 关闭 newfd 和复制的过程是原子的,避免了手close(newfd) 后、dup(newfd) 前被其他操作占用 newfd 的风险。
文件状态共享:newfd 和 oldfd 共享文件指针和状态,例如通过 oldfd 写入数据后,newfd 的文件指针也会同步移动。

Shell 中的输入重定向(<)、输出重定向(>)和追加重定向(>>)等操作,其底层实现本质上都依赖 dup2 系统调用

1.输出重定向测试:

 mytest.c  ⮀                                                    ⮂⮂ buffers 4 #include<sys/stat.h>5 #include<fcntl.h>6 #include<string.h>7 8 int main()9 {10     int fd=open("file.txt",O_CREAT|O_WRONLY,0666);11     if(fd<0){12         perror("open");13         return 1;14     }                                                                       15     dup2(fd,1);16     printf("fd:%d\n",fd);17     printf("hello linux\n");18     close(fd);19     return 0;20  }

在这里插入图片描述
最终输出结果并没有往显示器上打印,因为在维护文件信息的结构以下标fd为1的指针指向了新创建文件的fd而不是显示器,实现了输出重定向

2.追加重定向测试:

将输出重定向的代码做出以下更改
int fd = open(filename, O_CREAT|O_WRONLY|O_APPEND, 0666);添加一个O_APPEND追加选项,并且多执行两次打印语句,结果如下完成追加重定向
在这里插入图片描述

3.输入重定向测试

#include<stdio.h>2 #include<unistd.h>3 #include<sys/types.h>4 #include<sys/stat.h>5 #include<fcntl.h>6 #include<string.h>7 8 int main()9 { // 打开文件(只读模式,若文件不存在则open失败)11     int fd=open("file.txt",O_RDONLY);12     if(fd<0){13         perror("open");14         return 1;                                                           15     } // 打开文件(只读模式,若文件不存在则open失败)16     dup2(fd,0);17     printf("fd:%d\n",fd);18     char buf[1024];  // 初始化缓冲区为空,避免初始值干扰19     ssize_t n=read(0,buf,sizeof(buf)-1);20     if(n<0){21         perror("read");                                                     22         return 1;23     }24     buf[n]='\0';25     printf("echo# %s\n",buf);29     close(fd);30     return 0;31 }

在这里插入图片描述
打印结果并不是键盘输入得来,而是之前输出和追加重定向到文件中的内容得来

注意:
文件重定向的本质是修改进程的文件描述符表(内核为进程维护的打开文件映射关系),而进程替换(如exec系列函数)的作用是替换进程的代码段、数据段、堆栈等用户空间内容,但会保留进程的内核上下文(包括 PID、文件描述符表、信号掩码等)。所以重定向关系与进程替换无关

4.3区分标准输出与错误输出流

虽然标准输出流和错误输出流对应的都是显示器文件;
核心区别:stdout 是行缓冲 / 全缓冲,stderr 是无缓冲,导致输出时机、重定向到文件时的行为不同。
为什么要区分:可根据需求选择用 stdout(正常输出)还是 stderr(错误输出),并且利用重定向(1>/2>)分离日志,方便调试和维护。

测试代码:
在这里插入图片描述
在这里插入图片描述
还可以这样直接用fd重定向,标准输出重定向(1>),标准错误重定向(2>)该指令运行 mytest 程序后,会把程序正常的输出信息保存到 normal.log 文件中,把错误信息保存到 err.log 文件中。这样做的好处是:
1.便于查看和分析 2.保持终端整洁

还可以将二者合并输出
在这里插入图片描述
1.>all.log( 1>all.log 的简化写法):将程序的标准输出(stdout,文件描述符为 1) 重定向到 all.log 文件。若文件不存在则创建,若已存在则覆盖原有内容(想追加可改用 >>all.log )。
2.2>&1:将标准错误输出(stderr,文件描述符为 2) 重定向到 标准输出当前指向的目标 ,也就是这里的 all.log 文件。
2>&1 的作用是让 stderr “跟随” stdout 的去向

4.4自定义shell添加重定向功能

//宏定义增加,代表不同的fd#define NONE -1#define IN_RDIR     0#define OUT_RDIR    1                                                     #define APPEND_RDIR 2//全局变量增加,实现跨函数的数据共享和状态维持
char*rdirfilename=NULL;                                                   
int rdir =NONE;//增加检查重定向的函数,实现指令的分离和确定重定向
void check_redir(char*cmd){char*pos=cmd;while(*pos){if(*pos=='>'){                                                                 if(*(pos+1)=='>'){//将重定向符号置0,分割指令*pos++='\0';*pos++='\0';while(isspace(*pos)) pos++;rdirfilename=pos;rdir=APPEND_RDIR;break;}else{*pos++='\0';while(isspace(*pos)) pos++;rdirfilename=pos;rdir=OUT_RDIR;break;                                                        }}else if(*pos=='<'){*pos++='\0';while(isspace(*pos)) pos++;rdirfilename=pos;rdir=IN_RDIR;break;}else{// do nothing;}pos++;}}//交互函数增加重定向判断函数的调用
void interact(char *cline,int size){getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE"",getusername(),gethostname1(),pwd)      ;                                                                         char *s =fgets(cline,size,stdin);assert(s);(void)s;cline[strlen(cline)-1]='\0';check_redir(cline);}

其余代码见<<进程控制>>文章中部分

4.5理解Linux下一切皆文件

“文件” 的广义定义:资源的抽象

在 Linux 中,“文件” 被定义为 “可以被读取或写入的字节流”,它不局限于存储在磁盘上的文本 / 二进制数据,而是对所有 “可操作资源” 的抽象。无论是硬件设备(如键盘、硬盘)、进程间通信的管道,还是网络连接,都被视为一种 “文件”,并通过统一的文件系统接口进行操作。

“一切皆文件” 的核心:统一接口

Linux 通过文件描述符和一套标准系统调用来统一操作所有 “文件”,无论其实际类型是什么:
打开文件:open()(适用于所有文件类型,返回文件描述符)。
读取数据:read()(从文件描述符读取字节流)。
写入数据:write()(向文件描述符写入字节流)。
关闭文件:close()(释放文件描述符)。
开发者无需关心是磁盘文件还是外设文件,只需调用相同的接口

这种设计的优点:

简化系统复杂性:
用一套逻辑管理所有资源,避免为不同设备 / 功能设计独立接口。
统一权限控制:
通过文件权限(读r、写w、执行x)统一管理对资源的访问(例如限制普通用户操作/dev/sda硬盘)。
灵活扩展:
新增硬件或功能时,只需实现文件接口(如驱动程序),即可被系统接纳。
脚本与工具兼容性:
现有命令(如cat、grep、cp)可直接操作各种 “文件”,例如cat /proc/cpuinfo查看 CPU 信息,无需专门工具。

总结:
“一切皆文件” 是 Linux 对 “资源管理” 的高度抽象:它将硬件、进程、网络等所有可操作的实体,都封装成 “字节流” 形式的 “文件”,并通过统一的接口(用户层接口封装底层系统调用接口)和文件系统结构进行管理。这种设计不仅简化了系统实现,也让开发者能以一致的方式与各种资源交互,是 Linux 灵活性和扩展性的重要基础。体现了继承、封装、多态的思想

5.缓冲区

在这里插入图片描述
原理:
一共有两种缓冲区,分为用户级和内核级,用户级就是语言层面,每个语言都会提供一个自己的缓冲区。以C语言为例,C语言接口函数的打印内容都在自己所提供的缓冲区中,用户刷新的本质,就是将数据通过1(fd)+write写入到内核缓冲区中,在合适的时机操作系统会自动将内核缓冲区中数据刷新到磁盘或其他外设中去

现象解释1:

在这里插入图片描述
close(1)时,由于C语言接口函数写入到用户级缓冲区中的数据还没来得及调用write刷新到内核级缓冲区中,所以close时直接将内核级缓冲区中数据刷新到磁盘或其他外设中去。此时打印结果就只有write直接往内核级缓冲区中写入的内容

  • 补充内容

1.缓冲区的刷新问题
a:无缓冲—直接刷新
b:行缓冲—不刷新,直到碰到\n(典型:显示器)
c:全缓冲—缓冲区满了,才刷新(文件写入)
注意:进程退出时,也会刷新
2.为什么要有缓冲区?
a:解决效率问题(用户)

每刷新一次用户级缓冲区都调用一次write,系统调用接口都要和操作系统交互一次,所以并不是每次都调用最好,频繁的交互也会影响效率,所以才有不同的缓冲方式根据需求选择;
类比生活中的快递站,不是有人寄件就立马发出,这样成本太大,而是等发货量累计到一定程度再统一发送这样效率及成本最优

b:配合格式化

例如,在进行写入时,需要一个整数,但从键盘中输入的都是一个个字符,需要在缓冲区中将它们组成字符串的相应类型

现象解释2:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1.正常打印时就是逐语句打印,重定向时C语言接口都各被打印了两次,系统调用接口语句打印了一次,原因是,本来朝显示器打印缓冲方案是行缓冲模式,遇到\n就刷新,所以fork创建子进程前用户级缓冲区内的数据都被刷新到了内核级缓冲区去。
2.重定向后朝文件打印,缓冲模式变成全缓冲模式,遇到\n不再刷新,缓冲区写满才刷新,所以fork创建子进程后用户级缓冲区中内容被父子进程所共享,当进程要结束时会刷新用户级缓冲区,相当于共享的数据被修改,此时会发生写时拷贝,父子进程各持有一份用户级缓冲区中数据,通过write系统调用刷新到内核中,再由系统调度刷新到文件中,此过程父子进程都会执行,所以C语言接口函数的打印语句会打印两次,而系统调用write打印的语句只有一次,证明其直接写入内核中没有经历写时拷贝父子各执行一次

  • 加深理解FILE结构体
    文件操作离不开FILE结构体,里面一定包含fd文件描述符信息,还有对应打开文件的缓冲区字段和维护信息
    在这里插入图片描述
    每个文件都有自己的fd和缓冲区

5.1模拟实现C文件标准库

通过模拟实现,体会用户层(语言层)封装系统调用接口

  • 头文件Mystdio.h
//#pragma once
#ifndef __MYSTDIO_H__
#define __MYSTDIO_H__#include <string.h>#define SIZE 1024//通常作为_FILE结构体中flag成员的标志位使用
#define FLUSH_NOW 1//立即刷新,无缓冲
#define FLUSH_LINE 2//行缓冲
#define FLUSH_ALL 4//全缓冲typedef struct IO_FILE{int fileno;int flag; //char inbuffer[SIZE];//int in_pos;char outbuffer[SIZE]; // 用户级输出缓冲区数组,用于临时存储待输出的数据int out_pos;// 用户级输出缓冲区数组,用于临时存储待输出的数据
}_FILE;_FILE * _fopen(const char*filename, const char *flag);
int _fwrite(_FILE *fp, const char *s, int len);
void _fclose(_FILE *fp);

解析:
1.
#ifndef 是 “if not defined” 的缩写,意思是 “如果后面的标识符未被定义,则执行后续代码”。如果__MYSTDIO_H__未被定义(即头文件首次被包含),则执行此句,定义__MYSTDIO_H__标识符。这样,当该头文件再次被包含时,#ifndef判断会失败,从而跳过后续内容。
(MYSTDIO_H 是一个自定义的标识符(通常以头文件名大写加下划线组成,如头文件名为mystdio.h,则标识符常用__MYSTDIO_H__),用于标记该头文件是否已被包含过。)
作用:
避免因重复定义(如结构体、函数声明重复)导致的编译错误。
与#pragma once作用相同
2.
_FILE结构体:自定义文件缓冲管理结构,核心目的是实现用户级缓冲,减少系统调用次数,封装了文件描述符、缓冲区、刷新标志等核心信息,模仿了标准库FILE的设计思想。

  • 源文件Mystdio.c
#include "Mystdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>#define FILE_MODE 0666// "w", "a", "r"
_FILE * _fopen(const char*filename, const char *flag)
{assert(filename);assert(flag);int f = 0;int fd = -1;if(strcmp(flag, "w") == 0) {f = (O_CREAT|O_WRONLY|O_TRUNC);fd = open(filename, f, FILE_MODE);}else if(strcmp(flag, "a") == 0) {f = (O_CREAT|O_WRONLY|O_APPEND);fd = open(filename, f, FILE_MODE);}else if(strcmp(flag, "r") == 0) {f = O_RDONLY;fd = open(filename, f);}else return NULL;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(_FILE *fp, const char *s, int len)
{// "abcd\n"memcpy(&fp->outbuffer[fp->out_pos], s, len); // 没有做异常处理, 也不考虑局部问题fp->out_pos += len;if(fp->flag&FLUSH_NOW){write(fp->fileno, fp->outbuffer, fp->out_pos);fp->out_pos = 0;}else if(fp->flag&FLUSH_LINE){if(fp->outbuffer[fp->out_pos-1] == '\n'){ // 不考虑其他情况write(fp->fileno, fp->outbuffer, fp->out_pos);fp->out_pos = 0;}}else if(fp->flag & FLUSH_ALL){if(fp->out_pos == SIZE){write(fp->fileno, fp->outbuffer, fp->out_pos);fp->out_pos = 0;}}return len;
}void _fflush(_FILE *fp)
{if(fp->out_pos > 0){write(fp->fileno, fp->outbuffer, fp->out_pos);fp->out_pos = 0;}
}void _fclose(_FILE *fp)
{if(fp == NULL) return;_fflush(fp);close(fp->fileno);free(fp);
}

这段代码实现了一个简化版的 C 语言文件操作库,包含了_fopen、_fwrite、_fflush和_fclose四个核心函数,模仿了标准库中stdio.h的基本功能,重点体现了用户级缓冲区的设计与工作机制。
1.
_fopen:根据文件名和打开模式(“r”/“w”/“a”)打开文件,创建并初始化_FILE结构体(用户级文件管理结构)。
2.
_fwrite:将数据写入用户级缓冲区,并根据_FILE的flag判断是否需要刷新到内核。
3.
_fflush:将用户级缓冲区中未写入内核的数据强制刷新(无论是否满足刷新条件)。
4.
_fclose:关闭文件,释放缓冲区和结构体资源,确保数据不丢失。调用_fflush强制刷新缓冲区,确保剩余数据写入内核。调用close系统调用关闭文件描述符(释放内核资源)。用free释放_FILE结构体(释放用户态内存)。

  • 测试main.c
#include "Mystdio.h"
#include <unistd.h>#define myfile "test.txt"int main()
{_FILE *fp = _fopen(myfile, "a");if(fp == NULL) return 1;const char *msg = "hello world\n";int cnt = 10;while(cnt){_fwrite(fp, msg, strlen(msg));// fflush(fp);sleep(1);cnt--;}_fclose(fp);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/93493.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93493.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于微信小程序的生态农产销售管理的设计与实现/基于C#的生态农产销售系统的设计与实现、基于asp.net的农产销售系统的设计与实现

基于微信小程序的生态农产销售管理的设计与实现/基于C#的生态农产销售系统的设计与实现、基于asp.net的农产销售系统的设计与实现

Java研学-SpringCloud(五)

一 Nacos 配置中心 1 引入依赖 – services.pom每个微服务都需要<!--配置中心--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>2 配置文件 –…

.NET 中的延迟初始化:Lazy<T> 与LazyInitializer

标签&#xff1a;线程安全、延迟初始化、按需初始化、提升启动性能 项目地址&#xff1a;NitasDemo/12Lazy/LazyDemo at main Nita121388/NitasDemo 目录Lazy<T>1. 概念2. 基本用法 3. 异常处理 4. 线程安全模式 5. 示例1. 线程安全模式 (ExecutionAndPublication)2. 发…

【LLIE专题】LLIE低照度图像结构先验提取方法

Zero-Shot Day-Night Domain Adaptation with a Physics Prior&#xff08;ICCV,2021&#xff09;专题介绍一、研究背景二、方法1. 物理反射模型与颜色不变特征的推导&#xff08;原理推导、物理依据&#xff09;2. 颜色不变特征的计算&#xff08;特征计算公式整个过程&#x…

Font Awesome Kit 使用详解

在现代网页设计中&#xff0c;图标是提升用户体验的关键元素。而 Font Awesome 作为最受欢迎的图标库&#xff0c;其最新版本 Font Awesome 7 通过 Kit 功能提供了更便捷高效的集成方式。本文将带你全面了解如何使用 Font Awesome Kit&#xff0c;让你的网站图标管理变得轻松高…

第七十八章:AI的“智能美食家”:输出图像风格偏移的定位方法——从“滤镜病”到“大师风范”!

AI图像风格偏移前言&#xff1a;AI的“火眼金睛”——输出图像风格偏移的定位方法&#xff01;第一章&#xff1a;痛点直击——“画风跑偏”&#xff1f;AI生成艺术的“审美危机”&#xff01;第二章&#xff1a;探秘“画风密码”&#xff1a;什么是风格偏移&#xff1f;它藏在…

Android原生(Kotlin)与Flutter混合开发 - 设备控制与状态同步解决方案

Kotlin 原生实现 (Android) 1.1 AndroidManifest.xml <manifest xmlns:android"http://schemas.android.com/apk/res/android"package"com.afloia.smartconnect"><applicationandroid:name".MainApplication"android:label"Smart …

已开源:Highcharts.NET,Highcharts Android,与Highcharts iOS集成

近期了解到&#xff0c;Highcharts官方宣布将Highcharts.NET&#xff0c;Highcharts Android&#xff0c;与Highcharts iOS集成转换为开源。对于Highcharts提供世界一流的数据可视化工具&#xff0c;一直致力于将资源集中在可以为您提供最大价值的地方。官方提到&#xff1a;这…

KingbaseES:一体化架构与多层防护,支撑业务的持续稳定运行与扩展

声明&#xff1a;文章为本人真实测评博客&#xff0c;非广告 目录 引言 一、什么是KingbaseES&#xff1f; 二、KingbaseES核心特性 1. 一键迁移&#xff0c;极速性能&#xff0c;安全无忧​ 2. 性能强劲&#xff0c;扩展性强&#xff0c;助力企业应对大规模并发挑战​ …

scikit-learn/sklearn学习|广义线性回归 Logistic regression的三种成本函数

【1】引言 前序学习进程中&#xff0c;已经对线性回归和岭回归做了初步解读。 实际上&#xff0c; Logistic regression是一种广义的线性模型&#xff0c;在对线性分类的进一步学习前&#xff0c;有必要了解 Logistic regression。 【2】Logistic regression的3种成本函数 …

Tiptap(基于 Prosemirror)vs TinyMCE:哪个更适合你的技术栈?

在这之前&#xff0c;先来介绍一下 ProseMirror&#xff1a; 1. ProseMirror 是底层内核 定位&#xff1a;一个强大的 富文本编辑框架/引擎&#xff0c;不是一个成品编辑器。 作者&#xff1a;Marijn Haverbeke&#xff08;CodeMirror 作者&#xff09;。 核心思想&#xff1…

多墨智能-AI一键生成工作文档/流程图/思维导图

本文转载自&#xff1a;多墨智能-AI一键生成工作文档/流程图/思维导图 - Hello123工具导航 ** 一、AI 文档与视觉化创作助手 多墨智能是一款基于人工智能的在线工具&#xff0c;支持一键生成专业文档、流程图与思维导图&#xff0c;通过关键词输入快速完成内容创作&#xff0…

Kafka_Broker_副本基本信息

Kafka副本作用&#xff1a;提高数据可靠性 Kafka默认副本1个&#xff0c;生产环境一般配置为2个&#xff0c;保证数据可靠性&#xff0c;太多副本会增加磁盘存储空间&#xff0c;增加网络上数据传输&#xff0c;降低效率 Kafka中副本分为&#xff1a;Leader和Follower&#xff…

FreeRTOS 中的守护任务(Daemon Task)

在 FreeRTOS 中&#xff0c;守护任务&#xff08;Daemon Task&#xff09;是一个特殊的系统任务&#xff0c;主要用于管理软件定时器和其他后台操作。以下是关于 FreeRTOS 守护任务的详细信息&#xff1a; 守护任务的作用软件定时器管理&#xff1a; 当启用 configUSE_TIMERS 时…

博士招生 | 麻省理工学院 招收化学+人工智能方向 博士/博士后

内容源自“图灵学术博研社”gongzhonghao学校简介麻省理工学院&#xff08;MIT&#xff09;QS世界排名第1&#xff0c;是全球科技研究领域的顶尖学府。自成立以来&#xff0c;MIT以其卓越的科研和教育质量赢得了世界的尊敬。学校在科学、工程、经济和管理等多个领域具有深远的影…

云计算-OpenStack 实战运维:从组件配置到故障排查(含 RAID、模板、存储管理,网络、存储、镜像、容器等)

介绍 在云计算技术快速发展的背景下,OpenStack 作为开源的云计算管理平台,凭借其灵活性、可扩展性和强大的组件生态,成为构建私有云、公有云和混合云的重要选择。无论是云主机的创建与管理、存储方案的配置(如 RAID 阵列、Swift 对象存储、Cinder 块存储),还是网络编排、…

idea代码bug检测插件

代码检测工具&#xff08;插件&#xff09;推荐&#xff1a;Alibaba Java Coding Guidelines、CheckStyle、PMD、FindBugs、SonarLint。可以在idea中安装插件 让你在关注代码质量的同时&#xff0c;减少 code review 的工作量&#xff0c;提高 code review 的效率&#xff0c;…

Java String为什么要设计成不可变的?

大家好&#xff0c;我是锋哥。今天分享关于【Java String为什么要设计成不可变的?】面试题。希望对大家有帮助&#xff1b; Java String为什么要设计成不可变的? 超硬核AI学习资料&#xff0c;现在永久免费了&#xff01; Java中的String类被设计为不可变&#xff08;immut…

集成电路学习:什么是ORB方向性FAST和旋转BRIEF

ORB:方向性FAST和旋转BRIEF ORB(Oriented FAST and Rotated BRIEF)是一种在计算机视觉领域广泛应用的特征描述算法,它结合了FAST角点检测算法和BRIEF描述子算法的优点,以实现高效且具有旋转不变性的特征提取和匹配。以下是关于ORB算法的详细解析: 一、ORB算法概述 …

【langgraph基础入门】

1. LangGraph图结构概念说明在以图构建的框架中&#xff0c;任何可执行的功能都可以作为对话、代理或程序的启动点。这个启动点可以是大模型的 API 接口、基于大模型构建的 AI Agent&#xff0c;通过 LangChain 或其他技术建立的线性序列等等&#xff0c;即下图中的 “Start” …