【Linux从入门到精通】文件描述符详解

news2024/11/27 17:56:08

  

文章目录

一、引言 

二、引入文件描述符fd

2、1 观察fd的值

2、2 fd保存的位置

三、详解文件描述符fd

3、1 为什么要有文件描述符呢

3、2 到底什么是文件操作符呢

四、文件描述符的使用

4、1 验证文件描述符

4、1、1 验证stdin、stdout、stdout

4、1、2 验证fd值的大小顺序

4、2 输入输出重定向

4、2、1 dup2的使用 

 五、Linux下一切皆文件


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:Linux从入门到精通  👀

💥 标题:文件描述符💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️ 

一、引言 

  在Linux操作系统中,文件描述符是一种用于访问文件或输入/输出资源的抽象概念,它是为了更有效地管理和操作文件、设备、套接字等资源而引入的。文件描述符的作用和重要性在操作系统和编程中具有深远意义。

  通过 文件操作(C语言vs系统调用)上篇文章 文件操作(C语言vs系统调用) 对系统调用 open的讲解后,我们知道文件描述符是一个整数。当时它代表的含义是什么呢?又有什么意义呢?本篇文章会对文件描述符进行详细解释。

二、引入文件描述符fd

2、1 观察fd的值

  我们学习系统调用后,知道open调用完之后,会返回一个整型的值,该值就是文件描述符。那我们不妨打印出来观察一下fd值的大小和规律。代码如下:

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
    
