(20)Linux初始文件描述符
前言:本章我们介绍 O_WRONLY, O_TRUNC, O_APPEND 和 O_RDONLY。之后我们开始讲解文件描述符。
一、系统传递标记位
1、O_WRONLY
C 语言在 w 模式打开文件时,文件内容是会被清空的,但是 O_WRONLY 好像并非如此?
代码演示:当前我们的 log.txt 内有 5 行数据,现在我们执行下面的代码:
  1 #include <stdio.h>2 #include<string.h>3 #include <sys/types.h>4 #include <sys/stat.h>5 #include <fcntl.h>6 #include <unistd.h>  // 需引入头文件7 int main()8 {9       umask(0);   // umask现在就为0,听我的,别听操作系统的umask了10     int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);  // 八进制表示11     if (fd < 0) {12         perror("open");13         return 1;14     }15     printf("fd:%d\n",fd);16     int cnt=2;                                                                                                                                     17     const char* str="666666\n";                                                                                                           18     while(cnt--){                                                                                                                         19       write(fd,str,strlen(str));                                                                                                          20     }                                                                                                                                     21                                                                                                                                           22     close(fd);//关闭文件                                                                                                                  23                                                                                                                                           24     return 0;                                                                                                                             25 } 运行结果:

我们以前在 C 语言中,w 会覆盖把全部数据覆盖,每次执行代码可都是会清空文件内容的。
而我们的 O_WRONLY 似乎没有全部覆盖,曾经的数据被保留了下来,并没有清空!
其实,没有清空根本就不是读写的问题,而是取决于有没有加 O_TRUNC 选项!
因此,只有 O_WRONLY 和 O_CREAT 选项是不够的:
- 如果想要达到 w 的效果还需要增添 O_TRUNC
- 如果想到达到 a 的效果还需要 O_APPEND
2、 O_TRUNC 截断清空(对标 w)
在我们打开文件时,如果带上 O_TRUNC 选项,那么它将会清空原始文件。
如果文件存在,并且打开是为了写入,O_TRUNC 会将该文件长度缩短 (truncated) 为 0。
也就是所谓的 截断清空 (Truncate Empty) ,我们默认情况下文件系统调用接口不会清空文件的,
但如果你想清空,就需要给 open() 接口 带上 O_TRUNC 选项:
代码演示:让 open() 达到 fopen 中 "w" 模式的效果
  1 #include <stdio.h>2 #include<string.h>3 #include <sys/types.h>4 #include <sys/stat.h>5 #include <fcntl.h>6 #include <unistd.h>  // 需引入头文件7 int main()8 {9       umask(0);   // umask现在就为0,听我的,别听操作系统的umask了10     int fd = open("log.txt", O_WRONLY | O_CREAT|O_TRUNC, 0666);  // 八进制表示                                                                     11     if (fd < 0) {                                                                                       12         perror("open");                                                                                 13         return 1;                                                                                       14     }                                                                                                   15     printf("fd:%d\n",fd);                                                                               16     int cnt=2;                                                                                          17     const char* str="666666\n";                                                                         18     while(cnt--){                                                                                       19       write(fd,str,strlen(str));                                                                        20     }                                                                                                   21                                                                                                         22     close(fd);//关闭文件                                                                                23                                                                                                         24     return 0;                                                                                           25 } 运行结果:

然而 C 语言的 fopen 函数,只需要浅浅地标上一个 "w" 就能搞定了:
open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
fopen("log.txt", "w");3、O_APPEND 追加(对标 a)
C 语言中我们以 a 模式打开文件做到追加的效果。
现在我们用 open,追加是不清空原始内容的,所以我们不能加 O_TRUNC,得加 O_APPEND:
int fd = open("log.txt", O_WRONLY | O_CREATE | O_APPEND, 0666);运行结果如下:

再次追加:

我们再来对照 C 语言的 fopen,想做到这样的效果只需要一个 "a" :
open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
fopen("log.txt", "a");4、O_REONLY 读取
如果我们想读取一个文件,那么这个文件肯定是存在的,我们传 O_RDONLY 选项:
int main()
{umask(0);int fd = open("log.txt", O_RDONLY);if (fd < 0) {perror("open");return 1;}printf("fd: %d\n", fd);char buffer[128];ssize_t s = read(fd, buffer, sizeof(buffer) - 1);if (s > 0) {buffer[s] = '\0';  // 最后字符串序列设置为 '\0' printf("%s", buffer);}close(fd);   return 0;
}运行结果:
二、文件描述符(fd)
1、open 参数的返回值
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);我们使用 open 函数举的例子中,一直是用一个叫做 fd 的变量去接收的。
fopen 中我们习惯使用 fp / pf 接收返回值,那是因为是 fopen 的返回值 FILE* 是文件指针,
file pointer 的缩写即是 fp,所以我们就习惯将这个接收 fopen 返回值的变量取名为 fp / pf。
 那为什么接收 open 的返回值的变量要叫 fd 呢?
fd——————文件描述符
open 如果调用成功会返回一个新的 文件描述符 (file descriptor) ,如果失败会返回 -1 。

代码演示:我们现在多打开几个文件,观察 fd 的返回值
int main(void)
{int fd_1 = open("log1.txt", O_WRONLY | O_CREAT, 0666);int fd_2 = open("log2.txt", O_WRONLY | O_CREAT, 0666);int fd_3 = open("log3.txt", O_WRONLY | O_CREAT, 0666);int fd_4 = open("log4.txt", O_WRONLY | O_CREAT, 0666);int fd_5 = open("log5.txt", O_WRONLY | O_CREAT, 0666);printf("fd_1: %d\n", fd_1); printf("fd_2: %d\n", fd_2); printf("fd_3: %d\n", fd_3); printf("fd_4: %d\n", fd_4); printf("fd_5: %d\n", fd_5); close(fd_1);close(fd_2);close(fd_3);close(fd_4);close(fd_5);return 0;
}运行结果:

我们发现这 open 的 5 个文件的  (返回值) 分别是 
 ,那么问题了来了: 
为什么从 3 开始,而不是从 0 开始?0, 1, 2 去哪了?
 
 
系统接口认的是外设,而  标准库函数认的是: 
#include <stdio.h>extern FILE* stdin;
extern FILE* stdout;
extern FILE* stderr;系统调用接口!那么 stdin, stdout, stderr 和上面的 0,1,2 又有什么关系呢?
想解决这个问题,我们得先说说 :
我们知道,FILE* 是文件指针,那么  是什么呢?它是 
 库提供的结构体。
只要是结构体,它内部一定封装了多个成员!
虽然  用的是 FILE*,但是系统的底层文件接口只认 fd,也就是说:
 标准库调用的系统接口,对文件操作而言,系统接口只认文件描述符。
 因此,FILE 内部必定封装了文件操作符  !
下面我们来验证一下,先验证 0,1,2 就是标准 
代码验证:0 是标准输入 (stdin)
int main(void)
{// 验证 0,1,2 就是标准 I/Ochar buffer[1024];ssize_t s = read(0, buffer, sizeof(buffer) - 1);if (s > 0) {buffer[s] = '\0';printf("echo: %s", buffer);}
}运行结果如下:

代码验证:stdout 标准写入(1) 和 stderr 错误写入(2) :
 
 
至此,我们证明了:
0 (标准输入, stdin) ,1 (标准输出, stdout),2 (错误输出, stderr)
代码验证:下面我们就来证明 fd 的存在,证明 stdin, stdout 和 stderr 的对应关系
int main(void)
{printf("stdin: %d\n", stdin->_fileno);printf("stdout: %d\n", stdout->_fileno);printf("stderr: %d\n", stderr->_fileno);
}运行结果如下:
 
 
函数接口的对应:fopen / fclose / fread / fwrite : open / close / read / write
数据类型的对应:(FILE* → FILE) → 
 结论:我们用的  语言接口一定封装了系统调用接口!
