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

Linux | 文件描述符

文章目录

    • 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.kler.cn/a/543514.html

相关文章:

  • w~自动驾驶~合集17
  • git学习【个人记录b站尚硅谷】
  • C语言:目标文件和可执行文件里面都有什么?
  • Zabbix-监控SSL证书有效期
  • 中间件-安装Minio-集成使用(ubantu-docker)
  • 【数据库设计】深入理解常见范式
  • 【LeetCode】时间复杂度和空间复杂度
  • bug-ant下拉框解决下拉框跟随表单容器(指定下拉框挂载容器):getPopupContainer=“p=>p.parentNode“
  • snort3.0-ubuntu18.04 64入侵检测安装与使用ailx10ailx10​​知乎知识会员
  • LabVIEW用户界面(UI)和用户体验(UX)设计
  • Spring排序机制:接口与注解的使用
  • 据称苹果与阿里巴巴将合作为中国iPhone用户开发AI功能
  • 二分算法篇:二分答案法的巧妙应用
  • JavaScript 对象方法全面解析
  • 【蓝耘平台与DeepSeek强强联手】:深度探索AI应用实践
  • 网络安全 | SNI介绍及F5中的配置应用
  • 【Day38 LeetCode】动态规划DP 子序列问题Ⅱ
  • java lambda表达式
  • 电机实验曲线数据提取
  • 3、《Spring Boot 常见注解详解》
  • Node.js中的npm包:从入门到实践指南
  • 《qt open3d中添加半径滤波》
  • QGIS 导入表格乱码问题的全面解析与解决方案
  • 第一天:爬虫介绍
  • 详细解释一下HTTPS握手过程中的密钥交换?
  • 蓝桥杯(B组)-每日一题