目录
一、说明
在Linux中开发调试内核模块时,可通过创建debug fs,实现运行时跟踪和调试内核信息的功能。
二、创建debug fs
2.1 创建接口
struct dentry *debugfs_create_file(const char *name, mode_t mode, struct dentry *parent, void *data, const struct file_operations *fops);
- name:要创建的文件的名称。
- mode:文件的权限模式。
- parent:父目录的dentry结构的指针,表示要在其中创建新文件的目录。
- data:可选的指针,用于传递数据给文件的操作函数。
- fops:指向实现文件操作的file_operations结构的指针。这个结构定义了文件的读取、写入等操作。
通常在内核模块的初始化函数中调用,以在加载模块时创建debug fs方便调试。调用该函数后,将创建一个文件名为name,权限为mode,父目录为parent,并使用fops参数中指定的操作函数来实现文件操作的debug fs。
2.2 权限模式
mode 参数用于指定文件的权限模式,它是一个整数值,通常以八进制表示。常见的权限模式参数包括:
- S_IRUSR(0400):用户可读
- S_IWUSR(0200):用户可写
- S_IXUSR(0100):用户可执行
- S_IRGRP(0040):组可读
- S_IWGRP(0020):组可写
- S_IXGRP(0010):组可执行
- S_IROTH(0004):其他人可读
- S_IWOTH(0002):其他人可写
- S_IXOTH(0001):其他人可执行
这些权限位可以组合使用,例如,若要创建一个用户可读写的文件,组和其他人只能读取的文件,你可以将权限模式设置为 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,即 0644。
2.3 file_operations
file_operations
是 Linux 内核中用于定义文件操作的结构体,包含了open read write
等操作:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
...
};
三、代码示例
以下是一个在linux内核模块加载时创建debug fs的代码示例:
3.1 实现 file_operations
读接口定义,在此示例中,读接口将字符串通过copy_to_user接口将data字符串拷贝到用户空间缓冲区:
static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
char data[] = "Hello, this is debug information!\n";
size_t data_len = strlen(data);
ssize_t ret;
// 确定要读取的数据长度
size_t read_len = min(count, data_len - *ppos);
// 检查是否已经读到文件末尾
if (*ppos >= data_len)
return 0;
// 将数据复制到用户空间缓冲区中
if (copy_to_user(buf, data + *ppos, read_len)) {
ret = -EFAULT;
goto out;
}
// 更新读取位置
*ppos += read_len;
ret = read_len;
out:
return ret;
}
写接口定义,在此接口中,实现了接受例如0x12345678
的16进制数据,并将数据写道reg_addr寄存器中:
static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned long reg_addr = ***;
char *data;
u32 reg_val = 0;
ssize_t ret;
// 解析用户空间数据
if (count < sizeof(u32))
return -EINVAL;
data = kzalloc(count, GFP_KERNEL);
if (!data)
return -ENOMEM;
// 方法一, 接收数据后转为u32:
if (copy_from_user(&data, buf, count))
return -EFAULT;
kstrtou32(data, 0, ®_val);
// 方法二,接收数据并直接转为u32:
kstrtou32_from_user(buf, count, 0, ®_val);
// 写入数据到寄存器
writel(data, (void __iomem *)reg_addr);
// 返回写入的字节数
ret = sizeof(u32) - *ppos;
*ppos += ret;
return ret;
}
static const struct file_operations my_fops = {
.read = my_read,
.write = my_write,
};
3.2 模块初始化时创建debug fs
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/debugfs.h>
static struct dentry *debugfs_root;
/*
...
file_operations实现代码
...
*/
static int __init my_init(void)
{
debugfs_root = debugfs_create_dir("my_debugfs", NULL);
if (!debugfs_root) {
printk(KERN_ERR "Failed to create debugfs directory\n");
return -ENOMEM;
}
// 在module init接口中,调用debugfs_create_file创建一个调试文件
// mode: 0666,表示文件用户、组、其它人可读可写
// my_fops指定文件操作接口
debugfs_create_file("my_debug_info", 0666, debugfs_root, NULL, &my_fops);
return 0;
}
static void __exit my_exit(void)
{
debugfs_remove_recursive(debugfs_root);
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MODULE Name");
四、其它类似接口
Linux 中还有一些其它类似创建fs的接口,但是创建的位置不同,约定的使用用途也多不相同,但是创建的方法都是相似的:指定创建位置、模式、file_operations 等。
4.1 proc文件系统
- proc文件系统是一个虚拟文件系统,通常用于显示内核状态、参数和配置;
- 使用proc_create()系列函数在/proc目录下创建文件;例如:
proc_create("my_proc_file", 0644, NULL, &my_proc_fops);
proc_create
接口使用方法与debugfs_create_file
类似.
4.2 sysfs 文件系统
- sysfs文件系统提供了一种机制,用于向用户空间公开内核信息和配置,可以用于设备驱动程序和内核模块之间的通信。
- 使用sysfs_create_file()函数在/sys目录下创建文件。
struct file *sysfs_create_file(struct kobject *kobj, const struct attribute *attr);
- kobj:指向包含要创建文件的内核对象的指针。通常,这是表示设备或驱动程序的内核对象。
- attr:指向 attribute 结构体的指针,描述了要创建的文件的属性,包含了文件的名称、权限模式、读取和写入函数等信息。
例如:
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define MAX_DATA_SIZE 1024
static ssize_t my_sysfs_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
// 在这里实现读取文件的逻辑
return sprintf(buf, "Hello from sysfs!\n");
}
static ssize_t my_sysfs_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
// 在这里实现写入文件的逻辑
// 这里只是简单的示例,将数据原样写入内核日志
pr_info("Received data from user: %.*s\n", (int)count, buf);
return count; // 返回写入的字节数
}
static struct attribute my_attr = {
.name = "my_sysfs_file",
.mode = S_IRUGO | S_IWUSR, // 设置文件的权限模式,包括读写权限
.show = my_sysfs_show,
.store = my_sysfs_store, // 设置写入函数指针
};
static struct kobject *my_kobj;
static int __init my_init(void)
{
my_kobj = kobject_create_and_add("my_kobject", NULL);
if (!my_kobj) {
pr_err("Failed to create kobject\n");
return -ENOMEM;
}
if (sysfs_create_file(my_kobj, &my_attr)) {
pr_err("Failed to create sysfs file\n");
kobject_put(my_kobj);
return -ENOMEM;
}
return 0;
}
static void __exit my_exit(void)
{
sysfs_remove_file(my_kobj, &my_attr);
kobject_put(my_kobj);
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MODLE Name");
4.3 dev 文件系统
- dev文件系统允许设备驱动程序在/dev目录下创建设备文件,以便用户空间程序可以访问设备。
- 可以使用register_chrdev()或者cdev_add()等函数注册字符设备或块设备,并在/dev目录下创建相应的设备文件。