Linux打开一个文件并读取内容的详细流程【inode结构体、fd文件描述符、`struct file`结构体、一个打开普通文件和一个打开设备文件的详细流程分析】

inode结构体

inode结构体概述

Linux打开一个文件中的核心点是inode结构体,所以我们就从inode结构体开始讲这个问题。

inode应该来源于“index node”的缩写,所以发音为 [aɪ noʊd]

在Linux系统中,一个文件通常对应一个inode(索引节点),特殊情况下可能有多个文件名与一个inode对应,或者一个文件涉及多个inode,特殊情况我们不作考虑,在这里,我们只需要知道,在Linux系统中,一个文件通常对应一个inode。

inode(索引节点)是文件系统中的数据结构,存储文件的元信息(例如文件大小、权限、所有者、时间戳等),但不包含文件名。

每个文件在文件系统中都会分配一个唯一的inode。

具体来说,inode 在 Linux 内核中是一个结构体,定义在内核的源代码中,具体在头文件 include/linux/fs.h 中。它是文件系统的核心数据结构之一,用于表示文件的元数据。

struct inode 的基本结构

inode 结构体的定义包含许多字段,负责描述文件的各种属性和操作。例如:

struct inode {
    umode_t          i_mode;       /* 文件类型和权限 */
    unsigned short   i_opflags;    /* 操作相关标志 */
    kuid_t           i_uid;        /* 文件所有者的用户ID */
    kgid_t           i_gid;        /* 文件所有者的组ID */
    unsigned int     i_flags;      /* 文件标志 */

    const struct inode_operations *i_op; /* 文件操作方法 */
    struct super_block *i_sb;      /* 所属超级块 */
    struct address_space *i_mapping; /* 地址空间 */

    loff_t           i_size;       /* 文件大小 */
    struct timespec64 i_atime;     /* 最后访问时间 */
    struct timespec64 i_mtime;     /* 最后修改时间 */
    struct timespec64 i_ctime;     /* 状态更改时间 */

    unsigned int     i_nlink;      /* 硬链接计数 */
    ino_t            i_ino;        /* inode编号 */
    dev_t            i_rdev;       /* 主设备号和次设备号(如果是设备文件) */

	const struct file_operations *i_fop; /* 文件操作结构体,存储着诸如open,read,write等函数 */

    /* ...更多字段,用于缓存、锁、权限检查等... */
};

关键字段说明

  1. i_mode

    • 保存文件的类型(如普通文件、目录、设备文件)和权限信息。
    • 通过掩码可以检查文件类型,例如 S_ISREG 检查是否是普通文件。
  2. i_uidi_gid

    • 文件的所有者用户ID和组ID。
  3. i_size

    • 文件的大小(以字节为单位)。
  4. i_atime / i_mtime / i_ctime

    • 分别表示最后访问时间、最后修改时间和元数据更改时间。
  5. i_nlink

    • 硬链接计数,指向同一个inode的目录项数量。当计数降为0时,文件数据会被释放。
  6. i_op

    • 指向一个 inode_operations 结构体,用于定义文件系统对该inode支持的操作(例如创建、删除)。
  7. i_mapping

    • 表示文件的地址空间,通常用来管理文件数据的缓存。
  8. i_ino

    • inode编号,文件系统中每个文件的唯一标识。
  9. i_rdev

    • 里面存储着主设备号和次设备号信息。
  10. i_fop

    • 文件操作结构体,它实际上就是链接着文件操作结构体,比如驱动程序中的文件操作结构体,里面定义着文件的底层打开函数、读函数、写函数等。

inode 的核心作用

  1. 存储元信息

    • 保存文件的基本属性,比如权限、大小、时间戳等。
  2. 连接文件数据

    • inode 不直接保存文件数据,而是通过其他结构体(如块地址映射表)指向数据块。
  3. 支持文件操作

    • 内核通过 struct inode 来调度文件系统操作。

小结

inode 是 Linux 文件系统中用于描述文件的核心结构体,它包含了文件的所有元信息以及与文件数据的关联。虽然用户空间程序看不到 inode 结构体的细节,但文件系统操作(如openread)背后都依赖于它。

当我们获得了文件的inode后,就可以借由inode来进一步获取文件中的信息。

文件描述符(fd)及与文件描述符直接关联的结构体struct file

文件描述符(fd)

