当前位置: 首页 > news >正文

【Linux】文件描述符 fd

目录

一、C语言文件操作

1.1 fopen和fclose

1.2 fwrite和fread

1.3 C语言中的输入输出流

二、Linux的文件系统调用

2.1 open和文件描述符

2.2 close

2.3 read

2.4 write

三、Linux内核数据结构与文件描述符


一、C语言文件操作

在C语言中我们想要打开一个文件并对其进行读取写入等各种操作,需要依赖于fopen、fread、fwrite等函数。我们先来回顾一下这些函数

1.1 fopen和fclose

其中:

  • path:目标文件的路径
  • mode:文件的打开模式

文件的打开模式中,r表示只读,w表示只写,a表示追加,还有很多模式相信对大家来说都不陌生

fopen如果打开文件成功,会返回一个FILE*指针,其中FILE结构体是C库自己封装的结构体,内部封装了文件的各种属性

一个文件被打开后,如果我们不需要使用该文件了,就需要使用fclose关闭它,向函数传入先前从fopen接收到的FILE*指针即可。

1.2 fwrite和fread

其中:

  • ptr:指向某一内存块的指针
  • size:要读取/写入的元素大小
  • nmemb:要读取/写入的元素个数
  • stream:文件指针

所以读取或写入的字节总数为size*nmemb

我们可以用fwrite对一个文件进行写入,例如:

#include <stdio.h>
#include <string.h>int main()
{FILE *fp = fopen("myfile", "w");if(!fp){printf("fopen error!\n");}const char *str = "hello Linux\n";fwrite(str, strlen(str), 1, fp); fclose(fp);return 0;                                                                                                                                                                                                 
}            

运行代码,可以看到已经写入成功了

我们还可以用fread读取一个文件的内容,例如:

#include <stdio.h>
#include <unistd.h>
#include <string.h>int main()
{FILE *fp = fopen("myfile", "r");                                                                                                                                                                          if(!fp){printf("fopen error!\n");}char buf[1024];ssize_t s = fread(buf, 1, sizeof(buf), fp);if(s){buf[s] = '\0';printf("%s", buf);}fclose(fp);return 0;
}

fread如果读取成功,会返回读取的元素总数,与nmemb个数相同

运行代码,可以发现之前写入到myfile文件中的内容已经被读取出来了

除了以上的这些接口,C语言还有很多的文件操作接口,这里不是重点不作过多赘述

1.3 C语言中的输入输出流

在Linux中一切皆文件,显示器也是文件,我们也可以对其进行写入操作

如何对其进行写入呢?这里介绍C语言中的三个输入输出流

其中stdin标准输入流,对应我们的键盘stdout标准输出流stderr标准错误流,这两个流对应我们的显示器。这三个输入输出流是C语言程序在启动时默认会给我们打开的

不止是C语言,其他语言也会默认打开这三个流,不过名称不同。C++中分别是cin、cout和cerr 

进程将从标准输入流中得到输入数据,将正常输出数据输出到标准输出流,而将错误信息送到标准错误流中

可以看到,这三个流的类型都是FILE*,也就是说我们自己也可以通过fread从标准输入流中读取键盘输入的内容,通过fwrite向标准输出流或标准错误流即显示器文件写入我们的内容。

#include <stdio.h>
#include <unistd.h>
#include <string.h>int main()
{const char* str = "hello Linux\n";fwrite(str, 1, strlen(str), stdout);return 0;                                                                                                                                                                                                 
}

运行程序,可以看到我们已经把内容写入到显示器文件中了


二、Linux的文件系统调用

在前面了解操作系统的时候就提到,操作系统是有自己的系统调用接口的。

而语言中涉及到操作系统内核部分的函数,实际上都是封装了操作系统的系统调用接口,包括C语言的文件操作接口也是封装了操作系统的文件系统调用接口

所以我们先来了解一下Linux中的文件系统调用接口

2.1 open和文件描述符

open是Linux系统中用来打开文件的一个系统调用接口,其中:

  • pathname:目标文件的路径
  • flag:文件访问权限标志
  • mode:如果创建新文件,指定新建文件的访问权限

flag参数是文件访问权限标志位,其中:

  • O_RDONLY:以只读方式打开文件
  • O_WRONLY:以只写方式打开文件
  • O_RDWR:以可读可写方式打开文件

