4. 创建 proc 目录:proc_mkdir 函数详解、多级目录与权限设置

在 proc 文件系统中,目录(directory)是组织虚拟文件节点的核心容器。与普通文件系统不同,proc 目录并不对应物理存储介质上的目录项,而是通过内核函数 proc_mkdir 在内存中动态创建的虚拟目录节点。本章将深入解析 proc_mkdir 的函数原型、参数含义、多级目录创建策略以及目录权限的精细控制方法。

4.1 proc_mkdir 函数原型与参数详解

proc_mkdir 是 Linux 内核提供用于在 proc 文件系统中创建目录的核心 API。其函数原型定义在 <linux/proc_fs.h> 头文件中:

struct proc_dir_entry *proc_mkdir(const char *name,
                                  struct proc_dir_entry *parent);
参数 类型 说明
name const char * 目录名称字符串,仅支持单级目录名(如 "my_driver"),不能包含路径分隔符 /。
parent struct proc_dir_entry * 父目录的 proc_dir_entry 指针。若为 NULL,则目录创建在 proc 根目录 /proc 下。
返回值 struct proc_dir_entry * 成功时返回新创建目录的 proc_dir_entry 指针;失败返回 NULL。
关键提示:proc_mkdir 创建的目录默认权限为 0555(读+执行,无写权限),且目录的所有者与组均为 root。如果需要自定义权限,必须使用 proc_mkdir_mode 函数。

4.2 单级目录创建实战

以下代码演示如何在 /proc 根目录下创建一个名为 my_driver 的目录:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>

static struct proc_dir_entry *my_dir;

static int __init my_init(void)
{
    // 在 /proc 根目录下创建 "my_driver" 目录
    my_dir = proc_mkdir("my_driver", NULL);
    if (!my_dir) {
        pr_err("Failed to create /proc/my_driver\n");
        return -ENOMEM;
    }
    pr_info("Directory /proc/my_driver created successfully\n");
    return 0;
}

static void __exit my_exit(void)
{
    if (my_dir)
        proc_remove(my_dir);
    pr_info("Directory /proc/my_driver removed\n");
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
验证方法:加载模块后执行 ls -ld /proc/my_driver,应看到 dr-xr-xr-x 2 root root 0 ... 的目录项。

4.3 多级目录创建策略

proc 文件系统不支持类似 mkdir -p 的递归创建方式。要创建多级目录(如 /proc/my_driver/sub_dir),必须逐级调用 proc_mkdir,并保存每一级的 proc_dir_entry 指针作为下一级的父目录参数。

常见陷阱:不能直接使用 proc_mkdir("my_driver/sub_dir", NULL),因为 proc_mkdir 不解析路径分隔符,该调用会尝试创建名为 "my_driver/sub_dir" 的单级目录,导致失败。

正确的多级目录创建流程如下:

static struct proc_dir_entry *parent_dir;
static struct proc_dir_entry *child_dir;

static int __init my_init(void)
{
    // 第一级:创建 /proc/my_driver
    parent_dir = proc_mkdir("my_driver", NULL);
    if (!parent_dir)
        return -ENOMEM;

    // 第二级:在 /proc/my_driver 下创建 sub_dir
    child_dir = proc_mkdir("sub_dir", parent_dir);
    if (!child_dir) {
        proc_remove(parent_dir);
        return -ENOMEM;
    }

    pr_info("Created /proc/my_driver/sub_dir\n");
    return 0;
}

static void __exit my_exit(void)
{
    // 只需移除顶层目录,内核会递归清理子目录
    if (parent_dir)
        proc_remove(parent_dir);
}
最佳实践:移除目录时只需调用 proc_remove 移除顶层目录,内核会自动递归释放所有子目录和文件节点。切勿单独移除子目录后再移除父目录,可能导致双重释放。

4.4 目录权限设置:proc_mkdir_mode 函数

当需要自定义目录的访问权限时,应使用 proc_mkdir_mode 函数。其原型如下:

struct proc_dir_entry *proc_mkdir_mode(const char *name,
                                       umode_t mode,
                                       struct proc_dir_entry *parent);

与 proc_mkdir 相比,多了一个 mode 参数,用于指定目录的权限位(使用标准的 Unix 权限掩码)。

权限宏 数值 含义
S_IRUGO 0444 所有用户只读
S_IRWXUGO 0777 所有用户读、写、执行
S_IRUSR | S_IXUSR 0500 所有者读+执行,其他无权限
S_IRWXU | S_IRGRP | S_IXGRP 0750 所有者完全控制,组用户读+执行

示例:创建一个仅所有者可读写执行、其他用户只读执行的目录:

static struct proc_dir_entry *secure_dir;

static int __init my_init(void)
{
    // 权限:所有者 rwx (0700),组用户 r-x (050),其他用户 r-x (050)
    secure_dir = proc_mkdir_mode("secure", S_IRWXU | S_IRGRP | S_IXGRP |
                                          S_IROTH | S_IXOTH, NULL);
    if (!secure_dir)
        return -ENOMEM;

    pr_info("Created /proc/secure with mode 0755\n");
    return 0;
}

4.5 目录创建与权限设置流程总览

下图展示了 proc 目录从创建到权限设置的完整流程:

proc 目录创建与权限设置流程 开始:模块初始化 是否需要自定义权限? 否 是 proc_mkdir(name, parent) proc_mkdir_mode(name, mode, parent) 检查返回值是否为 NULL? 是 否 返回 -ENOMEM 目录创建成功 结束:模块初始化完成 注意事项 • 多级目录需逐级创建,每级保存父目录指针 • 移除目录时只需调用 proc_remove 移除顶层目录

4.6 目录权限与安全注意事项

  • 最小权限原则:proc 目录默认权限 0555 通常已足够。除非确实需要用户写入目录(如动态创建子目录),否则不应开放写权限。
  • 权限继承:在 proc 目录下创建的文件或子目录,其权限由各自的创建函数独立指定,不会自动继承父目录权限。
  • root 用户限制:即使目录权限设置为 0777,非 root 用户也无法通过 mkdir 或 rmdir 系统调用修改 proc 目录结构,因为 proc 文件系统在内核层面限制了目录修改操作。
  • 调试建议:开发阶段可使用 proc_mkdir_mode 设置 0755 权限,方便调试时查看目录内容;发布前应收紧权限。

4.7 常见错误与排查

错误现象 可能原因 解决方案
proc_mkdir 返回 NULL 内核内存不足,或目录名已存在 检查 dmesg 输出;使用 ls /proc 确认目录是否已存在
目录创建成功但无法访问 权限设置过严(如 0000) 使用 proc_mkdir_mode 设置至少 0555 权限
多级目录创建失败 父目录指针传递错误或父目录已被移除 确保每级目录创建后检查返回值,并正确传递指针
模块卸载时内核崩溃 重复移除目录或移除顺序错误 只调用一次 proc_remove 移除顶层目录
调试技巧:在 dmesg 中启用 proc 调试信息(echo 1 > /sys/kernel/debug/proc/level),可以查看目录创建和移除的详细内核日志。