(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 个文件的 \textrm{fd} (返回值) 分别是 3,4,5,6,7 ,那么问题了来了: 

为什么从 3 开始,而不是从 0 开始?0, 1, 2 去哪了?

 

系统接口认的是外设,而 C 标准库函数认的是: 

#include <stdio.h>
 
extern FILE* stdin;
extern FILE* stdout;
extern FILE* stderr;

系统调用接口!那么 stdin, stdout, stderr 和上面的 0,1,2 又有什么关系呢?

想解决这个问题,我们得先说说 \textrm{FILE}

我们知道,FILE* 是文件指针,那么 \textrm{FILE} 是什么呢?它是 C 库提供的结构体。

只要是结构体,它内部一定封装了多个成员!

虽然 C 用的是 FILE*,但是系统的底层文件接口只认 fd,也就是说:C 标准库调用的系统接口,对文件操作而言,系统接口只认文件描述符。

 因此,FILE 内部必定封装了文件操作符 \textrm{fd} !

下面我们来验证一下,先验证 0,1,2 就是标准 I/O

代码验证:0 是标准输入 (stdin) 

int main(void)
{
    // 验证 0,1,2 就是标准 I/O
    char 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) → \textrm{fd}

 结论:我们用的 C 语言接口一定封装了系统调用接口!

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 来表示文件的内存文件系统的操作,

我们称之为 \textrm{VFS} (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 结构,进而访问到底层对应的读写方法 (包括相关的属性,缓冲区等) 。
 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

H264码流进行RTP包封装

一.H264基本概念 H.264从框架结构上分为视频编码层&#xff08;VCL&#xff09;和网络抽象层&#xff08;NAL&#xff09;&#xff0c;VCL功能是进行视频编解码&#xff0c;包括运动补偿预测&#xff0c;变换编码和熵编码等功能&#xff1b;NAL用于采用适当的格式对VCL视频数据…

【竞技宝】DOTA2:梦幻联赛开战在即 中国区前两名将晋级正赛

北京时间2024年1月12日&#xff0c;近期DOTA2刚刚结束了别墅杯东南亚/中国区的封闭预选赛&#xff0c;而别墅杯的正赛还要等到下个月才会正式开打&#xff0c;而即将在明天开始进行的是梦幻联赛S22的中国区预选赛&#xff0c;除官方直邀的XG战队直接晋级正赛之外&#xff0c;其…

【Leetcode】2085. 统计出现过一次的公共字符串

文章目录 题目思路代码 题目 2085. 统计出现过一次的公共字符串 思路 使用两个哈希表 words1Count 和 words2Count 分别统计两个数组中每个单词的出现次数。然后遍历 words1Count 中的每个单词&#xff0c;如果该单词在 words1 中出现了一次&#xff0c;且在 words2 中也出…

深入理解计算机系统(2):信息的表示和处理

信息存储 大多数计算机使用 8 位的块&#xff0c;或者字节(byte)&#xff0c;作为最小的可寻址的内存单位&#xff0c;而不是访问内存中单独的位。机器级程序将内存视为一个非常大的字节数组&#xff0c;称为虚拟内存(virtual memory)。内存的每个字节都由一个唯一的数字来标识…

地表最强,接口调试神器Postman ,写得太好了!

postman是一款支持http协议的接口调试与测试工具&#xff0c;其主要特点就是功能强大&#xff0c;使用简单且易用性好 。 无论是开发人员进行接口调试&#xff0c;还是测试人员做接口测试&#xff0c;postman都是我们的首选工具之一 。 那么接下来就介绍下postman到底有哪些功…

连续多级主管

背景 组织中一般会有个直接主管&#xff0c;或者汇报主管&#xff0c;有的组织可能有多个主管&#xff0c;更有甚者一个人能可能在不同的业务项目中&#xff0c;这样这个人可能存在n个主管&#xff0c;这样在设计流程中就会衍生出很多问题来。一起看一款审批软件的设置&#x…

加入 The Sandbox,在「小王子:友谊与慈善的冒险」中踏上奇幻旅途

这次体验不仅是一次冒险&#xff0c;更是一次充满爱心的愉悦探索。此外&#xff0c;您购买的 NFT 还将用于慈善教育项目&#xff1a;它由安托万德圣埃克苏佩里青少年基金会资助&#xff0c;其中一部分销售收入将用于支持这个慈善机构。这次寓教于乐的旅程交织着对爱、友谊的思考…

MySQL比较运算符详解

MySQL比较运算符详解 一、常用的比较运算符二、比较运算符的使用方法2.1 等于运算符&#xff08;&#xff09;2.2 不等于运算符&#xff08;<>或!&#xff09;2.3 大于运算符&#xff08;>&#xff09;2.4 小于运算符&#xff08;<&#xff09;2.5 大于等于运算符&…

开源云真机平台-Sonic实际使用过程踩坑及解决方法(持续更新)

开源云真机平台-Sonic实际使用过程踩坑及解决方法(持续更新) 1、执行Python 自定义脚本时requests文件报错 Script stderrTraceback (most recent call last): File "D:TestToolssonic-agent-2.6 2-windows x86 64 onic-ent-v2.6.2-windows X86 64tes-utDut2b713b90-493-…

【MFC实践】基于MFC向导C++制作计算器(附文件)

一、写在前面1.1 什么是MFC向导&#xff1f;1.2 使用MFC向导制作计算器1.3安装visual studio 2022和MFC插件 二、设计计算器界面1.1 新创建MFC项目1.2 设计计算器界面1.3 添加相关变量1.4 算法的一些问题及解决方式1.5 计算功能的实现1.6 其它功能的实现1.6.1 DEL功能1.6.2 C置…

Linux——firewalld防火墙(二)

一、firewalld高级配置 1、IP地址伪装 地址伪装&#xff08;masquerade):通过地址伪装&#xff0c;NAT设备将经过设备的包转发到指定接收方&#xff0c;同时将通过的数据包的源地址更改为其自己的接口地址。当返回的数据包到达时&#xff0c;会将目的地址修改为原始主机的地址…

(26)Linux 进程通信之共享内存(共享储存空间)

共享内存是System V版本的最后一个进程间通信方式。共享内存&#xff0c;顾名思义就是允许两个不相关的进程访问同一个逻辑内存&#xff0c;共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一…

6.4、SDN在云数据中心的应用案例分析

云数据中心中的虚拟子网包含网关和IP网段,IP分配给各个服务器,服务器间能够互相通信或通过网关访问外部网络。 在SDN云数据中心内,用户可以随时订购任意网段的虚拟子网,而且这些子网是可以在不同用户之间复用的,也就是说,不同用户可以使用相同的私有网段。 SDN云数据中心…

Matlab 字符识别OCR实验

Matlab 字符识别实验 图像来源于屏幕截图&#xff0c;要求黑底白字。数据来源是任意二进制文件&#xff0c;内容以16进制打印输出&#xff0c;0-9a-f’字符被16个可打印字符替代&#xff0c;这些替代字符经过挑选&#xff0c;使其相对容易被识别。 第一步进行线分割和字符分割…

从“唯分论”到“过程评价” 助力教育高质量发展

近日,为推动教育评价改革工作高质量发展,山东省委办公厅、省政府办公厅印发《关于进一步推进教育评价改革工作的若干措施》,从学校评价改革、学生评价改革等6方面共提出25条举措,对教育评价改革进行了全面部署。 教育评价改革是教育改革的“牛鼻子”,为教育发展提供了明确的方…

CSS 中的伪装大师:伪类和伪元素的奇妙世界

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

开源大数据集群部署(四)Freeipa部署(kerberos+ldap)

作者&#xff1a;櫰木 1、 FreeIPA介绍 Kerberos协议只是一种协议标准的框架&#xff0c;而MIT Kerberos则是实现了该协议的认证服务&#xff0c;是Kerberos的物理载体。将它与Hadoop服务进行集成便能够很好地解决安全性不足的问题。 除了需要安装MIT Kerberos之外&#xff0…

调查问卷设计指南:提升数据收集与分析效果的实用技巧

想要做好一份调查问卷要明确哪几点&#xff1f; 一、问卷三要素 1、问卷主题 我们使用调查问卷法进行调查的时候&#xff0c;首先要明确主题是什么&#xff0c;是关于人员满意度调查、人员喜好类型调查还是其他主题。明确主题后我们才可以进行接下来的动作。 2、调查人群 明确问…

互联网 HR 眼中的好简历是什么样子的?

HR浏览一份简历也就25秒左右&#xff0c;如果你连「好简历」都没有&#xff0c;怎么能找到好工作呢&#xff1f; 如果你不懂得如何在简历上展示自己&#xff0c;或者觉得怎么改简历都不出彩&#xff0c;那请你一定仔细读完。 互联网运营个人简历范文> 男 22 本科 AI简历…

数据类型、数据类型转换(Java)

一、数据类型的分类 1. byte&#xff1a;1字节&#xff0c;-128~127 2. short&#xff1a;2字节&#xff0c;-32768~32767 3. int&#xff1a;4字节 默认整型 4. long&#xff1a;8字节 注意&#xff1a;随便写一个整型字面量会默认是整型的&#xff0c;所以我们在写一个…
最新文章