当前位置: 首页 > 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;如数据隐私、伦理道德等问题。本文将探讨人工智能在…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

2023赣州旅游投资集团

单选题 1.“不登高山&#xff0c;不知天之高也&#xff1b;不临深溪&#xff0c;不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

elementUI点击浏览table所选行数据查看文档

项目场景&#xff1a; table按照要求特定的数据变成按钮可以点击 解决方案&#xff1a; <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...

加密通信 + 行为分析:运营商行业安全防御体系重构

在数字经济蓬勃发展的时代&#xff0c;运营商作为信息通信网络的核心枢纽&#xff0c;承载着海量用户数据与关键业务传输&#xff0c;其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级&#xff0c;传统安全防护体系逐渐暴露出局限性&a…...