Linux文件操作:文件描述符fd

news2025/1/10 16:35:57

文章目录

  • 前言:
  • 回顾一下文件
    • 提炼一下关于文件的理解:
  • 理解文件:
  • 通过系统调用操作文件:
    • 理解标志位传参:
    • 打开文件 open
    • 写入信息 write
  • 理解文件描述符:
    • 对于open的返回值:
    • ==文件描述fd的本质是什么呢?==
  • 如何理解Linux中一切皆文件?
  • 打通系统调用和C语言函数

前言:

​ 现在我们对进程的总体概念也有了了解,下面我们进入新的模块学习。关于Linux如何操作文件。其关的操作也与进程有关。

回顾一下文件

​ 我们以前就使用C语言对文件进行读和写的操作,甚至说是追加append。下面代码就是一个最简单的使用C语言操作文件的代码:

#include <stdio.h>

int main()
{
    FILE* fp = fopen("log.txt", "w"); // 以写方式打开log.txt文件
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    fprintf(fp, "hello file");
    fclose(fp);
    return 0;
}

​ 最后的结果就是在当前目录文件夹下生成了对应的log.txt文件,这些我们在之前学习C语言的时候是有了解过的,我在这里也不必多说。但是我们现在是要从操作系统的角度来理解文件的,因此我们需要学习操作系统关于文件管理的操作。

​ 值得注意的是,这里以写方式操作文件
​ 1、如果文件不存在,就在当前路径下,新建指定文件。
​ 2、默认打开文件的时候,就会把里面的数据全部清空

提炼一下关于文件的理解:

文件 == 属性 + 内容

  • 首先,从语言层面(c语言,c++)来讲,我们是无法真正理解文件的。

    ——这是因为各个语言对应文件管理的接口不一样。因此我们要从操作系统的角度来学习。
    我们要进行文件操作,前提是我们的程序跑起来了,文件的打开和关闭,本质是CPU在执行我们的代码。

  • 操作文件,本质是进程在操作文件

    1. 文件在没有被打开的时候是存在于磁盘中的。
    2. 一个进程可以打开多个文件

    在很多情况下,操作系统内部一定存在大量被打开的文件,因此操作系统必须对打开的文件进行管理!!!
    谈及管理永远六个字:“先描述,在组织

    因此我们猜测,未来估计会有一个类似于task_struct的结构体对文件进行管理

理解文件:

  • 操作文件,本质是进程在操作文件。

  • 文件最是存在于磁盘之中,是外设硬件

  • 向文件写入 => 向硬件中写入

    单用户没有权限直接写入,因为OS是硬件的管理者,OS必须给我们提供系统调用(OS不相信任何人),但是对于fprintf / fscanf 等等C库函数,我们却可以向显示器 / 磁盘 等硬件中写入 / 读取。
    本质:我们用的C / C++都是对系统调用的封装(后面再谈封装)

通过系统调用操作文件:

理解标志位传参:

​ 我们先来看一份代码:

#include <stdio.h>

#define ONE 1             //  1   0000 0001
#define TWO (1 << 1)      //  2   0000 0010
#define FOUR (1 << 2)     //  4   0000 0100

void print(int num)
{
    if(num & ONE)
    {
        printf("1 ");
    }
    if(num & TWO)
    {
        printf("2 ");
    }
    if(num & FOUR)
    {
        printf("4 ");
    }
}

int main()
{
    print(ONE);
    printf("\n");
    
    print(TWO);
    printf("\n");
    
    print(FOUR);
    printf("\n");
    
    print(ONE | TWO);
    printf("\n");
    
    print(ONE | FOUR);
    printf("\n");
    
    print(TWO | FOUR);
    printf("\n");

}

最后的运行结果为:
image-20240822203053894

这需要我们理解按位或的操作,才能理解上述操作,接下来我们就来介绍系统调用函数

打开文件 open

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