2、文件描述符的底层理解
逻辑推导:进程:内存文件的关系 → 内存 → 被打开的文件实在内存里面的
一个进程可以打开多个文件,所以在内核中,进程与打开的文件之比为:1:n
所以系统在运行中,有可能会存在大量的被打开的文件 → OS 要对这些被打开的文件进行管理!
OS 如何管理这些被打开的文件呢?还是我们老生常谈的那句话:
先描述,再组织!
所以对我们来说,一个文件被打开不要片面的认为只是对文件内容动动手脚!
它还要 在内核中创建被打开文件的内核数据结构 —— 先描述
struct file {// 包含了你想看到的文件的所有大部分 内容 + 属性struct file* next;struct file* prev;
};如果你在内核中打开了多个的文件,那么系统会在内核中为文件创建一个 struct file 结构。
可以通过 next 和 prev 将其前后关联起来(内核的链表结构有它自己的设计,这里我们不关注)。
既然你打开了一个文件,就会创建一个 struct file,那么你打开多个文件,
系统中必然会存在大量的 struct file,并且该结构我们用链表的形式链接起来:
如此一来,对被打开的文件的管理,就转化成为了对链表的增删改查!
程如何和打开的文件建立映射关系?打开的文件哪一个属于我的进程呢?
在内核中,task_struct 在自己的数据结构中包含了一个 struct files_struct *files (结构体指针):
struct files_struct *files;而我们刚才提到的 "数组" 就在这个 file_struct 里面,该数组是在该结构体内部的一个数组。
struct file* fd_array[];该数组类型为 struct file* 是一个 指针数组,里面存放的都是指向 struct file 的指针!
数组元素映射到各个被打开的文件,直接指向对应的文件结构,若没有指向就设为 NULL。
此时,我们就建立起了 "进程" 和 "文件" 之间映射关系的桥梁。

如此一来,进程想访问某一个文件,只需要知道该文件在这张映射表中的数组下标。
上面这些就是在内核中去实现的映射关系了!这个下标 0,1,2,3,4 就是对应的文件描述符 fd
!
我们调用的 open / read / write / close 接口都需要 fd:
选号:当我们 open 打开一个新的文件时,先创建 struct file,然后在当前的文件描述表中分配一个没有被使用的下标,把 stuct file 结构体的地址填入 struct file* 中,然后通过 open 将对应的 fd 返回给用户,比如 3,此时我们的 fd 变量接收的 open 的返回值就是 3 了。
兑奖:后续用户再调用 read, write 这样的接口一定传入了对应的 fd,找到了特定进程的 files,在根据对应的 fd 索引到指针数组,通过 sturct file* 中存储的 struct file 结构体地址,找到文件对象,之后就可以对相关的操作了。
总结:其本质是因为它是一个数组下标,系统中使用指针数组的方式,建立进程和文件之间的关系。将 fd 返回给上层用户,上层用户就可以调用后续接口 (read, write...) 来索引对应的指针数组,找到对应文件,这就是 fd 为什么是 0,1,2... 的原因了!
3、初识 VFS(虚拟文件系统)
上面说的这种设置一套 struct file 来表示文件的内存文件系统的操作,
我们称之为  (virtual file system) ,即 虚拟文件系统 。

 
 
4、回头看问题:fd 的 0,1,2,3...
至此,我们梳理完了。现在我们再回过头看 fd 的 1,2,3,4... 就能有一个清楚的认识了。
现在我们再我们最开始的问题,想必大家已经做到 "知其然知其所以然" 了!
① 为什么从 3 开始,而不是从 0 开始?0, 1, 2 去哪了?
stdin,stdout,stderr 和 0,1,2 是对应关系,因为 open 时默认就打开了,这也是为什么我们默认打开一个新的文件,fd 是从 3 开始的而不是 0 开始的真正原因!
② 0, 1, 2, 3, 4……,是不是有点像数组下标?
  不是有点像,它其实上就是数组下标!fd 0,1,2,3,4...  在内核中属于进程和文件的对应关系,是用数组来完成映射的,这些数字就是数组的下标。read, write, close 这些接口都必须用 0,1,2,3,4 来找到底层对应的 struct file 结构,进而访问到底层对应的读写方法 (包括相关的属性,缓冲区等) 。
  
