当前位置: 首页 > news >正文

[linux仓库]透视文件IO:从C库函数的‘表象’到系统调用的‘本质’

🌟 各位看官好,我是

🌍 Linux == Linux is not Unix !

🚀 今天来学习基础IO的相关知识,理解文件内容,重温C文件接口以及open系统调用!

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦!

文件的引入

#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>//父子进程会写时拷贝,通过fork,让子进程对父进程数据进行并发备份的代码
int grray[100];pid_t backup(const char *filename)
{pid_t id =fork();if(id==0){//子进程完成对数据的备份FILE *fp = fopen(filename,"w");for(int i=0;i<100;i++){fprintf(fp,"%d",grray[i]);}fclose(fp);exit(0);}return id;
}int main()
{srand(time(NULL)^getpid());//父进程未来的数据for(int i=0;i<100;i++){grray[i]=rand()%10;}//要求父进程对自己每一轮数据进行保存pid_t sub1 = backup("log1.txt");for(int i=0;i<100;i++){grray[i]=rand()%10;}pid_t sub2 = backup("log2.txt");for(int i=0;i<100;i++){grray[i]=rand()%10;}pid_t sub3 = backup("log3.txt");waitpid(sub1,NULL,0);waitpid(sub2,NULL,0);waitpid(sub3,NULL,0);return 0;
}

父进程创建子进程,让子进程执行一个全新的程序:子进程执行父进程代码的一部分。若父进程要修改数据,此时会发生写时拷贝。通过fork让子进程对父进程数据进行并发备份,完成了把数据备份到文件内容的要求。

基础IO -- 理解文件内容

背景补充

  • 文件 = 内容 +属性
  • 访问一个文件,都必须先把对应文件打开-->为什么呢?本质上是把文件加载到内存中
  • 如果一个文件没有被打开,那么它就在磁盘中
  • 我的文件被谁打开的? -->用户通过bash,创建子进程,让这个进程通过操作系统打开这个文件的。

因此一个文件分为两部分:

  1. 被打开的文件,该如何处理?
  2. 未被打开的文件,又是怎样做的?
  • 那么os内,一定同时存在大量的被打开的文件!!! --> 操作系统要不要管理这些被打开的文件呢?(文件是属于哪个进程的,文件权限是什么,是只可读还是都可以) --> 先描述,在组织!!!
  • 进程有task_struct,未来进程也有要打开的文件,因此平时研究打开文件,本质是研究:进程与文件的关系!!!

狭义理解

  • 文件在磁盘里
  • 磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的(相对内存是临时性存储介质)
  • 磁盘是外设(即是输出设备也是输⼊设备)
  • 磁盘上的⽂件 本质是对文件的所有操作,都是对外设的 输入和输出 简称 IO

广义理解

Linux 下⼀切皆文件(键盘、显示器、网卡、磁盘…… 这些都是抽象化的过程)(后⾯会讲如何去

理解)

文件操作的归类认知

  • 对于 0KB内容 的空⽂件是占⽤磁盘空间的
  • ⽂件是⽂件属性(元数据)和⽂件内容的集合(⽂件 = 属性(元数据)+ 内容)
  • 所有的⽂件操作本质是⽂件内容操作和⽂件属性操

系统角度

  • 对⽂件的操作本质是进程对⽂件的操作
  • 磁盘的管理者是操作系统
  • 文件的读写本质不是通过 C 语⾔ / C++ 的库函数来操作的(这些库函数只是为用户提供⽅便),⽽是通过⽂件相关的系统调⽤接⼝来实现的

回顾C文件接口

   //默认往当前路径创建文件并进行写入FILE *fp = fopen("log.txt","w");if(!fp){perror("fopen");return 1;}const char *str = "hello linux\n";//如果我们要想文件中写入一个字符串,strlen()+1? 不要!!!fwrite(str,strlen(str),1,fp);fclose(fp);

fopen默认从当前路径下新建文件,那么当前路径从哪来呢?有了前面学习,我们清楚是从cwd来的。那我的cwd又如何被拿到的呢?进程记录了cwd

如何查看当前正在运行进程的信息:

ls /proc/[进程id] -l

输出结论:打开文件,必须先找到文件,要找到文件,就必须知道该文件的路径+文件名,这也是为什么进程要有cwd的原因之一!!!

扩展:

#include<...>  和 #include"..." ,我们说过 "..." 会优先在当前路径下进行查找,那它是如何做到的?

编译器跑起来也是一个进程,是进程就需要记录自己的cwd。如果源文件目录找不到,编译器会检查启动编译器时所在的当前工作目录。

要打开文件,就需要知道文件路径+文件名,那么意味着我们只要更改工作路径,就可以把文件创建到其他路径并进行写入。

fopen接口

可以看到打开一个文件,既可以只进行读取,也可以只进行写入,又或者是读写,C语言库函数给我提供了诸多选项,接下来进行一一介绍。

"w"

不存在该文件进行创建,若存在该文件会将内容做清空再进行写入。

#include <stdio.h>
#include <string.h>int main()
{FILE *fp = fopen("log.txt", "w");if (!fp){perror("fopen");exit(1);}const char *str = "hello linux!\n";size_t n = fwrite(str, strlen(str), 1, fp);fclose(fp);return 0;
}

  

这个功能和我们之前学的 > 重定向是类似的,又或者说 > 的底层不就是"w"吗?

 "a"

不存在该文件进行创建,若存在该文件不会将内容做清空,进行追加内容。

这个功能和我们之前学的 >> 重定向是类似的,又或者说 >> 的底层不就是"a"吗?

"r" 

对文件内容进行读取。

输出信息到显示器的方法

引入

方法

  • putc
  • fwrite
size_t fwrite(const void ptr[restrict .size * .nmemb], size_t size, size_t nmemb, FILE*restrict stream); // 第一个参数表示是与类型无关的
  • fputs
  • fwrite
  • fprintf

C默认会打开三个输⼊输出流(其实就是打开三个文件),分别是stdin, stdout, stderr

仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,⽂件指针

扩展:为什么要打开呢?

  • 易用性:让程序无需复杂配置即可实现基本的输入输出。
  • 规范性:统一所有程序的 IO 接口,便于系统管理和用户操作。
  • 灵活性:通过重定向和分离错误流,适应多样化的应用场景(交互、批处理、日志记录等)。

向显示器上打印,本质就是向stdout上写入,也是一个文件,因此是向一个文件写入,因为stdout也是FILE*。

int main()
{const char *s1 = "hello printf\n";printf(s1);const char *s2 = "hello fprintf\n";fprintf(stdout,s2);const char *s3 = "hello fputs\n";fputs(s3,stdout);//fwrite不区分文本和二进制const char *s4 = "hello fwrite\n";fwrite(s4,strlen(s4),1,stdout);return 0;
}

系统文件IO

开⽂件的⽅式不仅仅是fopen,ifstream等流式,语⾔层的⽅案,其实系统才是打开⽂件最底层的方案。

传递标志位

学习系统⽂件IO之前,先要了解下如何给函数传递标志位,该⽅法在系统⽂件IO接⼝中会使⽤:

//位图传递标记位置
#define VERSION1 (1<<0) //1
#define VERSION2 (1<<1) //2
#define VERSION3 (1<<2) //4
#define VERSION4 (1<<3) //8
#define VERSION5 (1<<4) //16void ShowVersion(int flags)
{if(flags & VERSION1)printf("Version1\n");if(flags & VERSION2)printf("Version2\n");if(flags & VERSION3)printf("Version3\n");if(flags & VERSION4)printf("Version4\n");if(flags & VERSION5)printf("Version5\n");
}int main()
{ShowVersion(VERSION1); //显示版本printf("---------------\n");ShowVersion(VERSION1 | VERSION2 ); //显示版本printf("---------------\n");ShowVersion(VERSION1 | VERSION5); //显示版本printf("---------------\n");ShowVersion(VERSION1 | VERSION2 | VERSION5); //显示版本printf("---------------\n");ShowVersion(VERSION1 | VERSION2 | VERSION3 | VERSION4 | VERSION5); //显示版本printf("---------------\n");return 0;
}

open系统调用

int open(const char *pathname, int flags, .../* mode_t mode */ );

  • pathname: 文件路径+文件名 
  • flags:打开⽂件时,可以传⼊多个参数选项,⽤下⾯的⼀个或者多个常量进⾏“或”运算,构成flags。(32个比特位,一个比特位,一个标志位)
参数:就是宏,一个数字,只有一个比特位是1
  1. O_RDONLY: 只读打开
  2. O_WRONLY: 只写打开
  3. O_RDWR : 读,写打开(这三个常量,必须指定⼀个且只能指定⼀个)
  4. O_CREAT : 若⽂件不存在,则创建它。需要使⽤mode选项,来指明新⽂件的访问权限
  5. O_APPEND: 追加写
  • ... :如果文件不存在时。创建文件时可以给它设置权限(如果打开的文件本身存在,用两个参数的open即可)

返回值:

  • 成功:新打开的⽂件描述符
  • 失败:-1

O_RONLY

O_CREAT

我们也可以给一个文件加上权限:

int fd = open("log.txt",O_CREAT | O_RONLY,0666);

   

O_TRUNC

作用:清空文件内容 

C语言库函数的底层一定是这样做的。

O_APPEND 

作用:对文件内容进行追加

C语言库函数的底层一定也是这样做的。

扩展

问题1:为什么C语言要封装文件操作接口?为什么大部分的语言,都要对系统调用做封装?

  • 系统调用麻烦不是主要原因;
  • 支持跨平台性、可移植性。

为什么语言要跨平台?
一个平台,本质是数百万的用户!增加语言的竞争力!!!

问题2:为什么要学习系统调用?

系统调用是所有语言,文件操作的根,只要理解系统调用,其他语言的文件操作,就只剩下,熟悉操作即可!

总结

本文介绍了Linux基础IO相关知识,重点讲解了文件操作和系统调用。主要内容包括:

  1. 通过fork实现父子进程的数据备份;
  2. 文件的基本概念(内容+属性)和访问原理;
  3. C语言文件操作接口(fopen、fwrite等)的使用和原理;
  4. 系统调用open的使用方法及其参数标志位的传递方式。文章还探讨了C语言封装系统调用的原因(跨平台性)和学习系统调用的重要性(理解底层机制)。通过实例代码和概念解析,帮助读者深入理解文件IO操作的本质。

http://www.dtcms.com/a/354473.html

相关文章:

  • RSA+AES 混合加密不复杂,但落地挺烦,我用 Vue+PHP 封装成了两个库
  • XTUOJ C++小练习(素数的判断,数字塔,字母塔)
  • 亚马逊合规风控升级:详情页排查与多账号运营安全构建
  • Unity游戏打包——Android打包环境(Mac下)
  • PDF压缩如何平衡质量与体积?
  • Electron 简介:Node.js 桌面开发的起点
  • 小鹏自动驾驶的BEV占用网络有哪些优势?
  • “矿山”自动驾驶“路网”编辑功能实现
  • Mip-splatting
  • 在docker 中拉取xxl-job以及配置数据库
  • 【Linux】Linux基础开发工具从入门到实践
  • Redis 哨兵(Sentinel)全面解析
  • JavaSE丨集合框架入门:从0掌握Collection与List核心用法
  • Two Knights (数学)
  • Feign整合Sentinel实现服务降级与Feign拦截器实战指南
  • uni-app 网络请求与后端交互完全指南:从基础到实战
  • 智能养花谁更优?WebIDE PLOY技术与装置的结合及实践价值 —— 精准养护的赋能路径
  • 【LeetCode】29. 两数相除(Divide Two Integers)
  • PhotoshopImageGenerator:基于Photoshop的自动化图像数据集生成工具
  • C# 操作 DXF 文件指南
  • WAF对比传统防火墙的优劣势
  • 从Cgroups精准调控到LXC容器全流程操作​:用pidstat/stress测试Cgroups限流,手把手玩转Ubuntu LXC容器全流程​
  • 打破存储局限:CS 创世 SD NAND 如何优化瑞芯微(RK)与北京君正平台的贴片式 SD 卡性能
  • 横扫SQL面试——流量与转化率分类
  • 机器人电源电感的认证和认证细节,知多少?
  • Spring Boot 整合 SSE, http长连接
  • odoo打印新解
  • lesson48:Ubuntu下Python与三大数据库实战:MySQL、MongoDB、Redis全攻略
  • 基于uni-app的iOS应用上架,从打包到分发的全流程
  • 算法题打卡力扣第15题:三数之和(mid)