int open(
	const char* pathname,  // 表示要打开的文件的所在路径,如果只有文件名则默认在当前路径
	int flags, 			  // 表示打开文件的方式
	mode_t mode            // 给文件赋予权限,可不提供该参数 (新文件权限随机,旧文件不变)
	);			

​ 我们在上述介绍的位图的概念,其实是为我们的参数做铺垫。对于第一个参数pathname不难理解,怎对于int flag,本质是用比特位来进行标志位的传递(OS设计了很多系统调用接口的常见方法)。传递方式就如同上述所讲的一致。

  • O_WRONLY -> 以写方式打开文件
  • O_CREAT -> 不存在则创建文件
  • O_TRUNC -> 如果文件已经存在,将原有的内容清空(截断)
  • O_APPEND -> 追加信息
  • O_CREAT -> 如果文件不存在,则创建它。需要第三个参数 mode 来指定新文件的权限。
  • O_EXCL -> 和 O_CREAT 一起使用时,如果文件已存在,则 open 失败,确保文件是新建的。
  • O_TRUNC -> 如果文件已存在且以写方式打开,则将文件内容清空。
  • O_APPEND -> 以追加的方式打开文件,写入的数据会添加到文件末尾。
  • O_NONBLOCK -> 对于设备文件,以非阻塞方式打开
  • O_SYNC -> 将写操作同步到磁盘。
  • O_DSYNC -> 类 O_SYNC,但只同步写入操作,不包括元数据的更新

代码演示:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

int main()
{
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0270); // 已权限270的方式创建
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    return 0;
}

image-20240822212004029

写入信息 write

#include <unistd.h>

ssize_t write(
	int fd,           // 文件描述符,表示要写入的是哪个文件
	const void *buf,  // 要写入到文件中的字符串的起始地址
	size_t count      // 要写入到文件当中的字节数
);

代码演示:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

int main()
{
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0270); // 已权限270的方式创建
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    
    const char* message = "hello system call -> write!\n";
    write(fd, message, strlen(message));
    
    return 0;
}

image-20240822212544165

在这里肯定也存在close关闭文件的,在这里我不做过多的赘述,不要忘记就好了。

理解文件描述符:

对于open的返回值:

​ 先看代码:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

