调试和性能优化
实验:procfs
Linux 内核的 proc 文件系统是一个虚拟的文件系统,它不占用磁盘空间,而是存在于内存中。这套虚拟的文件系统可以让用户和内核的内部数据结构进行交互。
常见的信息有,比如进程信息,在 /proc 目录下,每个正在运行的进程都有一个以其进程 ID(PID)命名的目录。这些目录包含了关于相应进程的各种信息。再比如系统信息,/proc/cpuinfo 显示 CPU 的详细信息;/proc/meminfo 显示内存使用情况的详细信息。
本节实验如代码清单 1 所示,展示如何使用 proc 文件系统提供的接口来查看和修改内核模块的状态或配置。
- #include <linux/module.h>
- #include <linux/proc_fs.h>
- #include <linux/uaccess.h>
- #include <linux/init.h>
- #define NODE "rlk/my_proc"
- static int param = 100;
- static struct proc_dir_entry* my_proc;
- static struct proc_dir_entry* my_root;
- #define KS 32
- static char kstring[KS];
- static ssize_t my_read(struct file* file, char __user* buf, size_t lbuf, loff_t* ppos)
- {
- int nbytes = sprintf(kstring, "%d\n", param);
- return simple_read_from_buffer(buf, lbuf, ppos, kstring, nbytes);
- }
- static ssize_t my_write(struct file* file, const char __user* buf, size_t lbuf, loff_t* ppos)
- {
- ssize_t rc;
- rc = simple_write_to_buffer(kstring, lbuf, ppos, buf, lbuf);
- sscanf(kstring, "%d", ¶m);
- pr_info("param has been set to %d\n", param);
- return rc;
- }
- static const struct proc_ops my_proc_ops =
- {
- .proc_read = my_read,
- .proc_write = my_write,
- };
- static int __init my_init(void)
- {
- my_root = proc_mkdir("rlk", NULL);
- if (IS_ERR(my_root))
- {
- pr_err("fail to make dir\n");
- return -1;
- }
- my_proc = proc_create(NODE, 0, NULL, &my_proc_ops);
- if (IS_ERR(my_proc))
- {
- pr_err("fail to make %s\n", NODE);
- return -1;
- }
- pr_info("created %s\n", NODE);
- return 0;
- }
- static void __exit my_exit(void)
- {
- if (my_proc)
- {
- proc_remove(my_proc);
- proc_remove(my_root);
- pr_info("remove %s\n", NODE);
- }
- }
- module_init(my_init);
- module_exit(my_exit);
- MODULE_LICENSE("GPL");
我们关注新接触的和 proc 相关的函数:
1. proc_mkdir 函数用于在 proc 文件系统中创建一个新目录。其原型为
- struct proc_dir_entry* proc_mkdir(const char* name,
- struct proc_dir_entry* parent);
其中,name 是要创建的目录名称;parent 是父目录的指针,如果创建的是顶级目录,则为 NULL。
2. proc_create 函数用于在 proc 文件系统中创建一个文件,并与之关联一个文件操作结构,允许读写操作。其原型为
- struct proc_dir_entry* proc_create(const char* name, umode_t mode,
- struct proc_dir_entry* parent,
- const struct file_operations* proc_fops);
其中,name 指定创建的文件名;mode 指定文件的权限模式;parent 指定父目录项的指针,如果是顶级目录则为 NULL;proc_fops 为指向 file_operations 结构的指针。
在新版本的 linux 内核中,proc_create 函数的第四个参数类型从 const struct file_operations* 变更为 const struct proc_ops*。这个改变是为了更清晰地区分文件操作和 proc 接口操作。
代码清单 1 中的编译环境使用的 proc_ops 结构体。
代码清单 1 在 proc 文件系统中新增了一个目录 rlk 和一个文件 rlk/my_proc。该文件支持读写操作:读操作返回一个整数参数 param 的当前值(默认为100),写操作允许用户输入一个新的整数值来更新 param 的值。
读写操作和之前的虚拟 FIFO 驱动非常类似,验证的手段也是一样的。
- root@tim:/proc/rlk# cat my_proc
- 100
- root@tim:/proc/rlk# echo "200" > my_proc
- root@tim:/proc/rlk# cat my_proc
- 200
实验:sysfs
sysfs 和 proc 类似,也是 linux 内核中的一个虚拟文件系统,允许用户空间应用程序查询和修改内核对象的属性。
有一个问题是,既然 procfs 和 sysfs 功能类似,为什么还会有 sysfs 呢?其实 sysfs 是作为替代旧的 proc 文件系统中用于设备和驱动程序信息的部分。因为随着 linux 系统的发展,procfs 开始被用于多种不同的目的,包括设备和驱动程序信息的展示。这导致 procfs 结构混乱,不同类型信息解析的一致性很差。
sysfs 被引入作为一种解决方案,以一种更加结构化和一致的方式表示系统中的设备和驱动程序,每个设备和驱动程序都在sysfs中有其对应的目录。
我们直接看到代码清单 2 的样例,了解相关的函数和结构体。
- #include <linux/module.h>
- #include <linux/proc_fs.h>
- #include <linux/uaccess.h>
- #include <linux/init.h>
- #include <linux/device.h>
- #include <linux/platform_device.h>
- #include <linux/sysfs.h>
- #define NODE "rlk/my_proc"
- static int param = 100;
- static struct proc_dir_entry* my_proc;
- static struct proc_dir_entry* my_root;
- static struct platform_device* my_device;
- #define KS 32
- static char kstring[KS];
- static ssize_t my_read(struct file* file, char __user* buf, size_t lbuf, loff_t* ppos)
- {
- int nbytes = sprintf(kstring, "%d\n", param);
- return simple_read_from_buffer(buf, lbuf, ppos, kstring, nbytes);
- }
- static ssize_t my_write(struct file* file, const char __user* buf, size_t lbuf, loff_t* ppos)
- {
- ssize_t rc;
- rc = simple_write_to_buffer(kstring, lbuf, ppos, buf, lbuf);
- sscanf(kstring, "%d", ¶m);
- pr_info("param has been set to %d\n", param);
- return rc;
- }
- static const struct proc_ops my_proc_ops =
- {
- .proc_read = my_read,
- .proc_write = my_write,
- };
- static ssize_t data_show(struct device* d, struct device_attribute* attr, char* buf)
- {
- return sprintf(buf, "%d\n", param);
- }
- static ssize_t data_store(struct device* d, struct device_attribute* attr, const char* buf, size_t count)
- {
- sscanf(buf, "%d", ¶m);
- dev_dbg(d, ": write %d into data\n", param);
- return strnlen(buf, count);
- }
- static DEVICE_ATTR_RW(data);
- static struct attribute* ben_sysfs_entries[] =
- {
- &dev_attr_data.attr,
- NULL
- };
- static struct attribute_group mydevice_attr_group =
- {
- .name = "rlk",
- .attrs = ben_sysfs_entries,
- };
- static int __init my_init(void)
- {
- int ret;
- my_root = proc_mkdir("rlk", NULL);
- my_proc = proc_create(NODE, 0, NULL, &my_proc_ops);
- if (IS_ERR(my_proc))
- {
- pr_err("fail to make %s\n", NODE);
- return PTR_ERR(my_proc);
- }
- pr_info("create %s on procfs\n", NODE);
- my_device = platform_device_register_simple("rlk", -1, NULL, 0);
- if (IS_ERR(my_device))
- {
- printk("platform device register fail\n");
- ret = PTR_ERR(my_device);
- goto proc_fail;
- }
- ret = sysfs_create_group(&my_device->dev.kobj, &mydevice_attr_group);
- if (ret)
- {
- printk("create sysfs group fail\n");
- goto register_fail;
- }
- pr_info("create sysfs node done\n");
- return 0;
- register_fail:
- platform_device_unregister(my_device);
- proc_fail:
- return ret;
- }
- static void __exit my_exit(void)
- {
- if (my_proc)
- {
- proc_remove(my_proc);
- proc_remove(my_root);
- pr_info("remove %s\n", NODE);
- }
- sysfs_remove_group(&my_device->dev.kobj, &mydevice_attr_group);
- platform_device_unregister(my_device);
- }
- module_init(my_init);
- module_exit(my_exit);
- MODULE_LICENSE("GPL");
1. platform_device_register_simple 函数,是 linux 内核中用于注册平台设备的便捷函数。其原型为
- struct platform_device* platform_device_register_simple(
- const char* name, int id,
- const struct resource* res, unsigned int num);
其中,参数 name 指定设备的名称。这个名字对应于驱动程序将要支持的设备名称,内核通过这个名称来匹配设备和其驱动程序。
参数 id 指定设备的 ID。如果系统中可能存在多个相同类型的设备,这个 ID 用于区分它们。如果系统只有一个此类设备,可以使用 -1 来表示。
参数 res 指向 resource 数组的指针,该数组描述了设备所需的资源,如内存区域、I/O 端口和中断。如果设备不需要分配任何资源,可以传递 NULL。
参数 num 指定 res 数组中资源的数量。如果 res 为 NULL,则此值应为 0。
2. device_attribute 结构体,用于表示设备属性的一种方式,专门用于 sysfs 文件系统中。其定义为
- struct device_attribute {
- struct attribute attr;
- ssize_t(*show)(struct device* dev, struct device_attribute* attr,
- char* buf);
- ssize_t(*store)(struct device* dev, struct device_attribute* attr,
- const char* buf, size_t count);
- };
其中,attr 是 attribute 结构体。它提供了属性的基本信息,包括属性的名字 name 和权限 mode。
- struct attribute {
- const char* name;
- umode_t mode;
- };
成员 show 函数指针,当用户空间尝试读取对应的 sysfs 文件时被内核调用;成员 store 函数指针,当用户空间向对应的 sysfs 文件写入数据时被内核调用。
3. DEVICE_ATTR_RW 宏,用于简化 device_attribute 的声明过程,只需指定属性的名称即可。宏定义为
- #define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store)
- #define DEVICE_ATTR_RW(_name) \
- struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
代码中的 data_show 和 data_store 函数,就是按照规则命名,并被引用的。
4. attribute_group 结构体,用于定义和管理一组 sysfs 属性。其定义为
- struct attribute_group {
- const char* name;
- umode_t(*is_visible)(struct kobject*,
- struct attribute*, int);
- umode_t(*is_bin_visible)(struct kobject*,
- struct bin_attribute*, int);
- struct attribute** attrs;
- struct bin_attribute** bin_attrs;
- };
这边的定义感觉很奇怪:
单独 attribute 结构的使用,可以看到后面是当作 device_attribute 用的。这边只传 attribute 指针,可以理解为基类抽象。
但是又引入了一个 bin_attribute 的“具象”结构,看着很不和谐。
bin_attribute 用于大数据传输场景。
5. sysfs_create_group 函数,用于将一组相关的属性添加到指定的 kobject 表示的 sysfs 目录下。
- int sysfs_create_group(struct kobject* kobj,
- const struct attribute_group* grp);
代码中所有和 sysfs 相关的内容均已介绍完毕,我们再梳理一下 sysfs 的创建流程:首先通过 platform_device_register_simple 注册一个名为 rlk 的平台设备;接着通过 DEVICE_ATTR_RW 宏,定义一个名为 data 的属性;然后将其包含在名为 rlk 的属性组下;最后通过 sysfs_create_group 添加属性。
注意高亮的名字,是目录组织的依据。我们定义的属性文件为 /sys/devices/platform/rlk/rlk/data。验证的手段还是一样。
虽然没有追踪目录命名的具体流程,但我们可以看出目录和节点的创建是基于各种指定的名称进行的。
- root@tim:/sys# cd devices/platform/rlk/rlk/
- root@tim:/sys/devices/platform/rlk/rlk# ls
- data
- root@tim:/sys/devices/platform/rlk/rlk# cat data
- 100
- root@tim:/sys/devices/platform/rlk/rlk# echo "200" > data
- root@tim:/sys/devices/platform/rlk/rlk# cat data
- 200
实验:debugfs
debugfs 是 linux 内核中的一个特殊文件系统,顾名思义,它用于为开发者提供一个便捷的内核调试方式。与 procfs 和 sysfs 相比,debugfs 专门设计用于调试目的,而不是作为向用户空间公开系统信息和配置接口的手段。
我们看到代码清单 3,来学习相关接口的使用。
- #include <linux/module.h>
- #include <linux/proc_fs.h>
- #include <linux/uaccess.h>
- #include <linux/init.h>
- #include <linux/debugfs.h>
- #define NODE "rlk"
- static int param = 100;
- struct dentry* debugfs_dir;
- #define KS 32
- static char kstring[KS];
- static ssize_t my_read(struct file* file, char __user* buf, size_t lbuf, loff_t* ppos)
- {
- int nbytes = sprintf(kstring, "%d\n", param);
- return simple_read_from_buffer(buf, lbuf, ppos, kstring, nbytes);
- }
- static ssize_t my_write(struct file* file, const char __user* buf, size_t lbuf, loff_t* ppos)
- {
- ssize_t rc;
- rc = simple_write_to_buffer(kstring, lbuf, ppos, buf, lbuf);
- sscanf(kstring, "%d", ¶m);
- return rc;
- }
- static const struct file_operations mydebugfs_ops = {
- .owner = THIS_MODULE,
- .read = my_read,
- .write = my_write,
- };
- static int __init my_init(void)
- {
- struct dentry* debug_file;
- debugfs_dir = debugfs_create_dir(NODE, NULL);
- if (IS_ERR(debugfs_dir))
- {
- printk("create debugfs dir fail\n");
- return -EFAULT;
- }
- debug_file = debugfs_create_file("my_debug", 0444, debugfs_dir, NULL, &mydebugfs_ops);
- if (IS_ERR(debug_file))
- {
- printk("create debugfs file fail\n");
- debugfs_remove_recursive(debugfs_dir);
- return -EFAULT;
- }
- pr_info("I create %s on debugfs\n", NODE);
- return 0;
- }
- static void __exit my_exit(void)
- {
- if (debugfs_dir)
- {
- debugfs_remove_recursive(debugfs_dir);
- pr_info("Remove %s\n", NODE);
- }
- }
- module_init(my_init);
- module_exit(my_exit);
- MODULE_LICENSE("GPL");
1. debugfs_create_dir 函数,用于在 debugfs 文件系统中创建一个新目录。通过目录结构来分类和管理调试信息,可以使调试过程更加高效和有序。其原型为
- struct dentry* debugfs_create_dir(const char* name, struct dentry* parent);
其中,name 参数指定创建的目录的名称。parent 参数指定父目录的目录项指针。如果这个参数为NULL,则新创建的目录将会在 debugfs 文件系统的根目录下。如果指定了一个有效的父目录,新目录将作为这个父目录的子目录创建。
2. debugfs_create_file 函数,用于在 debugfs 文件系统中创建一个文件。其原型为
- struct dentry* debugfs_create_file(const char* name, umode_t mode,
- struct dentry* parent, void* data,
- const struct file_operations* fops);
其中,name 指定文件的名称;mode 指定文件的权限;parent 指定父目录的目录项指针。
data 参数是传递给文件操作回调函数的数据,会被存储在创建的文件的 file 结构体的 private_data 成员中。fops 参数指定文件操作的结构体指针。
3. debugfs_remove_recursive 函数,用于删除 debugfs 文件系统中的一个目录及其所有子目录和文件。其原型为
- void debugfs_remove_recursive(struct dentry* dentry);
debugfs 的根目录在 /sys/kernel/debug,我们在其中找到我们的文件进行验证。
- root@tim:/sys/kernel/debug# cd rlk/
- root@tim:/sys/kernel/debug/rlk# ls
- my_debug
- root@tim:/sys/kernel/debug/rlk# cat my_debug
- 100
- root@tim:/sys/kernel/debug/rlk# echo "200" > my_debug
- root@tim:/sys/kernel/debug/rlk# cat my_debug
- 200