调试和性能优化

实验:procfs

Linux 内核的 proc 文件系统是一个虚拟的文件系统,它不占用磁盘空间,而是存在于内存中。这套虚拟的文件系统可以让用户和内核的内部数据结构进行交互。

常见的信息有,比如进程信息,在 /proc 目录下,每个正在运行的进程都有一个以其进程 ID(PID)命名的目录。这些目录包含了关于相应进程的各种信息。再比如系统信息,/proc/cpuinfo 显示 CPU 的详细信息;/proc/meminfo 显示内存使用情况的详细信息。

本节实验如代码清单 1 所示,展示如何使用 proc 文件系统提供的接口来查看和修改内核模块的状态或配置。

代码清单 1 procfs
  1. #include <linux/module.h>
  2. #include <linux/proc_fs.h>
  3. #include <linux/uaccess.h>
  4. #include <linux/init.h>
  5.  
  6. #define NODE "rlk/my_proc"
  7.  
  8. static int param = 100;
  9. static struct proc_dir_entry* my_proc;
  10. static struct proc_dir_entry* my_root;
  11.  
  12. #define KS 32
  13. static char kstring[KS];
  14.  
  15. static ssize_t my_read(struct file* file, char __user* buf, size_t lbuf, loff_t* ppos)
  16. {
  17.     int nbytes = sprintf(kstring, "%d\n", param);
  18.     return simple_read_from_buffer(buf, lbuf, ppos, kstring, nbytes);
  19. }
  20.  
  21. static ssize_t my_write(struct file* file, const char __user* buf, size_t lbuf, loff_t* ppos)
  22. {
  23.     ssize_t rc;
  24.     rc = simple_write_to_buffer(kstring, lbuf, ppos, buf, lbuf);
  25.     sscanf(kstring, "%d", &param);
  26.     pr_info("param has been set to %d\n", param);
  27.     return rc;
  28. }
  29.  
  30. static const struct proc_ops my_proc_ops =
  31. {
  32.     .proc_read = my_read,
  33.     .proc_write = my_write,
  34. };
  35.  
  36. static int __init my_init(void)
  37. {
  38.     my_root = proc_mkdir("rlk", NULL);
  39.     if (IS_ERR(my_root))
  40.     {
  41.         pr_err("fail to make dir\n");
  42.         return -1;
  43.     }
  44.  
  45.     my_proc = proc_create(NODE, 0, NULL, &my_proc_ops);
  46.     if (IS_ERR(my_proc))
  47.     {
  48.         pr_err("fail to make %s\n", NODE);
  49.         return -1;
  50.     }
  51.     pr_info("created %s\n", NODE);
  52.     return 0;
  53. }
  54.  
  55. static void __exit my_exit(void)
  56. {
  57.     if (my_proc)
  58.     {
  59.         proc_remove(my_proc);
  60.         proc_remove(my_root);
  61.         pr_info("remove %s\n", NODE);
  62.     }
  63. }
  64.  
  65. module_init(my_init);
  66. module_exit(my_exit);
  67. 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 的样例,了解相关的函数和结构体。

