4. 创建proc目录与文件

在 Linux 内核中,proc 文件系统为内核空间与用户空间提供了一种轻量级的交互接口。驱动开发者通过创建 proc 目录和文件,可以方便地导出运行时状态、调试信息或配置参数。本章将深入剖析三个核心 API:proc_mkdir、proc_create 和 proc_create_data,涵盖其原型、参数含义、使用场景及实现细节。

前置知识: 确保已包含头文件 #include <linux/proc_fs.h>,并在模块初始化/退出时正确管理 proc 节点的生命周期。

4.1 proc_mkdir:创建 proc 目录节点

proc_mkdir 用于在 /proc 文件系统中创建一个新的目录。目录本身不承载数据,但可以作为组织多个 proc 文件的容器,避免根目录下文件过多。

函数原型

struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent);

参数说明

参数 类型 描述
name const char * 目录名称,例如 "my_driver"。
parent struct proc_dir_entry * 父目录指针。若为 NULL,则在 /proc 根目录下创建。

返回值

  • 成功:返回指向新创建目录的 struct proc_dir_entry * 指针。
  • 失败:返回 NULL。

典型用法

static struct proc_dir_entry *my_proc_dir;

static int __init my_init(void)
{
    my_proc_dir = proc_mkdir("my_driver", NULL);
    if (!my_proc_dir) {
        pr_err("Failed to create /proc/my_driver\n");
        return -ENOMEM;
    }
    return 0;
}

static void __exit my_exit(void)
{
    if (my_proc_dir)
        remove_proc_entry("my_driver", NULL);
}
注意: 删除目录时,必须先删除目录下的所有文件,再删除目录本身。推荐使用 remove_proc_subtree() 递归删除整个子树。

4.2 proc_create:创建 proc 文件(标准方式)

proc_create 是创建 proc 文件最常用的接口,它允许开发者指定文件权限、父目录以及一组文件操作回调(struct file_operations)。

函数原型

struct proc_dir_entry *proc_create(const char *name, umode_t mode,
                                   struct proc_dir_entry *parent,
                                   const struct file_operations *proc_fops);

参数说明

参数 类型 描述
name const char * 文件名,例如 "status"。
mode umode_t 文件权限,如 0444(只读)、0666(读写)。
parent struct proc_dir_entry * 父目录指针。若为 NULL,则在 /proc 根目录下创建。
proc_fops const struct file_operations * 指向文件操作结构体的指针,定义 read、write 等回调。

返回值

  • 成功:返回 struct proc_dir_entry * 指针。
  • 失败:返回 NULL。

完整示例:创建只读 proc 文件

static ssize_t my_read(struct file *file, char __user *buf,
                       size_t count, loff_t *ppos)
{
    const char *msg = "Hello from proc!\n";
    return simple_read_from_buffer(buf, count, ppos, msg, strlen(msg));
}

static const struct file_operations my_fops = {
    .owner = THIS_MODULE,
    .read = my_read,
};

static struct proc_dir_entry *my_proc_file;

static int __init my_init(void)
{
    my_proc_file = proc_create("my_status", 0444, NULL, &my_fops);
    if (!my_proc_file) {
        pr_err("Failed to create /proc/my_status\n");
        return -ENOMEM;
    }
    return 0;
}

static void __exit my_exit(void)
{
    remove_proc_entry("my_status", NULL);
}

4.3 proc_create_data:带私有数据的 proc 文件

proc_create_data 是 proc_create 的增强版本,它允许在创建文件时传入一个私有数据指针(void *data)。这个数据指针可以在文件操作回调中通过 PDE_DATA(inode) 宏获取,非常适合将驱动中的特定结构体或上下文传递给 proc 文件操作函数。

函数原型

struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
                                        struct proc_dir_entry *parent,
                                        const struct file_operations *proc_fops,
                                        void *data);

参数对比

函数 额外参数 适用场景
proc_create 无 简单的全局状态导出,无需区分多个实例。
proc_create_data void *data 多实例驱动、需要传递设备结构体、或需要区分不同 proc 文件上下文。

私有数据获取宏

// 在 file_operations 回调中获取私有数据
struct my_device *dev = PDE_DATA(inode);

完整示例:使用私有数据传递设备结构体

struct my_device {
    int id;
    char name[32];
};

static ssize_t my_dev_read(struct file *file, char __user *buf,
                           size_t count, loff_t *ppos)
{
    struct my_device *dev = PDE_DATA(file_inode(file));
    char kbuf[64];
    int len;

    len = snprintf(kbuf, sizeof(kbuf), "id=%d, name=%s\n", dev->id, dev->name);
    return simple_read_from_buffer(buf, count, ppos, kbuf, len);
}

static const struct file_operations my_dev_fops = {
    .owner = THIS_MODULE,
    .read = my_dev_read,
};

static struct my_device dev_a = { .id = 1, .name = "device-A" };
static struct proc_dir_entry *proc_file_a;

static int __init my_init(void)
{
    proc_file_a = proc_create_data("dev_a", 0444, NULL, &my_dev_fops, &dev_a);
    if (!proc_file_a) {
        pr_err("Failed to create /proc/dev_a\n");
        return -ENOMEM;
    }
    return 0;
}

static void __exit my_exit(void)
{
    remove_proc_entry("dev_a", NULL);
}

4.4 三函数关系与选择策略

下图展示了三个 API 在 proc 文件系统创建流程中的位置与关系:

proc 目录与文件创建 API 关系图 proc 目录与文件创建 API 关系图 驱动模块初始化 proc_mkdir("my_driver", NULL) 创建 /proc/my_driver 目录 proc_create("status", 0444, parent_dir, &fops) 标准方式,无私有数据 proc_create_data("dev", 0444, parent_dir, &fops, &data) 带私有数据,多实例支持 .read = my_read .read = my_dev_read PDE_DATA(inode) 获取 data 选择策略:单实例、无上下文 → proc_create;多实例、需传递设备结构体 → proc_create_data 目录创建始终使用 proc_mkdir,文件创建后通过 remove_proc_entry 或 remove_proc_subtree 清理

4.5 关键差异与最佳实践

特性 proc_mkdir proc_create proc_create_data
创建对象 目录 文件 文件
文件操作回调 无(目录不可读写) 必须提供 file_operations 必须提供 file_operations
私有数据支持 不适用 不支持 支持,通过 PDE_DATA() 获取
典型用途 组织文件,创建子目录 导出简单全局信息 多设备实例、传递驱动上下文
清理函数 remove_proc_entry 或 remove_proc_subtree remove_proc_entry remove_proc_entry
最佳实践总结:
  • 始终检查返回值是否为 NULL,避免空指针解引用。
  • 使用 proc_create_data 替代 proc_create + 全局变量,提升代码可维护性。
  • 在模块退出函数中,按创建顺序的逆序清理 proc 节点,先删文件,再删目录。
  • 对于包含子文件的目录,优先使用 remove_proc_subtree() 一键递归删除。

4.6 调试与验证

创建 proc 文件后,可以通过以下方式验证其是否正常工作:

  1. 查看文件是否存在: ls -l /proc/my_status
  2. 读取文件内容: cat /proc/my_status
  3. 检查内核日志: dmesg | tail 查看驱动初始化/退出时的打印信息。
  4. 使用 strace 跟踪: strace cat /proc/my_status 观察系统调用流程。
调试技巧: 如果 cat 返回空或报错,首先检查 file_operations 中的 .read 回调是否正确实现,并确认 simple_read_from_buffer 的 ppos 处理逻辑无误。