相关文章:
 
(20)Linux初始文件描述符
前言:本章我们介绍 O_WRONLY, O_TRUNC, O_APPEND 和 O_RDONLY。之后我们开始讲解文件描述符。 一、系统传递标记位 1、O_WRONLY C 语言在 w 模式打开文件时,文件内容是会被清空的,但是 O_WRONLY 好像并非如此? 代码演示&…...
 
draw.io基础操作和代码高效画图进阶
文章目录 一、基础操作1、链接2、等比例变形3、复制4、插入表格 二、在线打开三、插入—功能聚集地1、插入图片2、插入画笔3、插入布局4、导出 四、图码转换——高效画图1、通用图码转换2、流程图生成:使用mermaid语言生成图: 五、图码转换高效画图的典型…...
 
2024-01-04 用llama.cpp部署本地llama2-7b大模型
点击 <C 语言编程核心突破> 快速C语言入门 用llama.cpp部署本地llama2-7b大模型 前言一、下载llama.cpp以及llama2-7B模型文件二、具体调用总结 前言 要解决问题: 使用一个准工业级大模型, 进行部署, 测试, 了解基本使用方法. 想到的思路: llama.cpp, 不必依赖显卡硬件…...
 
HTTP打怪升级之路
新手村 上个世纪80年代末,有一天,Tim Berners-Lee正在工作,他需要与另一台计算机上的同事共享一个文件。他尝试使用电子邮件,但发现电子邮件不能发送二进制文件。Tim Berners-Lee意识到,他需要一种新的协议来共享二进制…...
 
axure RP9.0安装字体图标库fontawesome
字体图库地址: Font AwesomeThe internets icon library toolkit. Used by millions of designers, devs, & content creators. Open-source. Always free. Always awesome.https://fontawesome.com/v6/download进入后下载想要的版本如我是6.3 下载后得到压缩包,解压之后…...
 
PiflowX组件-ReadFromUpsertKafka
ReadFromUpsertKafka组件 组件说明 upsert方式从Kafka topic中读取数据。 计算引擎 flink 有界性 Unbounded 组件分组 kafka 端口 Inport:默认端口 outport:默认端口 组件属性 名称展示名称默认值允许值是否必填描述例子kafka_hostKAFKA_HO…...
keil 5 ARM CC编译错误和警告解释大全(3)序列号2000-3000
2001年:已声明虚拟参数,但从未使用过 2002年:虚拟参数重新定义为do变量 2003:无法优化:常量/表达式传递给可能修改的变量 2004:重新维度的数组作为参数传递 2005:重维度数组等价 2006&…...
 
CentOS 7 实战指南:文件或目录的权限操作命令详解
前言 这篇文章详细介绍了文件和目录的常用权限操作命令,并提供了全面的技术解析。通过本文,你将学习如何使用 chmod 和 chown 命令来管理文件和目录的权限,控制用户和用户组的访问权限。无论你是初学者还是有经验的系统管理员,这…...
 
