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 文件系统创建流程中的位置与关系:
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 文件后,可以通过以下方式验证其是否正常工作:
- 查看文件是否存在:
ls -l /proc/my_status - 读取文件内容:
cat /proc/my_status - 检查内核日志:
dmesg | tail查看驱动初始化/退出时的打印信息。 - 使用
strace跟踪:strace cat /proc/my_status观察系统调用流程。
调试技巧: 如果
cat 返回空或报错,首先检查 file_operations 中的 .read 回调是否正确实现,并确认 simple_read_from_buffer 的 ppos 处理逻辑无误。