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

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

Golang——9、反射和文件操作

反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一&#xff1a;使用Read()读取文件2.3、方式二&#xff1a;bufio读取文件2.4、方式三&#xff1a;os.ReadFile读取2.5、写…...

MinIO Docker 部署:仅开放一个端口

MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

wpf在image控件上快速显示内存图像

wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像&#xff08;比如分辨率3000*3000的图像&#xff09;的办法&#xff0c;尤其是想把内存中的裸数据&#xff08;只有图像的数据&#xff0c;不包…...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器

拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件&#xff1a; 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...

Chrome 浏览器前端与客户端双向通信实战

Chrome 前端&#xff08;即页面 JS / Web UI&#xff09;与客户端&#xff08;C 后端&#xff09;的交互机制&#xff0c;是 Chromium 架构中非常核心的一环。下面我将按常见场景&#xff0c;从通道、流程、技术栈几个角度做一套完整的分析&#xff0c;特别适合你这种在分析和改…...