Linux中的文件描述符

news/2024/11/8 12:01:02/

1. 认识文件描述符

在这里插入图片描述

文件描述符的概念:在Linux中,文件描述符是内核为了高效的管理已经被打开的文件所创建的索引 ,它是一个非负整数,用于指代被打开的文件,所有执行I/O操作的系统调用都是通过文件描述符完成的。文件描述符是一个简单的非负整数,用来表明每一个被进程打开的文件。
在Linux中,进程是通过文件描述符 (file descriptors 简称fd)来访问文件的,文件描述符实际上是一个整数。 在程序刚启动的时候,默认有三个文件描述符,分别是:0 (代表标准输入),1 (代表标准输出),2 (代表标准错误)。 我们可以通过open函数得到一个指定文件的文件描述符,如果出现错误则返回-1。open函数需要传入一个文件路径和操作模式,调用会返回一个整型的文件描述符。
在这里插入图片描述
在Linux系统中,open函数主要作用就是打开和创建文件,可以根据参数来定制需要的文件的属性和用户权限等各种参数。flag参数相当于是宏,并且是可选的,用于设置打开文件的模式。flag参数的取值如下:

O_RDONLY: 只读模式
O_WRONLY: 只写模式
O_RDWR : 读写模式
O_NONBLOCK: 非阻塞模式
O_APPEND: 追加模式
O_CREAT: 创建并打开一个新文件
O_TRUNC: 打开一个文件并截断它的长度为零(必须有写权限)
O_EXCL: 如果指定的文件存在,返回错误

open的用法

接下来我们打印一些文件描述符,观察一下文件描述符的规律;

#include <stdio.h>    
#include <errno.h>    
#include <string.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>int main()
{int fd1 = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);int fd2 = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);int fd3 = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);int fd4 = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);int fd5 = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);printf("fd1: %d\n", fd1);printf("fd2: %d\n", fd2);printf("fd3: %d\n", fd3);printf("fd4: %d\n", fd4);printf("fd5: %d\n", fd5);close(fd1);close(fd2);close(fd3);close(fd4);close(fd5);return 0;
}

可以看到是一串连续的整数,而0,1, 2是默认的文件描述符,分别是0 (代表标准输入),1 (代表标准输出),2 (代表标准错误),所以文件描述符(open对应的返回值)本质就是:数组下标。
在这里插入图片描述
当我们打开一个文件,我们并没有创建数组,那么怎么会有上图的数组下标?

在Linux中,打开文件的过程涉及到进程、文件描述符、文件描述表、打开文件表、目录项、索引表之间的联系。当我们打开一个文件时,主要涉及了进程,文件描述符,文件描述表,打开文件表,目录项,索引表之间的联系。
具体来说,当我们打开一个文件时,操作系统会为该进程分配一个文件描述符(file descriptor),并将其存储在该进程的文件描述符表(file descriptor table)中。然后,操作系统会在打开文件表(open file table)中创建一个新的条目,并将该条目与该文件描述符相关联。此外,操作系统还会在目录项(directory entry)和索引表(inode table)中查找该文件,并将其加载到内存中。最后,操作系统会返回该文件描述符给应用程序,以便应用程序可以使用该文件描述符来访问该文件。

在这里插入图片描述
打开一个文件,系统做了大致如下几件事,首先CPU会寻找对应的struct task_struct(也就是进程PCB);struct task_struct中有许多的struct file_struct *files指针,这些指针指向的就是不同的文件结构体,而struct file_struct结构体中就记录着文件描述符(操作系统方面,我们必须要访问fd(文件描述符),才能找到文件!);0、1、2对应的是三个默认的文件描述符,之后的文件描述符就是open返回的;磁盘将文件加载到内存,通过fd我们可以查找到内存中的文件,这些大致就是我们访问文件,操作系统的处理。
概念:PCB(Process Control Block)是进程控制块的缩写,是操作系统中最重要的记录型数据结构之一。PCB中记录了操作系统所需要的、用于描述进程情况及控制进程运行所需要的全部信息。