我们在使用open函数时必须包含以上三种中的一种标志位,除了这些还有其他的可选标志位,这里列出常用的三种:

  • O_CREAT:如果pathname指向的文件不存在则创建文件
  • O_TRUNC:打开文件的同时清空文件
  • O_APPEND:以追加方式对文件进行写入

标志位可以通过位运算的方式同时选中多种,例如:

像上面这样,就是以只写方式打开文件、以追加方式写入、如果文件不存在则创建

其中如果选中了O_CREAT,一般都要对mode参数进行设置,该参数用来设置新创建的文件权限

关于文件权限,我在前面的文章中有讲过

【Linux】常用指令、热键与权限管理-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Eristic0618/article/details/138795212?spm=1001.2014.3001.5501接下来我们看看open函数的返回值

可以看到,成功打开文件后open函数会返回一个file descriptor,即所谓的文件描述符(fd)

文件描述符是一个整型,从0开始向后分配,系统在给文件分配文件描述符时会分配当前没有被使用的最小的文件描述符 

关于文件描述符的本质,在后面会详细提到,目前只需要知道进程是通过文件描述符来访问文件的

2.2 close

close是Linux系统中关闭文件的系统调用接口,其参数就是一个文件描述符,传入文件对应的文件描述符即可关闭该文件

2.3 read

read函数将从fd对应的文件中读取count个字节到buf指向的内存块中

成功则返回读取到的字节数,失败则返回-1,若调用read时文件已达末尾则返回0

2.4 write

write函数将把buf指向的内存块中的内容写入count个字节到fd对应的文件中

成功则返回写入的字节数,失败则返回-1


三、Linux内核数据结构与文件描述符

到这里,相信大家已经学会如何使用Linux中的文件系统调用接口了,接下来我们深入探讨一下访问文件的本质

一个文件由内容属性两部分构成,没打开的文件存储在磁盘中,本文重点不在于未打开的文件所以不作过多探讨。而我们如果要打开一个文件,就先得将其加载到内存中,这是由冯·诺依曼体系结构决定的。

所以在操作系统内部一定存在着大量的被打开的文件,操作系统也自然要对这些文件进行管理,即先描述再组织。操作系统内部使用file结构体(与C语言的FILE结构体不同)来描述一个被打开文件的信息,其中包括该文件的基本属性、权限、大小、内核缓冲区信息等等,将这些结构体用数据结构组织起来,就可以完成对被打开文件的管理了。

而一个进程可以同时打开多个文件,所以进程和文件是一对多的关系。要把一个进程打开的所有文件管理起来,我们就要使用一个files_struct结构体

我们知道操作系统通过PCB描述进程,而在进程的PCB中就有这么一个类型为 struct files_struct* 的指针files指向了进程的files_struct结构体,其定义如下

struct files_struct {atomic_t count;struct fdtable *fdt;struct fdtable  fdtab;int next_fd;struct embedded_fd_set close_on_exec_init;struct embedded_fd_set open_fds_init;struct file * fd_array[NR_OPEN_DEFAULT];
};

其中的fd_array数组,就指向了进程打开的一个个文件的file结构体。很多人称files_struct是文件描述符表,但我认为这个fd_array数组才是更准确的文件描述符表,因为所谓的文件描述符,其实就是文件在这个表中存储位置的下标

前面提到,程序在启动时会默认打开三个标准输入输出流,所以文件描述符表中的前三个位置是默认被使用的。对应的,标准输入流的文件描述符为0,标准输出流的文件描述符为1,标准错误流的文件描述符为2

如何验证?在C语言中文件的属性被封装在FILE结构体中,结构体中的_fileno就是文件的文件描述符,我们只需要把三个标准流的文件描述符打印出来就能验证了

int main()
{printf("stdin:%d\n", stdin->_fileno);printf("stdout:%d\n", stdout->_fileno);printf("stderr:%d\n", stderr->_fileno);                                                                                                                                                                   return 0;
}

可以看到三个标准流对应的文件描述符就是0、1、2

如果此时我们在打印前close(1),会发生什么呢?

int main()
{close(1);printf("stdin:%d\n", stdin->_fileno);printf("stdout:%d\n", stdout->_fileno);printf("stderr:%d\n", stderr->_fileno);                                                                                                                                                                   return 0;
}

什么也不会打印,因为标准输出流即显示器文件在打印前已经被关闭了

我们可以在程序中打开多个文件,看看它们的文件描述符是什么