int main()
{
    int fda = open("loga.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    int fdb = open("logb.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    int fdc = open("logc.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    
    printf("fda -> %d\n", fda);
    printf("fdb -> %d\n", fdb);
    printf("fdc -> %d\n", fdc);

    close(fda);
    close(fdb);
    close(fdc);
    return 0;
}

image-20240822213428312

​ 诶,在这里我们发现为什么我们创建新的文件后,对于open返回后的值是从3开始的呢?
不是都说程序员都是从0开始数数的吗,为什么不是从0,而是从3呢?

  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.

  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器

  • 你说的嘛,0 && 1 && 2是默认打开的,那么我们可以直接去找对对应的显示1号,直接写入,最后肯定也会直接打印在显示器上:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

int main()
{
    const char* message = "hello No.1 -> Monitor!\n";
    write(1, message, strlen(message));
    return 0;
}

image-20240822214814456

​ 结果也确实如此,这也说明了我们后面创建的文件标识符都是从3开始的。

文件描述fd的本质是什么呢?

问题就是凭什么我们可以直接对一个整形1写入,就可以在显示器显示?

image-20240822222245468

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

原理说白了就是:先描述,再组织

简单来说,open是在干嘛呢?

  1. 创建struct file
  2. 开辟文件缓冲区的空间,加载文件数据(这个过程可以延后)
  3. 查看进程的文件描述符
  4. 将struct file地址填入对应的表下标中
  5. 返回下标

所以,本质是文件映射关系的数组的下标。无论读写,都必须在合适的时候让OS把文件的内容读到文件缓冲区中。

如何理解Linux中一切皆文件?

​ 现在我们只是知道0 && 1 && 2 代表着键盘和显示器,可是这些是硬件啊,我们又该如何向这些硬件中写入和读取呢?

  • 如何使用C语言创建类?

    ​ 了解过C++的都知道,C++是一门面向对象的编程语言,而在C++中一切皆对象已经熟的不能再熟。为什么我们使用C++那么爱”类“?无非就是方便管理,正如老板肯定想的是如何赚大钱,那赚大钱的基础肯定是需要管理好手下的员工。正如使用C++目的是为了创建维护良好的项目,基础就是将数据代码管理好,因此也是需要管理!谈及管理,永远绕不开的六个字 —— 先描述,再组织
    ​ 类的出现就完美的实现了这六个字,不同于C语言创建的struct 对象,C++通过类创建出来的class 对象拥有灵活的“组织“功能,关键就是类能“描述”函数,函数就是方法,每个对象都有自己对应的方法!
    ​ 所以知道区别后,C语言想要实现C++的类,需要的正是“描述”函数,来创建动作。
    ​ 对于不同struct对象的函数,我们便可以使用函数指针来找到对应的函数,从而实现类的操作。

  • 深入剖析:

    image-20240822235724252

    ​ 对于每个硬件来说,都有自己独特的函数,例如read和write方式,但是硬件与硬件之间,甚至说型号与型号之间的read函数肯定不一样,然而我们并不需要关系这些区别,我们只负责使用就好!至于read函数是如何实现的,不必关心,因为在每个硬件都会收到对应的struct file进行管理,每个struct file内部都会有对应硬件的函数的函数指针,通过函数指针就能直接使用专门对应的函数

    不必关系底层实现,只负责使用。便是一切皆文件的意义!

    ​ 而我们又会发现,以上这种通过函数指针调用不同函数的方式,不就是我们在C++学习的多态吗!!!!

打通系统调用和C语言函数

​ 现在我们知道,系统在访问文件时,OS只认文件标识符fd。
image-20240823000619243

  • 那对于系统调用和C语言的库函数有什么关系吗?

    我们在使用C语言操作文件时,打开文件是使用函数fopen的,而函数fopen的返回值确实FILE*。
    FILE 本质是一个结构体,内部是会封装文件标识符的。

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    
    int main()
    {
        FILE* fp = fopen("log.txt", "w");
        printf("stdin-> %d\n", stdin->_fileno);
        printf("stdout-> %d\n", stdout->_fileno);
        printf("stderr-> %d\n", stderr->_fileno);
        printf("fp-> %d\n", fp->_fileno);
        fclose(fp);
        return 0;
    }
    

    image-20240823001610356

    FILE 本质是一个结构体,内部是会封装文件标识符的。而这个文件标识符就是_fileno。

    因为stdin、stdout和stderr的类型如下:

    extern FILE* stdin;
    extern FILE* stdout;
    extern FILE* stderr;
    

    所以我们也能打印他们对于的文件标识符,也和上面讲的一样,默认为0、1、2

  • 为什么要进行封装?

    ​ C语言的这些函数代码,是在我们配置环境时存在于自动下载的库当中,而这个库就是我们
    熟知的——C标准库。

    ​ 我们当然可以不用C语言的方式而使用系统调用来操作文件,但是不同平台具有不同的系统调用。windows有自己的一套,Linux有自己的,mac当然也有自己的。所以为了保证代码跨平台性我们建议使用C语言的方式来操作文件
    ​C语言在封装的时候,会使用条件编译来区分操作系统的类别:

    fopen()
    {
    #if windows
    	[...windows...]
    #elif mac
    	[...mac...]
    #elif linux
    	[...linux...]
    }
    

    然后通过这一份代码,针对不同的操作系统生成不同的C标准库,即可完成跨平台性!

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

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

相关文章

设计模式之Decorator装饰者、Facade外观、Adapter适配器(Java)

装饰者模式 设计模式的基本原则&#xff0c;对内关闭修改。 Decorator Pattern&#xff0c;装饰者模式&#xff0c;也叫包装器模式(Wrapper Pattern)&#xff1a;将一个对象包装起来&#xff0c;增加新的行为和责任。一定是从外部传入&#xff0c;并且可以没有顺序&#xff0…

Qml 实现仿前端的 Notification (悬浮出现页面上的通知消息)

【写在前面】 经常接触前端的朋友应该经常见到下面的控件&#xff1a; 在前端中一般称它为 Notification 或 Message&#xff0c;但本质是一种东西&#xff0c;即&#xff1a;悬浮弹出式的消息提醒框。 这种组件一般具有以下特点&#xff1a; 1、全局/局部显示&#xff1a;它不…

基于单片机的信号发生器设计

本设计采用了STM32F103C8T6单片机作为控制核心&#xff0c;通过控制DDS模块产生不同频率且高稳定和低失真的信号&#xff0c;再通过放大电路对信号的幅值进行放大。此外通过按键可以使用户对频率进行调节以及对输出波形进行切换&#xff0c;由于AD9833输出的幅值是固定的&#…

启动docker镜像

1、运行容器 2、当前运行的进程 3、当前位置和启动时间 4、cat/etc/redhat-release查看版本 5.镜像是模版&#xff0c;容器是实例 6.容器中没有命令运 7.容器总是能轻易获取 8.配置yum 9.安装http 10.修改index⽂件 11.httpd -k start 12.访问 13.退出就没有服务了 14…

细谈LCM驱动电压VGHVGL电路原理

前言&#xff1a; ***在液晶显示屏驱动电路中&#xff0c;VGH电压负责对TFT栅极电容进行充电开启&#xff0c;并使电容电压保持一个场周期&#xff0c;VGL电压负责TFT栅极的关闭。 如果VGH和VGL电压出现不稳或者幅度变化&#xff0c;都会引起图像显示故障&#xff0c;例如花屏…

委托发布 | 进迭时空联合移动云能力中心实现业界首个RISC-V IO虚拟化方案

仟江水商业电讯&#xff08;8月22日 北京 委托发布&#xff09;虚拟化是云计算技术基石&#xff0c;是RISC-V走进云计算等高性能计算场景的必然要求。RISC-V国际基金会2021年制定了Hypervisor 1.0规范&#xff0c;2023年制定了AIA 1.0规范和IOMMU 1.0规范&#xff0c;这3个规范…

CentOS 7使用RPM安装MySQL5.7

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言1&#xff1a;下载MySQL5.7的rpm安装包2&#xff1a;卸载已安装的MySQL&#xff08;没安装过则跳过&#xff09;3&#xff1a;MySQL安装环境准备4&#xff1a;安…

网络竞赛可视化:打造线上赛事

通过图扑网络竞赛可视化&#xff0c;可以实时跟踪和分析参赛者的表现&#xff0c;直观展示比赛进程和结果。这不仅提高了观赛体验&#xff0c;还帮助组织者更有效地管理和优化赛事。

STM32——SPI通信协议以及软件读写

1、SPI协议 SPI相对于I2C传输速度更快&#xff1b;设计简单&#xff0c;通信协议使用硬件线比较多&#xff0c;有些资源浪费 以下设备需要进行共地&#xff0c;如果从机没有独立的供电源&#xff0c;主机需要给供电 SS线低电平有效&#xff0c;主机只能选择一个从机 推挽输出…

Spring + Boot + Cloud + JDK8 + Elasticsearch 单节点 模式下实现全文检索高亮-分页显示 快速入门案例

1. 安装elasticsearchik分词器插件 sudo wget https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-8.13.4.zip sudo mkdir -p ./es_plugins/analysis-ik sudo mkdir ./es_data sudo unzip elasticsearch-analysis-ik-8.13.4.zip -d ./es_plugins/a…

SpringCloudAlibaba Seata分布式事务

分布式事务 事务是数据库的概念&#xff0c;数据库事务&#xff08;ACID&#xff1a;原子性、一致性、隔离性和持久性&#xff09;&#xff1b; 分布式事务的产生&#xff0c;是由于数据库的拆分和分布式架构(微服务)带来的&#xff0c;在常规情况下&#xff0c;我们在一个进…

自己动手写CPU_step4_逻辑运算|移位指令

序 上一篇中我们解决了流水线的数据相关问题&#xff0c;本篇将添加多条逻辑运算和移位运算指令&#xff0c;这些指令的格式严格按照MIPS的指令格式设计。 MIPS指令格式 由于本人也是处于学习的一个过程&#xff0c;如有不对之处&#xff0c;还请大牛指正。 就逻辑运算和移位运…

【软件逆向】第11课,软件逆向安全工程师之windows API函数,每天5分钟学习逆向吧!

资料获取 关注作者&#xff0c;备注课程编号&#xff0c;获取本课配套课件和工具程序。 干货开始-windows API函数。 微软官方提供的应用程序接口&#xff0c;是一些预先定义的函数&#xff0c;目的是提供应用程序与开发人员基于某软件或硬件提供的能力。 地址&#xff1a;h…

java基础 之 了解final

文章目录 定义使用及规则修饰类修饰方法修饰变量修饰成员变量修饰局部变量final与static共同修饰变量final修饰的变量和普通变量的区别 本篇文章代码就不附上了&#xff0c;建议大家实际敲一敲&#xff0c;更能加快理解 定义 final表示”最后的&#xff0c;最终的“含义&#…

精益思维赋能机器人行业的三大维度

在日新月异的科技浪潮中&#xff0c;机器人行业正以前所未有的速度蓬勃发展&#xff0c;成为推动产业升级与转型的关键力量。然而&#xff0c;如何在激烈的市场竞争中脱颖而出&#xff0c;实现高效、灵活与可持续的发展&#xff1f;精益思维&#xff0c;这一源自制造业的管理哲…

【el-switch更改高、宽、颜色样式】深入浅出element ui的switch同页面存在多个更改样式互不影响

1.技术&#xff1a; “vue”: “^2.6.14”, “element-ui”: “^2.15.6”, 2.需求&#xff1a; 同一个页面存在多个switch组件时&#xff0c; 需要更改各自的高度、宽度、选择颜色、非选中颜色等样式&#xff0c; 并且样式隔离互不影响&#xff01; 3.效果图&#xff1a; 4.重要…

C++动态规划(背包问题)

目录 一&#xff1a;动态规划是什么 二.动态规划的运用 &#xff08;1&#xff09;.用动态规划解决重复子问题 &#xff08;2&#xff09;.动态规划使用的条件与流程 Ⅰ.动态规划的使用条件&#xff1a; Ⅱ.动态规划的使用流程 &#xff08;3&#xff09;.背包问题 三.…

IO进程(学习)2024.8.22

信号 信号函数 信号处理函数 #include <signal.h> sighandler_t signal(int signum, sighandler_t handler); 功能&#xff1a;信号处理函数 参数&#xff1a;signum&#xff1a;要处理的信号 handler&#xff1a;信号处理方式 SIG…

基于矢量控制器的PMSM永磁同步电机速度控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于矢量控制器的PMSM永磁同步电机速度控制系统simulink建模与仿真&#xff0c;仿真输出电机转速跟踪曲线&#xff0c;PID控制器输出曲线以及Te输出曲线。 2.系统仿真结果 &…

kafka的一个有趣问题(BUG)

这是我的第104篇原创文章 问题由来 在使用kafka时&#xff0c;创建topic&#xff0c;对某个topic进行扩分区的操作&#xff0c;想必大家肯定都使用过。尤其是集群进行扩容时&#xff0c;对流量较大的topic进行扩分区操作。一般而言&#xff0c;期望的效果是&#xff1a;新扩的分…