【Linux操作系统】文件描述符fd
🔥🔥 欢迎来到小林的博客!!
🛰️博客主页:✈️林 子
🛰️博客专栏:✈️ Linux之路
🛰️社区 :✈️ 进步学堂
🛰️欢迎关注:👍点赞🙌收藏✍️留言
目录
- 系统文件I/O函数介绍
- open函数返回值
- 文件描述符fd
- 为什么文件描述符从0开始?
- 重定向的实现原理
- 不同的文件,怎么输入输出到不同的设备?
- 文件描述符的验证
- 文件描述符的继承
系统文件I/O函数介绍
我们C语言有fopen,fwrite,fread等接口来进行文件访问。其根本原因还是进行了一层系统调用,使用了系统提供的接口。所以我们可以直接使用系统的接口来进行文件的访问操作。
再此之前需要先介绍俩个函数。
open

int open(const char *pathname, int flags, mode_t mode);
其中 pathname,代表要写入的字符串,flags,就是要对文件进行的操作,mode_t mode 是一个八进制的权限值。如果文件打开失败会返回-1,打开成功则返回文件的fd值
write

ssize_t write(int fd, const void *buf, size_t count);
fd就是文件的fd值,buf就是要写入的字符串。count就是要写入的字符数量。而返回值是实际写入的数量。
所以我们可以写这样一份代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>int main() { int fd = open("logo",O_WRONLY | O_CREAT,0664);//O_WRONLY写,O_CREAT如果文件不存在就创建 if(fd < 0) //打开文件失败,open会返回-1 {perror("fail\n");return 1; }int count = 5; const char* str = "hello linux\n"; int len = strlen(str); while(count--)write(fd,str,len); // 往fd的位置写文件close(fd);return 0; }
如何运行看看结果。

我们可以发现,再运行这个程序之后。我们就会文件里写入程序中指定的内容。
那么再为大家介绍一个系统接口。
read

使用方法和write一样,只不过write是写,read是读。
代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("logo",O_RDONLY);if(fd < 0) //打开文件失败,open会返回-1{perror("fail\n");return 1;}int count = 5;const char* str = "hello linux\n";int len = strlen(str);char buff[1024] = {0};while(read(fd,buff,len) > 0) //把文件的数据读到buff中{printf("%s",buff);//打印读取的数据}close(fd);return 0;
}
而close,就是关闭文件的意思。open打开文件,close关闭文件。
open函数返回值
在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数。
- 上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
- 而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口

系统调用接口和库函数的关系,一目了然。
所以,可以认为,f系列的函数,都是对系统调用的封装,方便二次开发。
文件描述符fd
通过对open函数的学习,我们知道了文件描述符就是一个小整数
open打开文件时会返回这个文件描述符,那么我们来看看这个文件描述符是多少呢?
那我们就用下面这段代码来观察每个打开文件的文件描述符。
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>int main()
{int fd1 = open("./log.txt",O_WRONLY | O_CREAT);int fd2 = open("./log1.txt",O_WRONLY | O_CREAT);int fd3 = open("./log2.txt",O_WRONLY | O_CREAT);int fd4 = open("./log3.txt",O_WRONLY | O_CREAT);int fd5 = open("./log4.txt",O_WRONLY | O_CREAT);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);return 0;
}
编译后我们发现对应的fd(文件描述符)值是 3 4 5 6 7

我们可以看到是一个连续的整数数列。那么这下就有点好奇了,为什么文件描述符从3开始? 而不是从0 或者1 开始。
答案是,每个进程执行时,会默认打开三个文件。 这三个文件就是 stdin(标准输入) , stdout(标准输出) , stderr(标准错误) 。 而它们三个对应的fd值分别就是 0 , 1 , 2。所以后续打开的文件会从3开始。
所以我们还可以这样子输入输出:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{const char* print = "print\n";char buff[1024]; write(1,print,strlen(print)); //把 print写到显示器read(0,buff,1024); //把 键盘的字符串 读到buff里buff[strlen(buff)-1] = '\0'; //因为输入会录入回车。把回车换成\0printf("input:%s\n",buff);return 0;
}
运行结果:

为什么文件描述符从0开始?
现在知道,文件描述符就是从0开始的小整数。为什么从0 开始? 我们不妨思考一下,从0开始的,连续的整数。像什么? 是不是很像数组的下标? 没错,fd值对应的就是数组下标!!
在每个进程控制块(PCB) 中, 都有一个指针*files指针指向一张表files_struct 。这是因为当进程打开文件时,操作系统需要创建相应的数据结构来描述这些文件。 而在 files_stuct表中,有一个最重要的部分,那就是一个指针数组!这个数组中的每一个元素都是一个指向当前进程已经打开了的文件的指针!所以本质上,文件描述符就是这个数组的下标,只要拿着文件描述符,就可以找到对应的已打开的文件

fd的本质是内核中是进程和文件关联的数组的下标。
而我们一个进程可以打开多个文件。所以 **进程:文件 = 1 : n **。进程和文件是一对多的关系。
所以我们也可以直接往 1(标准输出)里面写 ,一样可以在显示器打印结果。
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>int main()
{char buff[] = "hello file\n";write(1,buff,sizeof(buff)-1);return 0;
}
我们编译运行一下。

那我们再来玩一个好玩的,我们把标准输入关掉。然后在打开文件,文件描述符还是3 吗?
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>int main()
{close(0);int fd1 = open("./log.txt",O_WRONLY | O_CREAT);printf("fd1 = %d\n",fd1);return 0;
}
我们会发现新打开的文件,它的fd值是0。 也就是我们把 标准输入关掉之后, 0 这个下标的位置空出来了,而这个时候打开文件那么就会往最前面的空位找。

结论:文件描述符的分配规则是从头开始,找下第一个空位分配。
重定向的实现原理
那再玩一个好玩的,把文件描述符1关掉呢?会发生什么?
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>int main()
{close(1);int fd1 = open("./log.txt",O_WRONLY | O_CREAT);printf("fd1 = %d\n",fd1);return 0;
}
我们编译运行

我们发现运行没有打印任何信息,但是! 我们在log.txt 发现了 fd1 = 1这个信息。这本是我们要打印在屏幕上的信息,为什么输出到了文件中呢?
这是因为printf函数不管你是标准输出,还是你自己打开的文件。它只负责往文件描述符为1的文件里写入。当我们关掉了标准输出,打开了新的文件log.txt时,printf函数还是理所当然的往fd为1的文件写入。然而此时文件描述符为1的文件早已不是我们的标准输出了,而是我们新打开的文件,所以就写入了我们新打开的文件。

而重定向的原理也是如此。
不同的文件,怎么输入输出到不同的设备?
我们都知道每个 file文件都是write和read接口。那么它怎么知道我要从键盘读,它怎么知道我要从键盘读取?
这就要用到多态的原理了。首先我们得有个虚函数表,存放的是对应的read/write函数的函数指针。这样文件就可以通过函数指针找到对应的外设进行该外设的读写操作。

文件描述符的验证
C语言中的FILE* 结构体一定包含了文件描述符, 为什么这么说呢? 因为任何语言,你想要使用外设设备。都必须要经过操作系统的同意!而你的标准输入输出会访问键盘和显示器, 所以也必须经过操作系统的同意! 因为操作系统不信任任何人,只相信自己,所以操作系统会提供一层系统调用接口。 通过调用系统调用接口,由操作系统去访问外设。 所以 C语言中的FILE* 文件结构体一定包含了文件描述符。怎么验证呢?我们可以访问结构体成员**_fileno** 来获取文件描述符,因为这个成员存取的就是文件描述符。
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>int main()
{FILE* f = fopen("./log.txt","w");printf("fd1 = %d\n",f->_fileno);return 0;
}
随后编译输出