int main()
{int fd1 = open("file1", O_WRONLY|O_TRUNC|O_CREAT, 0666);int fd2 = open("file2", O_WRONLY|O_TRUNC|O_CREAT, 0666);int fd3 = open("file3", O_WRONLY|O_TRUNC|O_CREAT, 0666);int fd4 = open("file4", O_WRONLY|O_TRUNC|O_CREAT, 0666);printf("fd1:%d\n", fd1);printf("fd2:%d\n", fd2);printf("fd3:%d\n", fd3);printf("fd4:%d\n", fd4);                                                                                                                                                                                  return 0;
}

可以看到,系统会给文件分配当前未被使用的最小的文件描述符

一个文件可以被多个进程打开,那么是不是一个进程将该文件关闭了,该文件对应的资源就要被系统回收呢?

不是的,在file结构体中有该文件的引用计数,用来计算该文件被多少个进程打开了。如果一个进程将该文件对应的fd关闭则减少对应的引用计数,只有当引用计数为0时文件才会被真正的关闭

完.

相关文章:

【Linux】文件描述符 fd

目录 一、C语言文件操作 1.1 fopen和fclose 1.2 fwrite和fread 1.3 C语言中的输入输出流 二、Linux的文件系统调用 2.1 open和文件描述符 2.2 close 2.3 read 2.4 write 三、Linux内核数据结构与文件描述符 一、C语言文件操作 在C语言中我们想要打开一个文件并对其进…...

带通采样定理

一、采样定理 1.1 低通采样定理(奈奎斯特采样) 低通采样定理&#xff08;奈奎斯特采样&#xff09;是要求大于信号的最高上限频率的两倍 1.2 带通采样定理 带通信号的采样频率在某个时间小于采样频率也能无失真恢复原信号 二、频谱混叠 对一个连续时域信号&#xff0c;采…...

运维工作中的事件、故障排查处理思路

一、运维工作中的事件 https://www.51cto.com/article/687753.html 二、运维故障排查 一&#xff09;故障排查步骤 1、明确故障 故障现象的直接表现故障发生的时间、频率故障发生影响哪些系统故障发生是否有明确的触发条件   故障举例&#xff1a;无法通过ssh登录系统 影响…...

深入源码P3C-PMD:使用流程(1)

PMD开源组件启动流程介绍 在软件开发领域&#xff0c;代码质量是项目成功的关键因素之一。为了提升代码质量&#xff0c;开发者们常常借助各种工具进行代码分析和检查。PMD作为一款开源的静态代码分析工具&#xff0c;在Java、JavaScript、PLSQL等语言项目中得到了广泛应用。本…...

java~反射

反射 使用的前提条件&#xff1a;必须先得到代表的字节码的Class&#xff0c;Class类用于表示.class文件&#xff08;字节码&#xff09; 原理图 加载完类后&#xff0c;在堆中就产生了一个Class类型的对象&#xff08;一个类只有一个Class对象&#xff09;&#xff0c;这个对…...

【Linux】(26) 详解磁盘与文件系统:从物理结构到inode机制

目录 1.认识磁盘、 1.1 理论 1.2 磁盘的物理结构 CHS 寻址 1.3 磁盘的逻辑抽象结构 2. inode 结构 1.Boot Block 启动块 2.Super Block&#xff08;超级块&#xff09; 3.Group Descriptor Block&#xff08;块组描述符&#xff09; 4.Data Blocks (数据块) 5.Inode…...

8.1 字符串中等 43 Multiply Strings 38 Count and Say

43 Multiply Strings【默写】 那个难点我就没想先解决&#xff0c;原本想法是先想其他思路&#xff0c;但也没想出。本来只想chat一下使用longlong数据类型直接stoi()得不得行&#xff0c;然后就看到了答案&#xff0c;直接一个默写的大动作。但这道题确实考察的是还原乘法&…...

upload-labs靶场:1—10通关教程

目录 Pass-01&#xff08;JS 验证&#xff09; Pass-02&#xff08;MIME&#xff09; Pass-03&#xff08;黑名单绕过&#xff09; Pass-04&#xff08;.htaccess 绕过&#xff09; Pass-05&#xff08;大小写绕过&#xff09; Pass-06&#xff08;空格绕过&#xff09; …...

Hive3:一键启动、停止、查看Hive的metastore和hiveserver2两个服务的脚本(好用)

脚本内容 #!/bin/bash # 一键启动、停止、查看Hive的metastore和hiveserver2两个服务的脚本 function start_metastore {# 启动Hive metastore服务hive --service metastore >/dev/null 2>&1 &for i in {1..30}; doif is_metastore_running; thenecho "Hiv…...

