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

2024-08-23 09:20
文章标签 linux 操作 fd 描述符

本文主要是介绍Linux文件操作:文件描述符fd,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言:
  • 回顾一下文件
    • 提炼一下关于文件的理解:
  • 理解文件:
  • 通过系统调用操作文件:
    • 理解标志位传参:
    • 打开文件 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 0100void 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标准库,即可完成跨平台性!

这篇关于Linux文件操作:文件描述符fd的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1098977

相关文章

Ubuntu 24.04启用root图形登录的操作流程

《Ubuntu24.04启用root图形登录的操作流程》Ubuntu默认禁用root账户的图形与SSH登录,这是为了安全,但在某些场景你可能需要直接用root登录GNOME桌面,本文以Ubuntu2... 目录一、前言二、准备工作三、设置 root 密码四、启用图形界面 root 登录1. 修改 GDM 配

Linux进程CPU绑定优化与实践过程

《Linux进程CPU绑定优化与实践过程》Linux支持进程绑定至特定CPU核心,通过sched_setaffinity系统调用和taskset工具实现,优化缓存效率与上下文切换,提升多核计算性能,适... 目录1. 多核处理器及并行计算概念1.1 多核处理器架构概述1.2 并行计算的含义及重要性1.3 并

JSONArray在Java中的应用操作实例

《JSONArray在Java中的应用操作实例》JSONArray是org.json库用于处理JSON数组的类,可将Java对象(Map/List)转换为JSON格式,提供增删改查等操作,适用于前后端... 目录1. jsONArray定义与功能1.1 JSONArray概念阐释1.1.1 什么是JSONA

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

golang程序打包成脚本部署到Linux系统方式

《golang程序打包成脚本部署到Linux系统方式》Golang程序通过本地编译(设置GOOS为linux生成无后缀二进制文件),上传至Linux服务器后赋权执行,使用nohup命令实现后台运行,完... 目录本地编译golang程序上传Golang二进制文件到linux服务器总结本地编译Golang程序

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

linux解压缩 xxx.jar文件进行内部操作过程

《linux解压缩xxx.jar文件进行内部操作过程》:本文主要介绍linux解压缩xxx.jar文件进行内部操作,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、解压文件二、压缩文件总结一、解压文件1、把 xxx.jar 文件放在服务器上,并进入当前目录#

Linux系统性能检测命令详解

《Linux系统性能检测命令详解》本文介绍了Linux系统常用的监控命令(如top、vmstat、iostat、htop等)及其参数功能,涵盖进程状态、内存使用、磁盘I/O、系统负载等多维度资源监控,... 目录toppsuptimevmstatIOStatiotopslabtophtopdstatnmon