输出的新打开文件的文件描述符是3。
文件描述符的继承
如果当父进程fork出了一个子进程之后,那么子进程会继承父进程的文件描述符吗?
答案是,一定会! 因为子进程是以父进程的模板创建的。子进程会单独复制一份file_struct 出来! 而不是和父进程共享。
所以,我们也可以解释一下,为什么每个进程执行的时候都会默认打开标准输入,标准输出,标准错误三个文件? 这一切都是因为这些进程继承了父进程的file_struct。而每个执行的进程的父进程都是bash,bash是命令行解析器。输入命令时,我们需要标准输入流,而标准输出用来返回输入命令的结果。标准错误用来返回报错。
一个进程可以打开多个文件,一个文件也可以被多个进程打开。在这里会用到一个引用计数的计数。文件每被一个进程打开,就会计数一次。被一个进程关闭,就会减少一次。直到为0时操作系统才会真正关闭这个文件。
相关文章:
【Linux操作系统】文件描述符fd
🔥🔥 欢迎来到小林的博客!! 🛰️博客主页:✈️林 子 🛰️博客专栏:✈️ Linux之路 🛰️社区 :✈️ 进步学堂 …...
【RocketMQ入门-安装部署与Java API测试】
【RocketMQ入门-安装部署与Java API测试】 一、环境说明二、安装部署三、Java API 编写Producer和Consumer进行测试四、小结 一、环境说明 虚拟机VWMare:安装centos7.6操作系统源码包:rocketmq-all-5.1.3-source-release.zip单master部署,在…...
SystemVerilog之覆盖率详解
文章目录 1.0 覆盖率前言1.1 覆盖率类型1.2 覆盖策略及覆盖组1.3 覆盖率数据采样1.3.1 bin的创建与使用1.3.2 条件覆盖率1.3.3 翻转覆盖率1.3.4 wildcard覆盖率1.3.5 忽略bin与非法bin 1.4 交叉覆盖率1.4.1 排除部分cross bin1.4.2 精细化交叉覆盖率1.4.3 单个实例的覆盖率1.4.…...
Qt Designer设计的界面如何显示、即运行显示窗口界面
首先利用Qt Designer设计.ui文件,然后采用Tools->External Tools->PyUIC转换成.py文件。这个.py文件是.ui文件编译而来的,将这种文件由.ui文件编译而来的.py文件称之为界面文件。由于界面文件每次编译时候都会初始化,所以需要新建一个.…...
vue3的setup的使用和原理解析
setup是Vue 3中引入的一个新的组件选项。它是一个在组件实例创建之前被调用的函数,用于设置组件的初始状态、计算属性、方法等。setup函数是Vue 3中函数式组件的核心部分,它提供了一种新的方式来编写组件逻辑。 使用setup函数有以下几个步骤:…...
Spring boot中的线程池-ThreadPoolTaskExecutor
一、jdk的阻塞队列: 二、Spring boot工程的有哪些阻塞队列呢? 1、默认注入的ThreadPoolTaskExecutor 视频解说: 线程池篇-springboot项目中的service层里简单注入ThreadPoolTaskExecutor并且使用_哔哩哔哩_bilibili 程序代码:…...
pgsql checkpoint机制(1)
检查点触发时机 检查点间隔时间由checkpoint_timeout设置pg_xlog中wall段文件总大小超过参数max_WAL_size的值postgresql服务器在smart或fast模式下关闭手动checkpoint 为什么需要检查点? 定期保持修改过的数据块作为实例恢复时起始位置(问题…...
微信小程序 map地图(轨迹)
allMarkers效果图 废话少说直接上马(最后是我遇到的问题) cover-view是气泡弹窗,可以自定义弹窗,要配合js:customCallout,如果是非自定义的话:callout(可以修改颜色、边框宽度、圆角…...
【钉钉接口】bpms_task_change、bpms_instance_change 的区别及举例
bpms_task_change:审批任务回调,是针对审批任务状态的推送。如审批人执行审批、审批人转交审批等针对具体某个审批节点的操作,属于 bpms_task_change 事件类型。bpms_instance_change:审批实例回调,是针对审批实例状态…...
vue左右div结构手动拉伸并且echarts图表根据拉伸宽高自适应
需求: 左右结构的div,可以根据数据抬起按下进行拉伸修改容器宽度的操作给左右结构某一图表设置拉伸自适应左右结构都设置个最小宽度,只能到一定区域内拉伸解决echarts的bug(重复加载chart实例):[ECharts] …...
开发工具Eclipse的使用
🥳🥳Welcome Huihuis Code World ! !🥳🥳 接下来看看由辉辉所写的关于Eclipse使用的相关操作吧 目录 🥳🥳Welcome Huihuis Code World ! !🥳🥳 一.Eclipse是什么 二.使用Eclipse的…...
DrawerLayout布局使用教程Android侧边栏导航完全指南:创建简单实用的导航抽屉
导航抽屉(侧边栏)在现代移动应用中扮演着关键角色,提供了流畅的用户导航体验。本文将带您从头开始,逐步创建一个基本的 Android 侧边栏导航示例,为您的应用增添更多交互魅力。 1. 创建新的 Android 项目 首先&#x…...
Dynamics 365 实体快速创建功能启用
这里我会先用例子讲快速创建,包含了字段创建等内容。希望直接了解配置过程的,可以根据目目录跳转查看。 1 例子 我们这里创建了两个实体,学生和选择的科目。它们的关系是一个学生可以选择多个科目,即学生和科目选择是一对多关系。所以我们在选择的科目中创建了一个学生的…...
Mybatis三剑客(一)在springboot中自动生成Mybatis【generator】
1、pom.xml中新增plugin <plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.7</version><configuration><overwrite>true</overwrite><…...
【LeetCode 热题 100】图论 专题(bfs,拓扑排序,Trie树 字典树)
from: https://leetcode.cn/studyplan/top-100-liked/ bfs 具有 边权为1 的最短路性质 拓扑排序,入度 Trie树, 高效存储 字符串【见鬼,不知道为什么写错,需要掌握熟练度】 文章目录 200. 岛屿数量【dfs / bfs】994. 腐…...
Jmeter压测实战:Jmeter二次开发之自定义函数
目录 1 前言 2 开发准备 3 自定义函数核心实现 3.1 新建项目 3.2 继承实现AbstractFunction类 3.3 最终项目结构 4 Jmeter加载扩展包 4.1 maven构建配置 4.2 项目打包 4.3 Jmeter加载扩展包 5 自定义函数调用调试 5.1 打开Jmeter函数助手,选择自定义函数…...
在python中使用nvidia的VPF库对RTSP流进行硬解码并使用opencv进行显示
解码并处理视频流的多线程应用 随着视频处理技术的不断发展,越来越多的应用需要对视频流进行解码和处理。在本文中,我们将介绍一个基于Python的多线程应用程序,该应用程序可以解码并处理多个RTSP视频流,同时利用GPU加速࿰…...
C++中using namespace std的作用记录
using namespace std;这句代码的作用是引入std命名空间,使得程序可以直接使用std命名空间下的标识符,而不需要加上std::前缀。 在C中,标识符被组织在不同的命名空间中,以避免命名冲突。最常见的命名空间是std,它包含了C标准库中的所有标识符,如cout、vector、string等。 默认…...
【TX 企业微信私有化历史版本 API 信息泄露】
目录 影响版本 复现过程 修复方式 影响版本 影响私有化部署: toB toG版微信 2.5.x 版本 2.6.930000 版本以下 危险程度:高危。攻击者可以进行获取企业的部门信息,员工信息,如权限较高包括应用获取,记录文件等等均…...
腾讯云轻量应用服务器镜像应用模板清单大全
腾讯云轻量应用服务器支持多种应用模板镜像,Windows和Linux镜像模板都有,如:宝塔Linux面板腾讯云专享版、WordPress、WooCommerce、LAMP、Node.js、Docker CE、K3s、宝塔Windows面板和ASP.NET等应用模板镜像,腾讯云服务器网分享腾…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