struct file结构体中,包含了文件权限,文件的大小,自己的缓冲区以及readp和writep等信息,这些信息都是以文件的方式存在的,外设通过驱动程序中的代码可以对文件进行读写。
在这里插入图片描述
Linux中所有内容都是以文件的形式保存和管理的。普通文件是文件,目录(Windows下称为文件夹)是文件,硬件设备(键盘、监视器、硬盘、打印机)是文件,所以说linux下一切皆文件。
查看文件描述符0, 1, 2,代码如下:

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

在这里插入图片描述

2. 更改默认的文件描述符

文件描述符的分配规则是什么?请看如下代码:

int main()
{//先关闭0,2,再进行打开文件close(0);close(2);int fd1 = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);int fd2 = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);int fd3 = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);int fd4 = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);int fd5 = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);printf("fd1: %d\n", fd1);printf("fd2: %d\n", fd2);printf("fd3: %d\n", fd3);printf("fd4: %d\n", fd4);printf("fd5: %d\n", fd5);close(fd1);close(fd2);close(fd3);close(fd4);close(fd5);return 0;
}

在这里插入图片描述
在Linux中,每个进程都有一个独立的文件描述符表,该表是一个数组,每个元素都是一个指向file结构体的指针。当进程打开一个文件时,内核会为该进程分配一个新的文件描述符,并将其返回给进程。文件描述符分配规则是在file_struct数组中,找到当前没有被使用的最小的一个下标, 作为新的文件描述符。

2.1 输出重定向

#include <stdio.h>    
#include <errno.h>    
#include <string.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>int main()
{close(1);int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666); //1printf("aaaaaaaaaaaa\n");printf("aaaaaaaaaaaa\n");printf("aaaaaaaaaaaa\n");//不用关掉fd :close(fd);return 0;
}

如上代码,先关掉默认描述符1(代表标准输出),所以关掉后printf函数就不会打印到显示器上;open后会返回一个文件描述符,根据文件描述符的规则:找到当前没有被使用的最小的一个下标,所以fd就是1;然后再打印字符串。
在这里插入图片描述
程序运行后,可以看到,数据没有打印在显示器上,而是打印在log.txt文件中了,这就是输出重定向。重定向的原理:在上层无法感知的情况下,在OS内部,更改进程对应的文件描述符表中,特定下标的指向。

2.2 输入重定向

先在log.txt文件中写入1234 5678

#include <stdio.h>    
#include <errno.h>    
#include <string.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>int main()
{close(0);int fd = open("log.txt", O_RDONLY);//0int a = 0, b = 0;scanf("%d %d",&a, &b);printf("a = %d, b = %d\n", a, b);return 0;
}

如上代码,先关掉默认描述符0(代表标准输入),open后会返回一个文件描述符,根据文件描述符的规则:找到当前没有被使用的最小的一个下标,所以fd就是0;然后再读取数据。
在这里插入图片描述
可以看到数据不是从键盘上读取的,而是从log.txt文件中读取的,这就是输入重定向。

2.3 追加重定向

追加重定向也是向显示器或文件中打印,所以close(1)。

#include <stdio.h>    
#include <string.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>int main()
{close(1);int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);printf("aaaaaaaaaaaa\n");printf("aaaaaaaaaaaa\n");return 0;
}

在这里插入图片描述

3. dup2函数

将常规消息打印打log.txt,将异常消息打印到err.txt?由上面概念所示,我们需要close(1),然后open一下,再close(2),然后open一个文件就行。

#include <stdio.h>    
#include <string.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>int main()
{close(1);open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);close(2);open("err.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);//因为linux下一切皆文件,所以向显示器打印,本质就是向文件中写入printf("hello printf->stdout\n");printf("hello printf->stdout\n");fprintf(stdout, "hello fprintf->stdout\n");fprintf(stdout, "hello fprintf->stdout\n");fprintf(stderr, "hello fprintf->stderr\n");fprintf(stderr, "hello fprintf->stderr\n");return 0;
}

