3. procfs 核心数据结构:struct proc_dir_entry、proc_ops 与 inode/file_operations 关系

在 Linux 内核中,procfs 的每一个文件或目录都由一个核心数据结构 struct proc_dir_entry(简称 PDE)表示。理解 PDE、proc_ops 以及 VFS 层 inode/file_operations 之间的关系,是编写正确 proc 文件读写回调的基础。本章将深入剖析这三者的角色与协作机制。

3.1 struct proc_dir_entry:proc 文件系统的节点基石

struct proc_dir_entry 是 procfs 在内存中管理每一个条目的核心结构体。它记录了文件名、访问权限、所属目录、以及最重要的——操作函数集。虽然内核版本不同其内部字段略有差异,但关键成员始终保持一致。

字段名 类型 说明
name const char * 文件名(不含路径)
namelen u8 文件名长度
mode umode_t 文件权限位(如 0444)
nlink nlink_t 硬链接计数
uid kuid_t 文件所有者 UID
gid kgid_t 文件所属组 GID
proc_iops const struct inode_operations * inode 操作(如 lookup)
proc_fops const struct file_operations * 文件操作(read/write 等)
parent struct proc_dir_entry * 指向父目录的指针
subdir struct rb_root 子目录红黑树根
data void * 私有数据指针,驱动可自由使用
关键点: data 字段是驱动与 proc 文件之间传递上下文的重要桥梁。在注册 proc 文件时,可以将设备结构体、状态变量等任意指针存入 data,在读写回调中通过 PDE 取出使用。

3.2 proc_ops:现代 proc 文件的操作函数集

从 Linux 内核 5.6 版本开始,procfs 引入了 struct proc_ops 替代了传统的 struct file_operations 用于 proc 文件。这一变化的主要目的是将 proc 文件的操作接口与普通 VFS 文件操作解耦,使内核内部实现更清晰。

struct proc_ops {
    unsigned int    proc_flags;
    int        (*proc_open)(struct inode *, struct file *);
    ssize_t    (*proc_read)(struct file *, char __user *, size_t, loff_t *);
    ssize_t    (*proc_read_iter)(struct kiocb *, struct iov_iter *);
    ssize_t    (*proc_write)(struct file *, const char __user *, size_t, loff_t *);
    loff_t     (*proc_lseek)(struct file *, loff_t, int);
    int        (*proc_release)(struct inode *, struct file *);
    __poll_t   (*proc_poll)(struct file *, struct poll_table_struct *);
    int        (*proc_ioctl)(struct inode *, struct file *, unsigned int, unsigned long);
    int        (*proc_mmap)(struct file *, struct vm_area_struct *);
    unsigned long (*proc_get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};
兼容性提示: 在 5.6 之前的内核中,proc 文件使用 struct file_operations。如果你的驱动需要支持旧内核,可以使用条件编译(#if LINUX_VERSION_CODE < KERNEL_VERSION(5,6,0))来分别处理。本课程以 proc_ops 为主。

3.3 inode 与 file_operations 的关系:VFS 层的桥接

当用户空间程序通过系统调用(如 open()、read())访问 proc 文件时,VFS 层会执行以下关键步骤:

  1. 路径查找: VFS 根据路径名找到对应的 dentry 和 inode。
  2. inode 初始化: procfs 在创建 inode 时,会将 PDE 中的 proc_fops 赋值给 inode 的 i_fop(即 file_operations)。
  3. 打开文件: open() 系统调用创建 struct file,其 f_op 指针指向 inode 的 i_fop。
  4. 读写操作: 后续的 read()/write() 通过 file->f_op 找到对应的回调函数执行。

下图展示了从 PDE 到 VFS 操作函数的完整链路:

procfs 数据结构关系图 procfs 核心数据结构关系与 VFS 桥接 struct proc_dir_entry name: "mydev" mode: 0644 data: &my_device proc_fops: &my_proc_fops proc_iops: &proc_inode_ops struct proc_ops proc_open = my_open proc_read = my_read proc_write = my_write proc_release = my_release struct inode i_fop = proc_fops i_private = data struct file f_op = i_fop private_data proc_fops 指向 赋值给 i_fop open 时继承 流程: 注册时 PDE.proc_fops → 打开时 inode.i_fop → 读写时 file.f_op 驱动回调通过 PDE->data 获取私有上下文,实现设备与 proc 文件的数据交换。

3.4 注册与查找:PDE 的生命周期管理

驱动通过以下 API 创建和删除 proc 条目:

函数 说明
proc_create(name, mode, parent, proc_ops) 创建 proc 文件并关联 proc_ops
proc_create_data(name, mode, parent, proc_ops, data) 创建 proc 文件并设置私有数据 data
proc_mkdir(name, parent) 创建 proc 目录
remove_proc_entry(name, parent) 删除 proc 条目
proc_symlink(name, parent, target) 创建符号链接
注意: remove_proc_entry() 是同步操作,会等待所有正在使用该条目的进程退出后才释放内存。在模块卸载路径中,务必在释放相关资源之前调用此函数,否则可能导致内核崩溃。

3.5 回调函数中的上下文获取

在 proc_read 或 proc_write 回调中,驱动开发者通常需要获取注册时设置的私有数据。标准做法如下:

static ssize_t my_proc_read(struct file *file, char __user *buf,
                            size_t count, loff_t *ppos)
{
    // 方法1:通过 file->f_inode 获取 PDE,再取 data
    struct proc_dir_entry *pde = PDE(file->f_inode);
    struct my_device *dev = (struct my_device *)pde->data;

    // 方法2:直接使用 file->private_data(需在 open 中设置)
    struct my_device *dev = (struct my_device *)file->private_data;

    // ... 读取 dev 中的数据并拷贝到用户空间 ...
    return copied;
}

static int my_proc_open(struct inode *inode, struct file *file)
{
    // 在 open 中将私有数据存入 file->private_data
    struct proc_dir_entry *pde = PDE(inode);
    file->private_data = pde->data;
    return 0;
}
最佳实践: 推荐在 proc_open 中将 pde->data 赋值给 file->private_data,后续读写回调直接使用 file->private_data。这样既避免了每次重复查找 PDE,也与其他 VFS 文件操作的惯用风格保持一致。

3.6 数据结构关系总结

下表总结了三个核心结构体在 proc 文件系统中的角色与关联:

结构体 生命周期 主要职责 与驱动的关系
proc_dir_entry 从 proc_create 到 remove_proc_entry 管理 proc 文件元数据、权限、父子关系 驱动通过 data 字段绑定私有上下文
proc_ops 通常为全局静态变量,常驻内存 定义 proc 文件的操作回调函数指针 驱动实现具体的 read/write 逻辑
inode/file 每次打开创建,关闭释放 VFS 层与进程交互的接口 驱动通过 file->private_data 访问上下文