遗传算法与深度学习实战——生命模拟及其应用

遗传算法与深度学习实战——生命模拟及其应用 0. 前言1. 康威生命游戏1.1 康威生命游戏的规则1.2 实现康威生命游戏1.3 空间生命和智能体模拟 2. 实现生命模拟3. 生命模拟应用小结系列链接 0. 前言 生命模拟是进化计算的一个特定子集&#xff0c;模拟了自然界中所观察到的自然…...

大数据|使用Apache Spark 删除指定表中的指定分区数据

文章目录 概述方法 1: 使用 Spark SQL 语句方法 2: 使用 DataFrame API方法 3: 使用 Hadoop 文件系统 API方法 4: 使用 Delta Lake使用注意事项常见相关问题及处理结论 概述 Apache Spark 是一个强大的分布式数据处理引擎&#xff0c;支持多种数据处理模式。在处理大型数据集时…...

OSPF动态路由协议实验

首先地址划分 一个骨干网段分成三个&#xff0c;r1&#xff0c;r2&#xff0c;r5三个环回网段 &#xff0c;总共要四个网段 192.168.1.0/24 192.168.1.0/26---骨干网段 192.168.1.0/28 192.168.1.16/28 192.168.1.32/28 备用 192.168.1.64/28 192.168.1.64/26---r1环回 192.1…...

tcp中accept()的理解

源码 参数理解 NAMEaccept, accept4 - accept a connection on a socketSYNOPSIS#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);#define _GNU_SOURCE …...

让我们逐行重现 GPT-2:第 1 部分

欢迎来到雲闪世界。Andrej Karpathy 是人工智能 (AI) 领域的顶尖研究人员之一。他是 OpenAI 的创始成员之一&#xff0c;曾领导特斯拉的 AI 部门&#xff0c;目前仍处于 AI 社区的前沿。 在第一部分中&#xff0c;我们重点介绍如何实现 GPT-2 的架构。虽然 GPT-2 于 2018 年由 …...

第十九天内容

上午 1、构建vue发行版本 2、java环境配置 jdk软件包路径&#xff1a; https://download.oracle.com/java/22/latest/jdk-22_linux-x64_bin.tar.gz 下午 1、安装tomcat软件 tomcat软件包路径&#xff1a; https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.26/bin/apache-to…...

Hive之扩展函数(UDF)

Hive之扩展函数(UDF) 1、概念讲解 当所提供的函数无法解决遇到的问题时&#xff0c;我们通常会进行自定义函数&#xff0c;即&#xff1a;扩展函数。Hive的扩展函数可分为三种&#xff1a;UDF,UDTF,UDAF。 UDF&#xff1a;一进一出 UDTF&#xff1a;一进多出 UDAF&#xff1a…...

jdk1.8中HashMap为什么不直接用红黑树

最开始使用链表的时候&#xff0c;空间占用比较少&#xff0c;而且由于链表短&#xff0c;所以查询时间也没有太大的问题。可是当链表越来越长&#xff0c;需要用红黑树的形式来保证查询的效率。 参考资料&#xff1a; https://blog.51cto.com/u_13294304/3075723...

消息推送只会用websocket、轮询?试试SSE,轻松高效。

SSE介绍 HTTP Server-Sent Events (SSE) 是一种基于 HTTP 的服务器推送技术,它允许服务器向客户端推送数据,而无需客户端发起请求。以下是 HTTP SSE 的主要特点: 单向通信: SSE 是一种单向通信协议,服务器可以主动向客户端推送数据,而客户端只能被动接收数据。 持久连接: SS…...

Spring-Retry 框架实战经典重试场景

Spring-Retry框架是Spring自带的功能&#xff0c;具备间隔重试、包含异常、排除异常、控制重试频率等特点&#xff0c;是项目开发中很实用的一种框架。 1、引入依赖 坑点&#xff1a;需要引入AOP&#xff0c;否则会抛异常。 xml <!-- Spring-Retry --> <dependency&…...

人工智能在医疗领域的应用与挑战

随着人工智能技术的不断发展&#xff0c;其在医疗领域的应用也越来越广泛。从辅助诊断到治疗决策&#xff0c;人工智能正在逐步改变着传统的医疗模式。然而&#xff0c;人工智能在医疗领域的应用也面临着诸多挑战&#xff0c;如数据隐私、伦理道德等问题。本文将探讨人工智能在…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...