运行结果如预料一样,如下所示:
在这里插入图片描述
如果我们需要将常规消息打印打log.txt,将异常消息打印到err.txt,那么我们要先关闭,再open,这样的工作就比较不方便,所以就有了dup。
在这里插入图片描述
dup2()是Linux系统中的一个系统调用,它可以将一个文件描述符复制到另一个文件描述符。 dup2()函数的原型如下:int dup2(int oldfd, int newfd);。
dup2()函数的作用是将newfd指向oldfd指向的文件,如果newfd已经打开,则先关闭newfd,然后将newfd指向oldfd指向的文件。

#include <stdio.h>    
#include <string.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>int main()
{int fd1 = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);int fd2 = open("err.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);dup2(fd1, 1);dup2(fd2, 2);//因为linux下一切皆文件,所以向显示器打印,本质就是向文件中写入printf("hello printf->stdout\n");printf("hello printf->stdout\n");fprintf(stdout, "hello fprintf->stdout\n");fprintf(stdout, "hello fprintf->stdout\n");fprintf(stderr, "hello fprintf->stderr\n");fprintf(stderr, "hello fprintf->stderr\n");return 0;
}

http://www.ppmy.cn/news/85201.html

相关文章

分文件实现温湿度数据管理系统项目

目标&#xff1a; 了解分文件的概念&#xff0c;要依次从C语言的函数声明、变量的存储类别、C语言编译预处理&#xff0c;说起。这些知识点我们之前或多或少接触过&#xff0c;这里做个总结与拓展。经过总结&#xff0c;最后我们归纳出一个实现C语言模块化编程的技巧&#xff…

Vue中组件之间通信的-六大方式-总结

方式一&#xff1a;props/$emit 父组件向子组件传值 通过一个例子&#xff0c;说明父组件如何向子组件传递值&#xff1a;在子组件Users.vue中如何获取父组件App.vue中的数据 users:["Henry","Bucky","Emily"] 注&#xff1a;父组件通过props向…

【leetcode刷题】剑指offer基础版(完结)

leetcode刷题笔记—剑指offer 剑指 Offer 05. 替换空格 class Solution { public:string replaceSpace(string s) {int len s.size();string g;for(int i 0; i < len; i){if(s[i] ){g "%20";continue;}g s[i];}return g;} };剑指 Offer 58 - II. 左旋转字…

Android Qcom Display学习(十五)

该系列文章总目录链接与各部分简介&#xff1a; Android Qcom Display学习(零) 简单方法 按键截图实现原理,通过PhoneWindowManager.java -> DisplayPolicy.java -> ScreenshotHelper.java,这个网上的叙述很多就不展开了 adb shell input keyevent KEYCODE_SYSRQ/fram…

01-bootstrap-概述

Bootstrap 是一款流行的前端开发框架&#xff0c;最初由 Twitter 开发&#xff0c;现在由社区维护。Bootstrap 提供了一套基于 HTML、CSS 和 JavaScript 的模板、样式、组件等&#xff0c;可以帮助开发者快速构建响应式、移动设备友好的网站和 Web 应用程序。 以下是 Bootstra…

百度API实现logo商标识别接口介绍

作者介绍 严松&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生 研究方向&#xff1a;机器人抓取检测 电子邮件&#xff1a;2448052777qq.com 王泽宇&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生&#xff0c;张…

计算机组成原理-存储系统-缓存存储器(Cache)

目录 一、Cache基本概念 1.2性能分析 二、 Cache和主存的映射发生 ​​​​​​2.1全相连映射​编辑 2.2直接映射​编辑 2.3组相连映射 三、Cachae的替换算法 3.1 随机算法(RADN) 3.2 先进先出算法(FIFO) 3.3 近期最少使用(LRU) 3.4 最近不经常使用(LFU) 四、写策略 4…

全网独一份微服务架构深度解析,连京东师哥都熬夜也要看完

什么是微服务&#xff0c;为什么需要用微服务&#xff1f; 一、微服务是什么&#xff1f; 定义&#xff1a;微服务是一些协同工作的小而自治的服务&#xff0c;这个服务是高凝聚力和松散耦合的。 微服务有以下特征&#xff1a; 1.一组小的服务&#xff08;大写没有特别的标…