Linux | 文件描述符

news2025/2/14 2:49:46

文章目录

    • Linux | 文件描述符
      • 1. 文件描述符概述
      • 2. 与文件描述符关联的数据结构
        • 2.1 进程级的文件描述符表(`struct files_struct`)
        • 2.2 文件描述符表项(`struct fdtable`)
        • 2.3 文件对象(`struct file`)
        • 2.4 索引节点(`struct inode`)
      • 3. 数据结构之间的关系
      • 4. 文件描述符相关系统调用
        • 4.1 `open` 函数
        • 4.2 `read` 函数
        • 4.3 `write` 函数
        • 4.4 `close` 函数
      • 5. 代码示例及现象观察
        • 5.1 示例代码
        • 5.2 代码解释
        • 5.3 编译和运行
        • 5.4 现象观察
      • 6. 标准文件描述符示例

Linux | 文件描述符

1. 文件描述符概述

在 Linux 系统里,文件描述符(File Descriptor)是一个非负整数,它是对文件或者 I/O 设备(如键盘、显示器等)的抽象表示。当进程打开一个现有文件或者创建一个新文件时,内核会为该进程分配一个文件描述符,进程通过这个文件描述符来对文件进行各种操作,例如读取、写入、关闭等。

每个进程都有一个对应的文件描述符表,文件描述符就是该表的索引。系统默认会为每个进程打开三个标准的文件描述符:

  • 标准输入(stdin:文件描述符为 0,通常关联键盘,用于读取用户输入。
  • 标准输出(stdout:文件描述符为 1,通常关联显示器,用于输出信息。
  • 标准错误(stderr:文件描述符为 2,也关联显示器,主要用于输出错误信息。

2. 与文件描述符关联的数据结构

2.1 进程级的文件描述符表(struct files_struct

每个进程都有一个 files_struct 结构体,它管理着该进程打开的所有文件描述符。这个结构体包含一个指针数组,数组的索引就是文件描述符,每个数组元素指向一个 file 结构体。

struct files_struct {
    atomic_t count;
    struct fdtable __rcu *fdt;
    struct fdtable fdtab;
    // 其他成员...
};
  • count:表示当前结构体的引用计数。
  • fdt:指向一个 fdtable 结构体,该结构体包含了文件描述符表的具体信息。
2.2 文件描述符表项(struct fdtable

fdtable 结构体包含了文件描述符数组以及其他相关信息,它是 files_struct 结构体中 fdt 指针所指向的对象。

struct fdtable {
    unsigned int max_fds;
    struct file __rcu **fd;      /* current fd array */
    unsigned long *close_on_exec;
    unsigned long *open_fds;
    unsigned long *full_fds_bits;
    struct rcu_head rcu;
    // 其他成员...
};
  • max_fds:表示当前文件描述符表的最大容量。
  • fd:是一个指向 file 结构体指针的数组,数组的索引就是文件描述符。
2.3 文件对象(struct file

file 结构体代表一个打开的文件,它包含了文件的状态信息、文件偏移量、操作函数指针等。

struct file {
    //.....
    atomic_long_t f_count;		/*引用计数,管理文件对象的生命周期*/
    struct mutex f_pos_lock;	/*保护文件未知的互斥锁*/
    loff_t f_pos;				/*当前文件位置(读写位置)*/
    //.....    
    struct path  f_path;		/*记录文件路径*/
    struct inode *f_inode;      /*指向与文件相关联的inode对象的指针,该对象用于维护文件元数据,如文件类型、访问权限等*/
    const struct file_operations *f_op;
    							/*指向文件操作函数表的指针,定义了文件支持的操作,如读、写、锁定等*/
    unsigned int f_flags;		/*表示文件的打开标志,如`O_RDONLY`、`O_WRONLY`等*/
    fmode_t f_mode;				/*表示文件的访问模式*/		
    // 其他成员...
};
2.4 索引节点(struct inode

inode 结构体代表文件在磁盘上的元数据,包含了文件的权限、大小、创建时间、数据块位置等信息。

struct inode {
    umode_t           i_mode;
    unsigned short    i_opflags;
    kuid_t            i_uid;
    kgid_t            i_gid;
    unsigned int      i_flags;
    const struct inode_operations *i_op;
    struct super_block *i_sb;
    struct address_space *i_mapping;
    // 其他成员...
};
  • i_mode:表示文件的权限和类型。
  • i_uidi_gid:分别表示文件的所有者用户 ID 和组 ID。
  • i_op:指向一个 inode_operations 结构体,该结构体包含了对 inode 进行各种操作的函数指针。
  • i_sb:指向文件所在的超级块结构体。

3. 数据结构之间的关系

当一个进程调用 open 系统调用打开一个文件时,内核会进行以下操作:

  1. 在内核中创建一个 file 结构体,用于表示这个打开的文件,并初始化其成员变量,如 f_flagsf_mode 等。
  2. 从进程的 files_struct 结构体中找到一个空闲的文件描述符,该文件描述符对应的 fdtable 数组元素指向新创建的 file 结构体。
  3. 根据文件路径找到对应的 inode 结构体,并将 file 结构体中的 f_inode 指针指向该 inode 结构体。

这样,进程就可以通过文件描述符访问对应的 file 结构体,进而通过 file 结构体中的 f_op 指针调用相应的操作函数对文件进行读写等操作。

4. 文件描述符相关系统调用

4.1 open 函数

open 函数用于打开或创建一个文件,并返回一个文件描述符。其原型如下:

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
  • pathname:要打开或创建的文件的路径。
  • flags:打开文件的方式,例如 O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)等。
  • mode:如果创建新文件,指定文件的权限,例如 0666 表示所有用户都有读写权限。
4.2 read 函数

read 函数用于从文件描述符对应的文件中读取数据。其原型如下:

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
  • fd:文件描述符。
  • buf:用于存储读取数据的缓冲区。
  • count:要读取的字节数。
4.3 write 函数

write 函数用于向文件描述符对应的文件中写入数据。其原型如下:

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
  • fd:文件描述符。
  • buf:要写入的数据的缓冲区。
  • count:要写入的字节数。
4.4 close 函数

close 函数用于关闭一个文件描述符,释放相关资源。其原型如下:

#include <unistd.h>

int close(int fd);
  • fd:要关闭的文件描述符。

5. 代码示例及现象观察

5.1 示例代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define BUFFER_SIZE 1024

int main() {
    int fd;
    char buffer[BUFFER_SIZE] = "Hello, File Descriptor!";
    char read_buffer[BUFFER_SIZE];

    // 打开一个文件,如果文件不存在则创建,以只写模式打开
    fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 从数据结构角度看,此时内核创建了 file 结构体,从进程的 files_struct 找到空闲 fd 指向该 file 结构体,file 结构体的 f_inode 指向文件对应的 inode 结构体

    // 向文件中写入数据
    ssize_t bytes_written = write(fd, buffer, strlen(buffer));
    if (bytes_written == -1) {
        perror("write");
        close(fd);
        return 1;
    }
    printf("Written %zd bytes to test.txt\n", bytes_written);

    // 关闭文件
    if (close(fd) == -1) {
        perror("close");
        return 1;
    }

    // 以只读模式打开文件
    fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 从文件中读取数据
    ssize_t bytes_read = read(fd, read_buffer, BUFFER_SIZE);
    if (bytes_read == -1) {
        perror("read");
        close(fd);
        return 1;
    }
    read_buffer[bytes_read] = '\0';
    printf("Read %zd bytes from test.txt: %s\n", bytes_read, read_buffer);

    // 关闭文件
    if (close(fd) == -1) {
        perror("close");
        return 1;
    }

    return 0;
}
5.2 代码解释
  • 打开文件:使用 open 函数以只写模式打开 test.txt 文件,如果文件不存在则创建,同时截断文件内容。如果打开成功,会返回一个文件描述符。从数据结构角度,内核会创建一个 file 结构体,从进程的 files_struct 中找到一个空闲的文件描述符,让该文件描述符对应的 fdtable 数组元素指向新创建的 file 结构体,并且 file 结构体中的 f_inode 指针会指向文件对应的 inode 结构体。
  • 写入数据:使用 write 函数将字符串 "Hello, File Descriptor!" 写入文件。此操作会通过文件描述符找到对应的 file 结构体,然后根据 file 结构体中的 f_op 指针调用相应的写入函数。
  • 关闭文件:使用 close 函数关闭文件描述符,释放资源。此时会减少 file 结构体的引用计数 f_count,当引用计数为 0 时,内核会释放 file 结构体。
  • 再次打开文件:以只读模式打开 test.txt 文件,重复上述打开文件的流程。
  • 读取数据:使用 read 函数从文件中读取数据,并将其存储在 read_buffer 中。同样是通过文件描述符找到 file 结构体,调用 f_op 中的读取函数。
  • 输出结果:将读取到的数据输出到终端。
5.3 编译和运行

将上述代码保存为 file_descriptor.c,然后使用以下命令编译和运行:

gcc -o file_descriptor file_descriptor.c
./file_descriptor
5.4 现象观察
  • 运行程序后,会在当前目录下创建一个 test.txt 文件,并将字符串 "Hello, File Descriptor!" 写入该文件。
  • 程序会输出写入和读取的字节数,以及读取到的内容。

6. 标准文件描述符示例

#include <unistd.h>

int main() {
    const char *message = "Hello, Standard Output!\n";
    // 使用标准输出文件描述符(1)写入数据
    write(1, message, strlen(message));

    return 0;
}

在这个示例中,我们直接使用标准输出的文件描述符(1)将字符串 "Hello, Standard Output!" 输出到终端。编译并运行该程序,你会看到字符串显示在终端上。标准文件描述符对应的 file 结构体在进程启动时就已经初始化好,分别关联到标准输入、标准输出和标准错误设备。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2297669.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯-洛谷刷题-day5(C++)(为未完成)

1.P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布 i.题目 ii.代码 #include <iostream> #include <string> using namespace std;int N, Na, Nb; //0-"剪刀", 1-"石头", 2-"布", 3-"蜥", 4-"斯"&#xff1…

LVS 负载均衡集群(NAT模式)

一、环境准备 四台主机&#xff08;一台 LVS、两台 RS、一台客户端&#xff09; 1.1.LVS 主机 LVS 主机&#xff08;两块网卡&#xff09; 第一块&#xff1a;NAT模式&#xff08;内网&#xff09; 第二块&#xff1a;添加网卡&#xff08;仅主机模式&#xff09;&#xff0…

解决 DeepSeek 官网服务器繁忙的实用方案

解决 DeepSeek 官网服务器繁忙的实用方案 大家在使用 DeepSeek 时&#xff0c;是不是经常遇到官网服务器繁忙&#xff0c;等半天都加载不出来的情况&#xff1f;别担心&#xff0c;今天就给大家分享一个用 DeepSeek 硅基流动 Cherry Studio 解决这个问题的实用方案&#xff…

嵌入式八股文面试题(二)C语言算法

相关概念请查看文章&#xff1a;C语言概念。 1. 如何实现一个简单的内存池&#xff1f; 简单实现&#xff1a; #include <stdio.h> #include <stdlib.h>//内存块 typedef struct MemoryBlock {void *data; // 内存块起始地址struct MemoryBlock *next; // 下一个内…

#渗透测试#批量漏洞挖掘#LiveBos UploadFile 任意文件上传漏洞

免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停止本文章读。 目录 漏洞背景 漏洞成因 影响评估 检测方案 …

ds-download-link 插件:以独特图标选择,打造文章下载链接

源码介绍 “ds-download-link”插件为 WordPress 网站提供了在文章编辑器中添加下载链接的功能&#xff0c;每个下载链接都支持图标选择&#xff0c;并能将这些链接以美观的样式展示在文章前端页面。以下是该插件的主要特性和功能&#xff1a; 后台功能 在文章编辑器下方添加…

判断函数是否为react组件或lazy包裹的组件

function Modal(){return <p>123</p> } 实参里填入函数名,是false 实参里填入标签形式的函数,是true isValidElement(Modal)//false isValidElement(<Modal></Modal>)//true 官方说明 isValidElement – React 中文文档 但是官方并不建议用isValidE…

PHP 中的除以零错误

除以零错误&#xff08;Division by zero&#xff09;是指数字除以零的情况&#xff0c; 这在数学上是未定义的。在 PHP 中&#xff0c;处理这种错误的方式取决于 PHP 版本&#xff1a; PHP 7&#xff1a; 使用 / 运算符会产生一个警告 (E_WARNING) 并返回 false。 使用 intd…

【QT】控件 -- 多元素类 | 容器类 | 布局类

&#x1f525; 目录 一、多元素类1. List Widget -- 列表2. Table Widget -- 表格3. Tree Widget -- 树形 二、容器类1. Group Box -- 分组框2. Tab Widget -- 标签页 三、布局类1. 垂直布局【使用 QVBoxLayout 管理多个控件】【创建两个 QVBoxLayout】 2. 水平布局【使用 QHBo…

NO.15十六届蓝桥杯备战|while循环|六道练习(C++)

while循环 while语法形式 while 语句的语法结构和 if 语句⾮常相似&#xff0c;但不同的是 while 是⽤来实现循环的&#xff0c; if 是⽆法实现循环的。 下⾯是 while 循环的语法形式&#xff1a; //形式1 while ( 表达式 )语句; //形式2 //如果循环体想包含更多的语句&a…

kotlin标准库里面也有很多java类

Kotlin 标准库中确实存在许多与 Java 类直接关联或基于 Java 类封装的结构&#xff0c;但这并不是“问题”&#xff0c;而是 Kotlin 与 JVM 生态深度兼容和互操作性的体现。以下从技术原理和设计哲学的角度详细解释&#xff1a; 一、Kotlin 与 JVM 的底层关系 Kotlin 代码最终…

Flutter 双屏双引擎通信插件加入 GitCode:解锁双屏开发新潜能

在双屏设备应用场景日益丰富的当下&#xff0c;移动应用开发领域迎来了新的机遇与挑战。如何高效利用双屏设备优势&#xff0c;为用户打造更优质的交互体验&#xff0c;成为开发者们关注的焦点。近日&#xff0c;一款名为 Flutter 双屏双引擎通信插件的创新项目正式入驻 GitCod…

01、单片机上电后没有正常运行怎么办

单片机上电后没有运转, 首先要检查什么? 1、单片机供电是否正常? &电路焊接检查 如果连最基本的供电都没有,其它都是空谈啊!检查电路断路了没有?短路了没有?电源合适吗?有没有虚焊? 拿起万用表之前,预想一下测量哪里?供电电压应该是多少?对PCB上电压测量点要…

使用 EMQX 接入 LwM2M 协议设备

LwM2M 协议介绍 LwM2M 是一种轻量级的物联网设备管理协议&#xff0c;由 OMA&#xff08;Open Mobile Alliance&#xff09;组织制定。它基于 CoAP &#xff08;Constrained Application Protocol&#xff09;协议&#xff0c;专门针对资源受限的物联网设备设计&#xff0c;例…

使用 mkcert 本地部署启动了 TLS/SSL 加密通讯的 MongoDB 副本集和分片集群

MongoDB 是支持客户端与 MongoDB 服务器之间启用 TLS/SSL 进行加密通讯的, 对于 MongoDB 副本集和分片集群内部的通讯, 也可以开启 TLS/SSL 认证. 本文会使用 mkcert 创建 TLS/SSL 证书, 基于创建的证书, 介绍 MongoDB 副本集、分片集群中启动 TLS/SSL 通讯的方法. 我们将会在…

P3372 【模板】线段树 1【题解2】

本题题解分两篇 此篇为第贰篇&#xff0c;用树状数组做 第壹篇&#xff1a;P3372 【模板】线段树 1【题解1】 本文讲解树状数组解决区间修改区间查询 其它树状数组相关文章&#xff1a; 树状数组讲解单点修改/查询树状数组解决区间修改单点查询 P3372 【模板】线段树 1 题…

使用 EDOT 监测由 OpenAI 提供支持的 Python、Node.js 和 Java 应用程序

作者&#xff1a;来自 Elastic Adrian Cole Elastic 很自豪地在我们的 Python、Node.js 和 Java EDOT SDK 中引入了 OpenAI 支持。它们为使用 OpenAI 兼容服务的应用程序添加日志、指标和跟踪&#xff0c;而无需任何代码更改。 介绍 去年&#xff0c;我们宣布了 OpenTelemetry…

CNN-BiGRU卷积神经网络双向门控循环单元多变量多步预测,光伏功率预测

CNN-BiGRU卷积神经网络双向门控循环单元多变量多步预测&#xff0c;光伏功率预测 代码下载&#xff1a;CNN-BiGRU卷积神经网络双向门控循环单元多变量多步预测&#xff0c;光伏功率预测 一、引言 1.1、研究背景及意义 随着全球能源危机和环境问题的日益严重&#xff0c;可再…

mysql8.0使用MGR实现高可用与利用MySQL Router构建读写分离MGR集群

MGR是MySQL Group Replication的缩写&#xff0c;即MySQL组复制。 在以往&#xff0c;我们一般是利用MySQL的主从复制或半同步复制来提供高可用解决方案&#xff0c;但这存在以下几个比较严重的问题&#xff1a; 主从复制间容易发生复制延迟&#xff0c;尤其是在5.6以前的版本…

保研考研机试攻略:python笔记(4)

🐨🐨🐨15各类查找 🐼🐼二分法 在我们写程序之前,我们要定义好边界,主要是考虑区间边界的闭开问题。 🐶1、左闭右闭 # 左闭右闭 def search(li, target): h = len(li) - 1l = 0#因为都是闭区间,h和l都可以取到并且相等while h >= l:mid = l + (h - l) // 2…