int main()    
{    
    int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-    
    printf("open success, fd: %d\n", fd1);    
    int fd2 = open("log2.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-    
    printf("open success, fd: %d\n", fd2);    
    int fd3 = open("log3.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-    
    printf("open success, fd: %d\n", fd3);    
    int fd4 = open("log4.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-    
    printf("open success, fd: %d\n", fd4);    
    
    
    close(fd1);    
    close(fd2);    
    close(fd3);    
    close(fd4);                                                                                                                                              
    return 0;    
} 

  上述代码就是打开了四个文件,然后打印他们的文件描述符fd的值。我们看输出结果:

  我们惊奇的发现,文件描述符的值好像是有规律的!从上述打印的结果观察出,fd的值是从3开始,依次往上加一递增的。那么问题来了:为什么从3开始呢?有0、1、2吗?

  我们在学习C语言时,可能会听说过:当我们运行程序时,系统会默认帮我们打开标准输入、标准输出、标准错误。那标准输入、标准输出、标准错误到底是什么呢?

  首先我们要知道Linux下,一切皆文件。难道显示器和键盘也是文件吗?答案是是的!!(后面我们也会对Linux下一切皆文件进行讲解)没错,当程序运行起来时,系统会默认帮我们打开三个标准文件的!而这三个文件分别对应的fd值就是0、1、2

2、2 fd保存的位置

  我们知道当程序运行起来时,系统会默认帮我们打开三个标准文件后,那么这三个文件描述符fd的只会被保存起来的。如果不保存起来,怎么向标准输出,也就是显示器打印值呢?或者又怎么从标准输入,也就是键盘读取值呢?所以他们的fd值一定会被保存起来的。那么问题来了,这个值保存在了哪里了呢?

  上述调用fopen时,返回值为FILE* ,FILE是什么类型呢?FILE就是一个结构体!fd其实就是保存在了一个结构体,该结构体就是一个文件结构体FILE(struct file)。过海结构体中包含的文件的大部分信息。后面我们也会给出具体的详细解释。

三、详解文件描述符fd

3、1 为什么要有文件描述符呢

  到这里会一直有疑问:到底什么是文件描述符呢(就是一个整数吗)?为什么要有文件描述符呢?接下来会详细解释。

  首先,一个进程可能会打开很对文件。上面的代码中就打开了四个文件,如果想的话甚至可能会更多。内存中不仅仅只有一个进程吧!那么就很有可能在系统中打开了很多文件。那么操作系统要不要对这些文件进行管理呢?必须要进行管理!!!怎么管理呢?先描述,后组织!!!

  怎么描述呢?我们都知道,Linux操作系统使用C语言写的。那么问题就转换成了C语言用什么来描述一个对象呢?不就是结构体吗!关键问题来了:怎么进行组织呢?这就与文件描述符有关系了。接着往下看。

  当一个程序运行起来加载到内存后,创建对应的数据结构,就会变成一个进程。其中关键是进程控制块PCB。在Linux内核中,PCB为task_struct。其中,task_struct中就包含了一个文件结构体指针files_struct* fs。改指针就是指向的文件结构体。重点是该文件结构体中有一个指针数组file* fd_arry[](文件映射表),该指针数组指向的就是我们所打开的文件。我们所新打开的文件fd的值是文件描述表中最小的为空的位置具体可结合下图理解:

  我们在对上述的组织过程进行从头到尾的详细阐述一下:首先进程调用fwrite -> 找到所传入的FILE*  -> 找到FILE中的fd -> 调用内部封装的write(系统调用) -> 找到进程的task_struct -> 找files_struct* fs -> 找到 files_struct -> 扎到files_struct中的file* fd_array[] -> fd_array[fd] -> 该位置的值是一个指针,该指针指向的就是file -> 找到该文件后,再进行操作。

  相信到这里,你就会对为什么要引入文件操作符fd就会清楚了。一个很重要的原因就是便于管理文件和操作

3、2 到底什么是文件操作符呢

  刚开始我们只知道文件操作符是一个整数,其具体到表的含义并不知道。通过对上述的了解后,我们也就知道了文件描述符是一个下标,是用来标识和操作文件或者输入输出设备的整数。每个打开的文件(包括标准输入、标准输出和标准错误输出)都会被分配一个唯一的文件描述符文件描述符的使用主要有以下几个方面:

  1. 文件操作:通过文件描述符,我们可以对文件进行读取、写入、定位等操作。例如,可以使用文件描述符来打开、关闭、读取、写入文件。
  2. 输入输出重定向:文件描述符可以用于在程序运行时动态地将输入输出重定向到其他文件。例如,可以将程序的输出重定向到文件中,或者将文件作为程序的输入。
  3. 管道通信:文件描述符可以用于实现进程间的通信,其中最常见的方式是使用管道。通过创建管道,并使用文件描述符将数据从一个进程传递给另一个进程。

  接下来下面我们会对上述的三个使用进行讲解的。

四、文件描述符的使用

4、1 验证文件描述符

4、1、1 验证stdin、stdout、stdout

  首先我们要验证三个标准:stdin、stdout、stdout中是否包含文件描述符fd。首先stdin、stdout、stdout是数据类型什么类型的呢?结合我们所学的文件操作函数,数据类型不就是FILE*吗。验证代码如下:

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
    
int main()    
{    
    //_fileno就是文件描述符fd
    printf("%d\n%d\n%d\n",stdin->_fileno,stdout->_fileno,stderr->_fileno);    
    int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-    
    printf("open success, fd: %d\n", fd1);     
   
    close(fd1);    
  
    return 0;    
}

  输出结果如下:

4、1、2 验证fd值的大小顺序

  我们上述提到:新打开的文件fd的值是文件描述表中最小的为空的位置。怎么证明呢?不要忘记了可以通过close结合fd,进行关闭系统默认打开的标准文件。验证代码如下:

  #include<stdio.h>    
  #include<sys/types.h>    
  #include<sys/stat.h>    
  #include<fcntl.h>    
  #include<unistd.h>    
      
  int main()    
  {    
      close(0)    
      int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-    
      printf("open success, fd: %d\n", fd1);                                                                                                                 
                                                                                                                                                                                                                                
      close(fd1);                                                                                                                                           
      return 0;                                                                            
  }  

  上述代码就是把fd值为0(标准输入)文件关闭,我们再新创建文件,在观察它的fd值。结果如下:

  确实,值为0了。我们在通过下述代码再次验证:

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
    
int main()    
{    
    close(0);                                                                                                                                                
    int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-    
    printf("open success, fd: %d\n", fd1);    
    int fd2 = open("log2.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-    
    printf("open success, fd: %d\n", fd2);    
 
    
    
    close(fd1);    
    close(fd2);     
    return 0;    
}

  输出结果如下: 

 

  确实,我们只关闭了fd值为0(标准输入)文件关闭,同时打开两个文件。其中一个fd的值为0,另一个为3。我们所述的是正确的。

4、2 输入输出重定向

 我们平常的输入是从标准输入(键盘)读取,输出往标准输出(显示器)打印!输入和输出重定向就是不再从标准输入读取,向标准输出打印了,就是从指定的文件读取,或只输入到指定文件。什么原理呢?

  首先,我们知道默认是输出到标准输出(显示器)上。我们能不能关闭标准输出,然后打开一个我们想输出到指定的文件,这是新打开的文件fd的值不就是我们刚刚关闭的标准输出文件的fd的值嘛!!! 根据我们上述详解文件描述知道,操作系统內部只认识文件标识符fd,并不认识所谓的stdin、stdout。此时新打开的文件不就称为了标准输出吗!!!我们可结合下图理解:

  通过上述讲解,思路是有了。我们再看代码实现:

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    
    
    
int main()    
{    
    close(1);    
    int fd = open("log.txt", O_WRONLY | O_APPEND | O_CREAT);    
    if(fd<0)     
    {    
        perror("open");    
        return 1;    
    }    
    
    printf("fd: %d\n", fd); // stdout->FILE{fileno=1}->log.txt    
    printf("fd: %d\n", fd);    
    printf("fd: %d\n", fd);    
    printf("fd: %d\n", fd);    
    printf("fd: %d\n", fd);                                                                                                                                  
    printf("fd: %d\n", fd);    
    fprintf(stdout, "hello fprintf\n");    
    const char *s = "hello fwrite\n";    
    fwrite(s, strlen(s), 1, stdout);    
    printf("fd: %d\n", fd);
    fflush(stdout);
    close(fd);
    return 0;
}

   我们再来看输出结果:

  确实不在输出到屏幕上,而是我们制定的文件。这不就是输出重定向吗。 

4、2、1 dup2的使用 

  dup2函数的作用有两个方面:一是复制文件描述符,二是重新分配文件描述符。

  1. 复制文件描述符: dup2可以用来复制一个已打开的文件描述符,创建一个新的文件描述符,使其指向相同的文件、管道或套接字。这种方式可以实现重定向输入、输出或错误流的功能。
  2. 重新分配文件描述符: 使用dup2可以将一个文件描述符重新指定为指定的文件、管道或套接字。如果新的文件描述符已经被打开,则系统会自动关闭它。

  这里我们主要讲解复制文件描述符。

  在每次输入重定向前,都需要调用close关闭标准输出,未免会有点麻烦。有没有其他的方法呢?答案是有的。我们可以看一下dup系列的系统调用接口。如下图:

  这里我们就讲解dup2的使用。 具体如下图:

   一共是有两个参数:oldfd、newfd, newfd是拷贝oldfd得到的。

  我们想要的效果是:把新打开的文件fd值为3的地址拷贝到fd值为1的地址处。那么结合我们上面说到newfd是拷贝oldfd得到的。那么dup2的使用方法不就是dup2(3,1)。我们再看输出重定向的代码:

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

int main() {
    int fd = open("file.txt", O_WRONLY | O_CREAT, 0644);
    if (fd < 0) {
        perror("open");
        return -1;
    }

    // 复制文件描述符
    int newfd = dup2(fd, stdout); // 将标准输出重定向到文件 dup2(3,1);
    if (newfd < 0) {
        perror("dup2");
        return -1;
    }

    printf("Hello, World!\n"); // 输出将被重定向到文件

    close(fd);

    return 0;
}

  运行结果如下:

  输入重定向的原理与输出重定向的原理相同,本篇文章就不再做过多解释。 

 五、Linux下一切皆文件

  在Linux中,"一切皆文件"(Everything is a file)是一个重要的概念,用于描述Linux操作系统中所有资源和设备都以文件的形式进行访问和处理。

  这个概念可以理解为,无论是硬盘上的文件、网卡、设备、进程等,都被抽象为文件的形式存在。在Linux系统中,通过文件系统(File System)来管理和访问这些资源。

具体来说,"一切皆文件"可以被解释为:

  1. 文件:在Linux中,普通的文件就是我们常见的文本文件、二进制文件等。它们被组织成一个层次结构的目录树,通过路径来定位和访问。

  2. 目录:目录也是一种文件,它包含了其他文件和目录的信息。通过目录,可以组织和管理文件的层次结构。

  3. 设备文件:Linux将硬件设备(如磁盘、网络接口等)和虚拟设备(例如打印机,输入设备)都看作是文件来处理。通过设备文件,可以读取和写入设备的数据。

  4. 进程:在Linux系统中,每个正在运行的进程都有与之关联的文件。通过读取和写入相应的文件,可以与进程进行通信和交互。

  通过将所有资源都抽象为文件,Linux提供了一套统一的接口,可以使用相同的命令和工具来访问和管理不同的资源。这种统一性使得Linux操作系统更加灵活和强大,同时也方便了开发者和系统管理员进行各种操作和配置。

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

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

相关文章

IMU惯性测量单元相关技术(概念版)

重要说明&#xff1a;本文从网上资料整理而来&#xff0c;仅记录博主学习相关知识点的过程&#xff0c;侵删。 一、参考资料 新手入门系列3——Allan方差分析方法的直观理解 惯性测量单元Allan方差分析详解 IMU误差&测量模型 IMU标定之—Allan方差 IMU误差模型简介及VINS…

2023年中国日志审计市场竞争格局、市场规模、下游应用领域及行业发展趋势[图]

日志是行为或状态详细描述的载体&#xff0c;其时效性与信息丰富程度在网络安全事件分析、事件回溯和取证过程中起到重要作用。在法律层&#xff0c;日志也是重要的电子证据&#xff0c;日志记录、监控、审计手段等&#xff0c;可以帮助有效地减少信息破坏、信息泄露的问题&…

2023最新python学习方法总结!(内部机密)

不要再问我python好不好学了 我之前做过半年少儿编程老师&#xff0c;一个小学四年级的小孩子都能在我的教学下独立完成python游戏&#xff0c;植物大战僵尸简单版&#xff0c;如果要肯花时间&#xff0c;接下来的网络开发也不是问题&#xff0c;人工智能也可以学个调包也没啥问…

【TypeScript】this指向,this内置组件

this类型 TypeScript可推导的this类型函数中this默认类型对象中的函数中的this明确this指向 怎么指定this类型 this相关的内置工具类型转换ThisParameterType<>ThisParameterType<>ThisType TypeScript可推导的this类型 函数中this默认类型 对象中的函数中的this…

PLY模型格式详解【3D】

本文介绍PLY 多边形文件格式&#xff0c;这是一种用于存储被描述为多边形集合的图形对象。 PLY文件格式的目标是提供一种简单且易于实现但通用的格式足以适用于各种模型。 PLY有两种子格式&#xff1a;易于入门的 ASCII 表示形式和用于紧凑存储和快速保存和加载的二进制格式。 …

多线程与高并发--------线程

一、线程的基础概念 一、基础概念 1.1 进程与线程 什么是进程&#xff1f; 进程是指运行中的程序。 比如我们使用钉钉&#xff0c;浏览器&#xff0c;需要启动这个程序&#xff0c;操作系统会给这个程序分配一定的资源&#xff08;占用内存资源&#xff09;。 什么线程&am…

JAVA(一)

我的第一个JAVA程序 以下我们通过一个简单的实例来展示Java编程,创建文件HelloWorld.java&#xff08;文件名需与类名一致&#xff09;,代码如下 实例 public class HelloWorld{public static void main(String[] args){System.out.println(Hello World) } } 注:Srting ar…

[NLP]LLM 训练时GPU显存耗用量估计

以LLM中最常见的Adam fp16混合精度训练为例&#xff0c;分析其显存占用有以下四个部分&#xff1a; GPT-2含有1.5B个参数&#xff0c;如果用fp16格式&#xff0c;只需要1.5G*2Byte3GB显存, 但是模型状态实际上需要耗费1.5B*1624GB. 比如说有一个模型参数量是1M&#xff0c;在…

RCNA——VLAN进阶划分

一&#xff0c;实验背景 有时&#xff0c;公司可能会非常大&#xff0c;因为业务需求的因素&#xff0c;所以每个部门在每一个楼层都有相应的办公室&#xff0c;因此想要实现他们之间的互连互通&#xff0c; 可以在每个楼层部署一个楼层交换机&#xff0c;实现各个楼层相同部门…

堆的实现以及应用

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; 刷题专栏&#x1f440; C语言&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大家三连关注&…

IntelliJ IDEA和Android studio怎么去掉usage和作者提示

截止到目前我已经写了 600多道算法题&#xff0c;其中部分已经整理成了pdf文档&#xff0c;目前总共有1000多页&#xff08;并且还会不断的增加&#xff09;&#xff0c;大家可以免费下载 下载链接&#xff1a;https://pan.baidu.com/s/1hjwK0ZeRxYGB8lIkbKuQgQ 提取码&#xf…

考虑微网新能源经济消纳的共享储能优化配置(Matlab代码实现

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

关于ArrayList的十三连问

文章目录 一、底层存储结构是什么二、初始容量三、构造方法四、扩容原理五、读写速度比较六、克隆为深克隆还是浅克隆七、多线程环境下是否安全八、增强遍历时添加或删除元素会发生什么事情九、为什么数组被transient修饰十、通过subList()获得的集合能否转为ArrayList十一、使…

基于灰狼优化(GWO)、帝国竞争算法(ICA)和粒子群优化(PSO)对梯度下降法训练的神经网络的权值进行了改进(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【python】一文了解Python爬虫 | 文末送书

目录 引言 1. 爬虫基础知识 1.1 什么是爬虫 1.2 HTTP协议 1.2.1 HTTP请求方法 1.GET请求 1.2.2 请求头常见字段 1.2.3 响应状态码 1.3 HTML解析 1.3.1 Beautiful Soup 解析库 1.3.2 XPath xpath解析原理: xpath 表达式 2. 爬虫进阶技巧 2.1 防止被反爬虫 2.1.1 …

信息论基础知识

注意&#xff1a;本文只针对离散随机变量做出探讨&#xff0c;连续随机变量的情况不适用于本文探讨的内容&#xff01; &#xff08;一&#xff09;自信息 1. 自信息 I ( x ) − l o g n P ( x ) \color{blue}I(x) - log_{n}{P(x)} I(x)−logn​P(x) 注意&#xff1a; 若n …

用ChatGPT和六顶帽思考法帮助自己更好地决策和解决问题

当我们在解决复杂问题时&#xff0c;我们常常陷入单一视角的状态。创造性思维领域的先驱爱德华德博诺&#xff0c;提出了六顶帽思考法[1]&#xff0c;这意味着我们可以从六个不同的视角来思考一个问题&#xff0c;以实现高水平决策和解决问题。 每一顶“帽子”代表不同的视角。…

【Matlab】PSO优化(单隐层)BP神经网络算法

上一篇博客介绍了BP-GA&#xff1a;BP神经网络遗传算法(BP-GA)函数极值寻优——非线性函数求极值&#xff0c;本篇博客将介绍用PSO&#xff08;粒子群优化算法&#xff09;优化BP神经网络。 1.优化思路 BP神经网络的隐藏节点通常由重复的前向传递和反向传播的方式来决定&#…

UGUI源码深度剖析

源码下载后 直接嵌入工程&#xff0c;删除引擎extension里的&#xff1b; 自制UI&#xff0c;在一个空场景中显示一个图片&#xff0c;当鼠标点击图片&#xff0c;执行操作。 gameobject &#xff1a; mesh meshfilter meshrender maintexture meshcollider camera ray

基于SSM的智能商城购物系统

基于SSM的智能商城购物系统 项目简介项目获取开发环境项目技术运行截图 项目简介 该智能商城系统主要实现两大功能模块:前台管理和后台管理 前台管理包括五大模块:用户登录注册、商品信息、购物车信息、个人信息管理、下单与订单管理、订单物流设置。 (1)用户登录注册模块:该功…