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

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

逻辑回归暴力训练预测金融欺诈

简述 「使用逻辑回归暴力预测金融欺诈&#xff0c;并不断增加特征维度持续测试」的做法&#xff0c;体现了一种逐步建模与迭代验证的实验思路&#xff0c;在金融欺诈检测中非常有价值&#xff0c;本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案

在大数据时代&#xff0c;海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构&#xff0c;在处理大规模数据抓取任务时展现出强大的能力。然而&#xff0c;随着业务规模的不断扩大和数据抓取需求的日益复杂&#xff0c;传统…...

学习一下用鸿蒙​​DevEco Studio HarmonyOS5实现百度地图

在鸿蒙&#xff08;HarmonyOS5&#xff09;中集成百度地图&#xff0c;可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API&#xff0c;可以构建跨设备的定位、导航和地图展示功能。 ​​1. 鸿蒙环境准备​​ ​​开发工具​​&#xff1a;下载安装 ​​De…...

如何配置一个sql server使得其它用户可以通过excel odbc获取数据

要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据&#xff0c;你需要完成以下配置步骤&#xff1a; ✅ 一、在 SQL Server 端配置&#xff08;服务器设置&#xff09; 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到&#xff1a;SQL Server 网络配…...

【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解

一、前言 在HarmonyOS 5的应用开发模型中&#xff0c;featureAbility是旧版FA模型&#xff08;Feature Ability&#xff09;的用法&#xff0c;Stage模型已采用全新的应用架构&#xff0c;推荐使用组件化的上下文获取方式&#xff0c;而非依赖featureAbility。 FA大概是API7之…...