我的第一个前端项目,vue项目从零开始创建和运行
入门前端,从基础做起,从零开始新建项目 背景:VUE脚手架项目是一个“单页面”应用,即整个项目中只有1个网页! 在VUE脚手架项目中,主要是设计各个“视图组件”,它们都是整个网页中某个部分&…...
【OJ】C++,Java,Python,Go,Rust
for循环语法 // cpp// java// python for i in range(集合): for i, val in enumerate(集合): for v1,v2,v3,... in zip(集合1,集合2,集合3,...):Pair // cpp pair<int, string> first second // java Pair<Integer, String> first() new Pair<>(firstVal…...
Flink 任务指标监控
目录 状态监控指标 JobManager 指标 TaskManager 指标 Job 指标 资源监控指标 数据流监控指标 任务监控指标 网络监控指标 容错监控指标 数据源监控指标 数据存储监控指标 当使用 Apache Flink 进行流处理任务时,可以根据不同的监控需求,监控…...
Go语言程序设计-第7章--接口
Go语言程序设计-第7章–接口 接口类型是对其他类型行为的概括与抽象。 Go 语言的接口的独特之处在于它是隐式实现。对于一个具体的类型,无须声明它实现了哪些接口,只要提供接口所必须实现的方法即可。 7.1 接口即约定 7.2 接口类型 package iotype …...
 
性能优化-OpenMP基础教程(二)
本文主要介绍OpenMP并行编程技术,编程模型、指令和函数的介绍、以及OpenMP实战的几个例子。希望给OpenMP并行编程者提供指导。 🎬个人简介:一个全栈工程师的升级之路! 📋个人专栏:高性能(HPC&am…...
 
让电脑变得更聪明——用python实现五子棋游戏
作为经典的棋类游戏,五子棋深受大众喜爱,但如果仅实现人与人的博弈,那程序很简单,如果要实现人机对战,教会计算机如何战胜人类,那就不是十分容易的事了。本文我们先从简单入手,完成五子棋游戏的…...
C#-接口
接口 (interface) 定义了一个可由类和结构实现的协定。接口可以包含方法、属性、事件和索引器。接口不提供它所定义的成员的实现 — 它仅指定实现该接口的类或结构必须提供的成员。 接口可支持多重继承。在下面的示例中,接口 IComboBox 同时从 ITextBox 和 IListBox 继承。 i…...
 
ASP.NET可视化流程设计器源码
源码介绍: ASP.NET可视化流程设计器源码已应用于众多大型企事业单位。拥有全浏览器兼容的可视化流程设计器、表单设计器、基于角色的权限管理等系统开发必须功能,大大为您节省开发时间,是您开发OA.CRM、HR等企事业各种应用管理系统和工作流系统的最佳基…...
 
景联文科技GPT教育题库:AI教育大模型的强大数据引擎
GPT-4发布后,美国奥数队总教练、卡耐基梅隆大学数学系教授罗博认为,这个几乎是用“刷题”方式喂大的AI教育大模型的到来,意味着人类的刷题时代即将退出历史舞台。 未来教育将更加注重学生的个性化需求和多元化发展,借助GPT和AI教育…...
 
PHP进阶-实现网站的QQ授权登录
授权登录是站点开发常见的应用场景,通过社交媒体一键授权可以跳过注册站点账户的繁琐操作。本文将讲解如何用PHP实现QQ授权登录。首先,我们需要申请QQ互联开发者账号获得APPID和密钥;接着,我们下载QQ官方SDK:PHP SDK v…...
 
字节跳动基础架构SRE-Copilot获得2023 CCF国际AIOps挑战赛冠军
近日,2023 CCF国际AIOps挑战赛决赛暨“大模型时代的AIOps”研讨会在北京成功举办,活动吸引了来自互联网、运营商、科研院所、高校、软硬件厂商等领域多名专家学者参与,为智能运维的前沿学术研究、落地生产实践打开了新思路。决赛中࿰…...
python moviepy 图文批量合成带字幕口播视频
最近在研究将图片和文本批量合成为带字幕口播视频 主要是基于python的moviepy库 from generator import audio, pics, subs, videodef main():texts_input examplepics_input example# 图片分辨率预处理pics.adjust(pics_input)# 文字转语音audio.text_to_audio(texts_inpu…...
 
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
 
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
 
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
 
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
 
2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
 
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
 
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
 
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