如果我们要访问一个文件的内容,通常的流程是这样的:
文件系统首先通过文件路径和文件名在目录中查找对应的inode,并根据inode中的元信息定位文件的存储位置。
接着,文件系统打开文件并返回一个文件描述符(通常用fd表示),文件描述符是进程内的一个整数标识符,在这个进程中可以用来进一步访问文件内容,而不用每次都再去打开文件。

请注意文件描述符只是进程内的一个整数标识,它对应的文件信息其实是存储在结构体struct file中的,所以我们需要详细了解下结构体struct file

结构体struct file

在 Linux 内核中,打开文件时会涉及到另一个重要的结构体 struct file,它与 struct inode 一起协同工作,负责文件的实际操作和管理。


struct file 的作用

  • struct file 表示一个已打开的文件,即文件打开后的状态。
  • 它与用户空间的文件描述符(file descriptor)直接关联。
  • 每次调用 open 系统调用时,内核会为该文件创建一个新的 struct file 实例,用来管理这个打开的文件。

struct filestruct inode 的区别

struct inodestruct file
表示文件在文件系统中的元信息和属性表示一个已打开的文件实例(与进程相关)
一个文件在文件系统中只有一个 inode每次打开文件会创建一个新的 struct file
包含文件权限、大小、时间戳等信息包含文件的当前偏移量、模式、操作等信息
共享于多个文件描述符(通过硬链接等)文件描述符对应唯一的 struct file 实例

struct file 的关键字段

struct file 定义在内核头文件 include/linux/fs.h 中,主要字段包括:

struct file {
    const struct file_operations *f_op;   /* 文件操作方法 */
    struct inode        *f_inode;        /* 指向对应的 inode */
    void                *private_data;   /* 私有数据(通常是驱动使用) */
    loff_t              f_pos;           /* 文件的当前偏移量 */
    unsigned int        f_flags;         /* 打开文件的标志(如读写模式) */
    struct path         f_path;          /* 文件路径信息 */
    atomic_long_t       f_count;         /* 引用计数 */
    /* 其他字段省略 */
};
主要字段解释:
  1. f_op:

    • 指向 struct file_operations,定义了文件支持的操作(如 readwriteioctl 等)。
    • 不同的文件类型(如普通文件、设备文件)会提供不同的操作实现。
  2. f_inode:

    • 指向文件对应的 struct inode,通过它可以访问文件的元信息。
  3. f_pos:

    • 文件的当前读写位置(偏移量)。
  4. f_flags:

    • 打开文件时的标志(如 O_RDONLY, O_WRONLY, O_RDWR)。
  5. private_data:

    • 文件的私有数据,常用于驱动程序中存储设备相关信息。
  6. f_path:

    • 表示文件的路径信息(包括文件的 dentryvfsmount)。

打开文件的核心流程

当调用 open 系统调用时,内核的处理流程如下:

  1. 路径解析

    • 根据文件路径解析到 inode,找到对应的 struct inode
  2. 创建 struct file

    • 为每个打开的文件分配一个新的 struct file,并初始化其字段。
    • struct filef_inode 字段会指向文件的 inode
  3. 文件描述符与 struct file 的关联

    • struct file 注册到当前进程的文件表中,并分配一个文件描述符(整数值)。
    • 文件描述符用于用户空间访问,而 struct file 是内核的实际表示。

struct file 在文件操作中的作用

struct file 主要用于管理打开文件的状态和操作。例如:

  • 读写操作
    • 当调用 read(fd)write(fd) 时,内核会根据文件描述符找到对应的 struct file,并调用其 f_op 字段中定义的操作函数。
  • 偏移量管理
    • f_pos 保存了文件的当前偏移量,支持顺序读写操作。

小结

在 Linux 内核中:

  • struct inode 是文件的静态表示,存储文件的元信息。
  • struct file 是打开文件的动态表示,描述了文件打开后的状态(如偏移量、模式等)。
  • 两者协作完成文件系统的读写和管理操作。每次打开文件,都会生成一个新的 struct file,但它们可能共享同一个 struct inode(例如通过硬链接)。

访问一个文件的内容,通常的流程

流程概述

当调用系统函数 open 打开一个文件时,内核的处理流程如下:

  1. 路径解析

    • 根据文件路径解析到 inode,找到对应的 struct inode
  2. 创建 struct file

    • 为每个打开的文件分配一个新的 struct file,并初始化其字段。
    • struct filef_inode 字段会指向文件的 inode
  3. 文件描述符与 struct file 的关联

    • struct file 注册到当前进程的文件表中,并分配一个文件描述符(整数值)。
    • 文件描述符用于用户空间访问,而 struct file 是内核的实际表示。