代码清单 2 sysfs
  1. #include <linux/module.h>
  2. #include <linux/proc_fs.h>
  3. #include <linux/uaccess.h>
  4. #include <linux/init.h>
  5. #include <linux/device.h>
  6. #include <linux/platform_device.h>
  7. #include <linux/sysfs.h>
  8.  
  9. #define NODE "rlk/my_proc"
  10.  
  11. static int param = 100;
  12. static struct proc_dir_entry* my_proc;
  13. static struct proc_dir_entry* my_root;
  14. static struct platform_device* my_device;
  15.  
  16. #define KS 32
  17. static char kstring[KS];
  18.  
  19. static ssize_t my_read(struct file* file, char __user* buf, size_t lbuf, loff_t* ppos)
  20. {
  21.     int nbytes = sprintf(kstring, "%d\n", param);
  22.     return simple_read_from_buffer(buf, lbuf, ppos, kstring, nbytes);
  23. }
  24.  
  25. static ssize_t my_write(struct file* file, const char __user* buf, size_t lbuf, loff_t* ppos)
  26. {
  27.     ssize_t rc;
  28.     rc = simple_write_to_buffer(kstring, lbuf, ppos, buf, lbuf);
  29.     sscanf(kstring, "%d", &param);
  30.     pr_info("param has been set to %d\n", param);
  31.     return rc;
  32. }
  33.  
  34. static const struct proc_ops my_proc_ops =
  35. {
  36.     .proc_read = my_read,
  37.     .proc_write = my_write,
  38. };
  39.  
  40. static ssize_t data_show(struct device* d, struct device_attribute* attr, char* buf)
  41. {
  42.     return sprintf(buf, "%d\n", param);
  43. }
  44.  
  45. static ssize_t data_store(struct device* d, struct device_attribute* attr, const char* buf, size_t count)
  46. {
  47.     sscanf(buf, "%d", &param);
  48.     dev_dbg(d, ": write %d into data\n", param);
  49.     return strnlen(buf, count);
  50. }
  51.  
  52. static DEVICE_ATTR_RW(data);
  53.  
  54. static struct attribute* ben_sysfs_entries[] =
  55. {
  56.     &dev_attr_data.attr,
  57.     NULL
  58. };
  59.  
  60. static struct attribute_group mydevice_attr_group =
  61. {
  62.     .name = "rlk",
  63.     .attrs = ben_sysfs_entries,
  64. };
  65.  
  66. static int __init my_init(void)
  67. {
  68.     int ret;
  69.  
  70.     my_root = proc_mkdir("rlk", NULL);
  71.     my_proc = proc_create(NODE, 0, NULL, &my_proc_ops);
  72.     if (IS_ERR(my_proc))
  73.     {
  74.         pr_err("fail to make %s\n", NODE);
  75.         return PTR_ERR(my_proc);
  76.     }
  77.     pr_info("create %s on procfs\n", NODE);
  78.  
  79.     my_device = platform_device_register_simple("rlk", -1, NULL, 0);
  80.     if (IS_ERR(my_device))
  81.     {
  82.         printk("platform device register fail\n");
  83.         ret = PTR_ERR(my_device);
  84.         goto proc_fail;
  85.     }
  86.  
  87.     ret = sysfs_create_group(&my_device->dev.kobj, &mydevice_attr_group);
  88.     if (ret)
  89.     {
  90.         printk("create sysfs group fail\n");
  91.         goto register_fail;
  92.     }
  93.     pr_info("create sysfs node done\n");
  94.     return 0;
  95.  
  96. register_fail:
  97.     platform_device_unregister(my_device);
  98. proc_fail:
  99.     return ret;
  100. }
  101.  
  102. static void __exit my_exit(void)
  103. {
  104.     if (my_proc)
  105.     {
  106.         proc_remove(my_proc);
  107.         proc_remove(my_root);
  108.         pr_info("remove %s\n", NODE);
  109.     }
  110.  
  111.     sysfs_remove_group(&my_device->dev.kobj, &mydevice_attr_group);
  112.     platform_device_unregister(my_device);
  113. }
  114.  
  115. module_init(my_init);
  116. module_exit(my_exit);
  117. 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,来学习相关接口的使用。

代码清单 3 debugfs
  1. #include <linux/module.h>
  2. #include <linux/proc_fs.h>
  3. #include <linux/uaccess.h>
  4. #include <linux/init.h>
  5. #include <linux/debugfs.h>
  6.  
  7. #define NODE "rlk"
  8.  
  9. static int param = 100;
  10. struct dentry* debugfs_dir;
  11.  
  12. #define KS 32
  13. static char kstring[KS];
  14.  
  15. static ssize_t my_read(struct file* file, char __user* buf, size_t lbuf, loff_t* ppos)
  16. {
  17.     int nbytes = sprintf(kstring, "%d\n", param);
  18.     return simple_read_from_buffer(buf, lbuf, ppos, kstring, nbytes);
  19. }
  20.  
  21. static ssize_t my_write(struct file* file, const char __user* buf, size_t lbuf, loff_t* ppos)
  22. {
  23.     ssize_t rc;
  24.     rc = simple_write_to_buffer(kstring, lbuf, ppos, buf, lbuf);
  25.     sscanf(kstring, "%d", &param);
  26.     return rc;
  27. }
  28.  
  29. static const struct file_operations mydebugfs_ops = {
  30.     .owner = THIS_MODULE,
  31.     .read = my_read,
  32.     .write = my_write,
  33. };
  34.  
  35. static int __init my_init(void)
  36. {
  37.     struct dentry* debug_file;
  38.  
  39.     debugfs_dir = debugfs_create_dir(NODE, NULL);
  40.     if (IS_ERR(debugfs_dir))
  41.     {
  42.         printk("create debugfs dir fail\n");
  43.         return -EFAULT;
  44.     }
  45.  
  46.     debug_file = debugfs_create_file("my_debug", 0444, debugfs_dir, NULL, &mydebugfs_ops);
  47.     if (IS_ERR(debug_file))
  48.     {
  49.         printk("create debugfs file fail\n");
  50.         debugfs_remove_recursive(debugfs_dir);
  51.         return -EFAULT;
  52.     }
  53.  
  54.     pr_info("I create %s on debugfs\n", NODE);
  55.     return 0;
  56. }
  57.  
  58. static void __exit my_exit(void)
  59. {
  60.     if (debugfs_dir)
  61.     {
  62.         debugfs_remove_recursive(debugfs_dir);
  63.         pr_info("Remove %s\n", NODE);
  64.     }
  65. }
  66.  
  67. module_init(my_init);
  68. module_exit(my_exit);
  69. 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