调用系统函数 open 打开一个文件后,最终返回了文件描述符,文件描述符是进程内的一个整数标识符,它与一个结构体 struct file 相关联,里面存储着文件及对文件操作的各种上下文信息,从而,在这个进程后续的操作中,可以用文件描述符内进一步访问文件内容或向文件写内容。

一个打开并操作普通文件的例子

一个操作普通文件的例子如下:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open("/home/user/file.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    char buffer[1024];
    ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);
    if (bytesRead > 0) {
        buffer[bytesRead] = '\0';
        printf("File content: %s\n", buffer);
    } else {
        perror("read");
    }

    close(fd);
    return 0;
}

在上面的例子中,通过文件路径/home/user/file.txt找到文件file.txt对应的inode,找到文件file.txtinode后,我们就知道了文件file.txt的存储位置,如果inode中的信息显示这个文件有可以打开权限,那么系统的open()函数便能打开它,并且返回属于这个进程的文件描述fd,相关代码如下:

    int fd = open("/home/user/file.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

借由这个文件描述符,那么在该进程内我们需要再次操作这个文件时,便不需要再去打开操作了。
比如上面的示例中,代码:

 ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);

便是进程内借由文件描述符fd去读取文件内容。之所以要少读一个,原因见 https://blog.youkuaiyun.com/wenhao_ir/article/details/144671071

写文件的流程这里就再叙述了,理解了inode和文件描述符的概念后,再配合一个读的示例,这个问题基本就搞清了。

一个打开驱动程序的设备文件的例子

假设驱动程序的文件操作结构体file_operations如下定义:

static struct file_operations hello_drv_fops = {
    .owner = THIS_MODULE,
    .open = hello_drv_open,
    .release = hello_drv_close,
};

设备底层打开函数hello_drv_open的代码如下:

static int hello_drv_open(struct inode *node, struct file *file)
{
    dev_t dev = node->i_rdev;
    int major = MAJOR(dev);
    int minor = MINOR(dev);

    printk("Opening device: major=%d, minor=%d\n", major, minor);

    // 为每次打开操作分配私有数据
    file->private_data = kmalloc(sizeof(int), GFP_KERNEL);
    if (!file->private_data) {
        printk("Failed to allocate memory for private data\n");
        return -ENOMEM;
    }
    *(int *)file->private_data = 42; // 示例数据

    return 0;
}

这个函数的两个参数便使用到了结构体inode和结构体file
当系统函数open尝度打开设备文件/dev/hello时,即下面的代码:

    // 打开设备文件
    fd = open("/dev/hello", O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        return errno;
    }

系统会根据设备文件路径/dev/hello找到文件的inode实例,然后准备利用inode结构体打开文件。
打开文件前首先会为准备找开的文件分配一个 struct file实例,这个结构体在上文中已经讲清楚了,这里不再多述。
然后正式开始打开文件,打开文件时找到inode结构体的成员i_fop,其定义如下:

const struct file_operations *i_fop; /* 文件操作结构体,存储着诸如open,read,write等函数 */

它实际上指向着我们在驱动程序中定义的设备结构体,代码如下:

static struct file_operations hello_drv_fops = {
    .owner = THIS_MODULE,
    .open = hello_drv_open,
    .release = hello_drv_close,
};

这个结构体成员中便存储着设备底层的打开函数,然后通过它获得设备的底层打开函数,在这里就是函数hello_drv_open(),并且把系统自动生成的结构体struct inode 和结构体 struct file的实例作为参数传递给函数hello_drv_open(),函数hello_drv_open()最终利用这两个参数完成设备文件的打开操作,当然,函数hello_drv_open()的函数其实并不一定非得用到这两个参数,不用也可以,只是系统的open()函数会把这两个参数传递给设备结构体hello_drv_fops 中的打开函数hello_drv_open()。在我们这里给出的例子中,用到了这两个参数,我们利用参数struct inode *node打印出了设备号,利用参数struct file *file记录了一些私有数据,这些数据可以根据我们的需要用作不同的用途。

如果一切正常,系统的open()函数会为此次设备文件的打开分配一个文件描述符(fd),并将文件描述符(fd)的值记录到结构体 struct file的实例中。在进程的后续操作中,便可利用这个文件文件描述符(fd)对文件进行一系列操作。

以上便是一个打开驱动程序的设备文件的例子的详细流程。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昊虹AI笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值