[TOC]

kernel/ksysfs.c Sysfs 内核对象接口(Sysfs Kernel Object Interface) 将内核对象(kobject)层次结构展现为文件系统的核心实现

历史与背景

这项技术是为了解决什么特定问题而诞生的?

kernel/ksysfs.c 是实现 sysfs 文件系统的核心。sysfs 的诞生是为了解决在它之前的 /proc 文件系统所面临的结构混乱信息冗余问题,并为Linux 2.6内核中引入的全新**统一设备模型(Unified Device Model)**提供一个干净、结构化的视图。

ksysfs.c 旨在解决以下核心问题:

  • 反映内核内部结构:内核的设备模型是一个层次化的树状结构(设备连接在总线上,驱动绑定到设备上)。需要一种机制能将这种内在的、面向对象的层次关系精确地反映到用户空间的文件系统中。
  • 提供稳定的ABI:用户空间工具(最著名的是udev)需要一个稳定、可预测的接口来发现设备、查询其属性并响应设备的热插拔事件。/proc 中杂乱无章的文件和格式使得这项工作非常脆弱。
  • 机制与策略分离ksysfs.c 提供“机制”,即创建目录和属性文件来代表设备。而“策略”(例如,根据这些信息创建 /dev 节点、加载固件)则完全交给用户空间的udev等工具来完成。
  • 设备属性的可调谐性:提供一个简单的、基于文件的接口(读文件获取属性,写文件修改属性),用于查看和调整设备参数。

它的发展经历了哪些重要的里程碑或版本迭代?

sysfsksysfs.c 的发展是与Linux设备模型紧密相连的。

  • 诞生(2.5/2.6内核)sysfs作为新设备模型的一部分被引入,最初名为driverfsksysfs.c从一开始就奠定了其核心设计:将kobject(内核对象)映射为目录,将attribute(属性)映射为文件。
  • kobject模型的成熟sysfs的成功完全建立在kobject之上。kobjectksetktype这套机制的发展,使得内核中任何对象都能方便地在sysfs中获得一个表示。
  • 符号链接(Symbolic Links)的广泛使用:这是一个关键的里程碑。sysfs不仅仅是表示父子关系。它通过符号链接来表示更复杂的关系,例如,一个位于/sys/devices下的设备目录,会通过符号链接指向它所属的总线、驱动程序、子系统等,从而将整个设备模型的关系网络清晰地展现出来。
  • 与uevent的集成ksysfs.c在创建或删除目录/文件时,会生成uevent(内核事件),通过netlink socket广播给用户空间。这是现代Linux热插拔和设备管理(udev)的基石。

目前该技术的社区活跃度和主流应用情况如何?

ksysfs.c是内核最基础、最稳定的部分之一。其核心架构已经非常成熟。社区的活跃度主要体现在:

  • 修复在并发或极端情况下可能出现的细微bug。
  • 进行性能优化,如减少锁竞争、优化路径查找。
  • 配合新的内核子系统,确保它们能够正确、安全地通过sysfs导出其对象。

sysfs是所有现代Linux系统的基础,其应用无处不在:

  • udev/systemd-udevdsysfs的主要消费者,负责设备的动态管理。
  • 电源管理工具:通过读写/sys/class/power_supply//sys/devices/system/cpu/下的文件来监控和控制电源状态。
  • 容器技术cgroups(控制组)通常被挂载为一个类似sysfs的文件系统,用于资源隔离和管理。
  • 系统监控和调优工具

核心原理与设计

它的核心工作原理是什么?

ksysfs.c的核心原理是将内核中的**kobject层次结构翻译成sysfs中的目录和文件结构**。

  1. kobject是基石kobject是内核中用于表示对象的一个嵌入式结构体。它本身不做什么,但提供了引用计数、父子关系指针和一个名字。任何希望在sysfs中出现的内核对象(如struct device, struct bus_type)都必须内嵌一个kobject
  2. kobject -> 目录:当一个kobject通过kobject_add()被添加到内核对象体系中时,kobject_add()会调用ksysfs.c中的sysfs_create_dir_ns()函数。此函数会在sysfs这个内存文件系统中创建一个以kobject的名字命名的目录
  3. attribute -> 文件:每个kobject都关联着一个类型(ktype),这个ktype定义了一组默认的属性(attributes)。每个属性由struct attribute描述,包含一个名字和文件权限。更重要的是,它关联着两个函数指针:
    • show(kobject, attribute, buffer):当用户空间读取此属性文件时,ksysfs.c提供的通用文件操作会最终调用这个函数。驱动开发者在此函数中将内核数据格式化成字符串放入buffer
    • store(kobject, attribute, buffer, size):当用户空间写入此属性文件时,会调用这个函数。驱动开发者在此函数中解析buffer中的字符串,并用它来修改内核中的参数。
  4. 文件创建流程:在sysfs_create_dir_ns()之后,内核会遍历与该kobject关联的所有属性,并为每个属性调用sysfs_create_file()。这个函数在刚刚创建的目录下创建一个以属性名命名的文件,并将其文件操作(file_operations)指向ksysfs.c中预定义的通用read/write实现。
  5. 用户空间交互:当用户cat /sys/.../attribute_file时:
    • VFS(虚拟文件系统)层调用sysfs的文件read操作。
    • sysfsread操作从文件路径中找到对应的kobjectattribute
    • 调用该attributeshow()方法,将结果返回给用户。

它的主要优势体现在哪些方面?

  • 结构清晰:严格的“一个对象一个目录”和“一个属性一个文件”的规则,使得sysfs的结构与内核对象模型完全对应。
  • 高度解耦:设备驱动开发者只需要定义好自己的kobjectattributeshow/store函数),而完全无需关心VFS、文件系统实现等复杂细节。
  • 动态性:与kobject的生命周期管理紧密绑定,kobject被创建,目录就出现;kobject被销毁,目录就消失。完美支持热插拔。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • “一个值一个文件”的开销:这个设计哲学虽然简单,但在需要导出大量数据时效率低下。获取100个值需要进行100次open/read/close系统调用。
  • 不适合流式数据和事件sysfs是为表示**状态(state)属性(attributes)**而设计的。它不适合传输流式数据(如音频流)或发送连续的事件通知(应使用netlink或字符设备)。
  • ABI稳定性负担:一旦一个属性被加入sysfs,它就成为了内核对用户空间的ABI的一部分。移除或修改它会破坏用户空间程序,因此内核开发者对此非常谨慎,导致sysfs中可能存在一些过时的接口。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

ksysfs.c是内核内部的实现,驱动开发者直接与之交互的是kobjectsysfs_* API。这些API是以下场景的首选:

  • 设备驱动模型集成:任何标准的设备驱动(块设备、网络设备、USB设备等)都必须通过设备模型注册,其struct device中内嵌的kobject会自动被ksysfs.c处理,在/sys/devices下创建目录。
  • 导出可调参数:当驱动需要向用户空间暴露一个可配置的参数时(例如,块设备的I/O调度器、网络接口的MTU),sysfs属性是标准的实现方式。
  • 展示设备状态:向用户空间展示不常变动的设备状态或统计信息(例如,电池电量、风扇转速、网卡收发包统计)。

是否有不推荐使用该技术的场景?为什么?

  • 大量、复杂的数据交换:如果需要一次性向用户空间传递一个大的、复杂的C结构体,或者进行RPC式的复杂交互,应该创建一个字符设备并实现ioctl
  • 开发者调试信息:对于不稳定的、仅用于内核开发者调试的内部状态信息,应该使用debugfsdebugfs没有任何ABI稳定性保证,可以随意修改。
  • 进程相关信息:与进程强相关的信息,其传统和正确的归属地是procfs(例如/proc/<pid>/)。

对比分析

请将其 与 其他相似技术 进行详细对比。

特性 Sysfs (ksysfs.c) Procfs (/proc) Debugfs ioctl on Char Device
主要用途 结构化的内核对象模型视图,特别是设备模型。提供稳定的ABI。 进程信息系统状态/统计的混合体。 内核开发者调试无ABI保证 与特定设备进行双向I/O通信和控制
接口稳定性 稳定ABI。一旦添加,极难移除或更改。 混合。/proc/<pid>部分稳定,其他部分是历史遗留,新接口不推荐在此添加。 完全不稳定。可以随时更改,用户空间程序不应依赖它。 稳定ABIioctl命令集一旦定义,就不应改变。
数据模型 一个值一个文件。数据是简单的文本字符串。 格式化的文本文件。一个文件可能包含多行多列的结构化文本。 任意。可以是二进制blob,也可以是文本。 二进制命令/数据结构。通过命令码区分操作。
典型场景 查看/修改设备参数(/sys/class/net/eth0/mtu),udev集成。 ps, top, free, 查看/proc/meminfo, /proc/cpuinfo 查看驱动内部数据结构、手动触发调试功能。 配置硬件、发送/接收数据块、执行复杂操作(如刷新固件)。

历史与背景

这项技术是为了解决什么特定问题而诞生的?

kernel/ksysfs.c 是实现 sysfs 文件系统的核心。sysfs 的诞生是为了解决在它之前的 /proc 文件系统所面临的结构混乱信息冗余问题,并为Linux 2.6内核中引入的全新**统一设备模型(Unified Device Model)**提供一个干净、结构化的视图。

ksysfs.c 旨在解决以下核心问题:

  • 反映内核内部结构:内核的设备模型是一个层次化的树状结构(设备连接在总线上,驱动绑定到设备上)。需要一种机制能将这种内在的、面向对象的层次关系精确地反映到用户空间的文件系统中。
  • 提供稳定的ABI:用户空间工具(最著名的是udev)需要一个稳定、可预测的接口来发现设备、查询其属性并响应设备的热插拔事件。/proc 中杂乱无章的文件和格式使得这项工作非常脆弱。
  • 机制与策略分离ksysfs.c 提供“机制”,即创建目录和属性文件来代表设备。而“策略”(例如,根据这些信息创建 /dev 节点、加载固件)则完全交给用户空间的udev等工具来完成。
  • 设备属性的可调谐性:提供一个简单的、基于文件的接口(读文件获取属性,写文件修改属性),用于查看和调整设备参数。

它的发展经历了哪些重要的里程碑或版本迭代?

sysfsksysfs.c 的发展是与Linux设备模型紧密相连的。

  • 诞生(2.5/2.6内核)sysfs作为新设备模型的一部分被引入,最初名为driverfsksysfs.c从一开始就奠定了其核心设计:将kobject(内核对象)映射为目录,将attribute(属性)映射为文件。
  • kobject模型的成熟sysfs的成功完全建立在kobject之上。kobjectksetktype这套机制的发展,使得内核中任何对象都能方便地在sysfs中获得一个表示。
  • 符号链接(Symbolic Links)的广泛使用:这是一个关键的里程碑。sysfs不仅仅是表示父子关系。它通过符号链接来表示更复杂的关系,例如,一个位于/sys/devices下的设备目录,会通过符号链接指向它所属的总线、驱动程序、子系统等,从而将整个设备模型的关系网络清晰地展现出来。
  • 与uevent的集成ksysfs.c在创建或删除目录/文件时,会生成uevent(内核事件),通过netlink socket广播给用户空间。这是现代Linux热插拔和设备管理(udev)的基石。

目前该技术的社区活跃度和主流应用情况如何?

ksysfs.c是内核最基础、最稳定的部分之一。其核心架构已经非常成熟。社区的活跃度主要体现在:

  • 修复在并发或极端情况下可能出现的细微bug。
  • 进行性能优化,如减少锁竞争、优化路径查找。
  • 配合新的内核子系统,确保它们能够正确、安全地通过sysfs导出其对象。

sysfs是所有现代Linux系统的基础,其应用无处不在:

  • udev/systemd-udevdsysfs的主要消费者,负责设备的动态管理。
  • 电源管理工具:通过读写/sys/class/power_supply//sys/devices/system/cpu/下的文件来监控和控制电源状态。
  • 容器技术cgroups(控制组)通常被挂载为一个类似sysfs的文件系统,用于资源隔离和管理。
  • 系统监控和调优工具

核心原理与设计

它的核心工作原理是什么?

ksysfs.c的核心原理是将内核中的**kobject层次结构翻译成sysfs中的目录和文件结构**。

  1. kobject是基石kobject是内核中用于表示对象的一个嵌入式结构体。它本身不做什么,但提供了引用计数、父子关系指针和一个名字。任何希望在sysfs中出现的内核对象(如struct device, struct bus_type)都必须内嵌一个kobject
  2. kobject -> 目录:当一个kobject通过kobject_add()被添加到内核对象体系中时,kobject_add()会调用ksysfs.c中的sysfs_create_dir_ns()函数。此函数会在sysfs这个内存文件系统中创建一个以kobject的名字命名的目录
  3. attribute -> 文件:每个kobject都关联着一个类型(ktype),这个ktype定义了一组默认的属性(attributes)。每个属性由struct attribute描述,包含一个名字和文件权限。更重要的是,它关联着两个函数指针:
    • show(kobject, attribute, buffer):当用户空间读取此属性文件时,ksysfs.c提供的通用文件操作会最终调用这个函数。驱动开发者在此函数中将内核数据格式化成字符串放入buffer
    • store(kobject, attribute, buffer, size):当用户空间写入此属性文件时,会调用这个函数。驱动开发者在此函数中解析buffer中的字符串,并用它来修改内核中的参数。
  4. 文件创建流程:在sysfs_create_dir_ns()之后,内核会遍历与该kobject关联的所有属性,并为每个属性调用sysfs_create_file()。这个函数在刚刚创建的目录下创建一个以属性名命名的文件,并将其文件操作(file_operations)指向ksysfs.c中预定义的通用read/write实现。
  5. 用户空间交互:当用户cat /sys/.../attribute_file时:
    • VFS(虚拟文件系统)层调用sysfs的文件read操作。
    • sysfsread操作从文件路径中找到对应的kobjectattribute
    • 调用该attributeshow()方法,将结果返回给用户。

它的主要优势体现在哪些方面?

  • 结构清晰:严格的“一个对象一个目录”和“一个属性一个文件”的规则,使得sysfs的结构与内核对象模型完全对应。
  • 高度解耦:设备驱动开发者只需要定义好自己的kobjectattributeshow/store函数),而完全无需关心VFS、文件系统实现等复杂细节。
  • 动态性:与kobject的生命周期管理紧密绑定,kobject被创建,目录就出现;kobject被销毁,目录就消失。完美支持热插拔。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • “一个值一个文件”的开销:这个设计哲学虽然简单,但在需要导出大量数据时效率低下。获取100个值需要进行100次open/read/close系统调用。
  • 不适合流式数据和事件sysfs是为表示**状态(state)属性(attributes)**而设计的。它不适合传输流式数据(如音频流)或发送连续的事件通知(应使用netlink或字符设备)。
  • ABI稳定性负担:一旦一个属性被加入sysfs,它就成为了内核对用户空间的ABI的一部分。移除或修改它会破坏用户空间程序,因此内核开发者对此非常谨慎,导致sysfs中可能存在一些过时的接口。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

ksysfs.c是内核内部的实现,驱动开发者直接与之交互的是kobjectsysfs_* API。这些API是以下场景的首选:

  • 设备驱动模型集成:任何标准的设备驱动(块设备、网络设备、USB设备等)都必须通过设备模型注册,其struct device中内嵌的kobject会自动被ksysfs.c处理,在/sys/devices下创建目录。
  • 导出可调参数:当驱动需要向用户空间暴露一个可配置的参数时(例如,块设备的I/O调度器、网络接口的MTU),sysfs属性是标准的实现方式。
  • 展示设备状态:向用户空间展示不常变动的设备状态或统计信息(例如,电池电量、风扇转速、网卡收发包统计)。

是否有不推荐使用该技术的场景?为什么?

  • 大量、复杂的数据交换:如果需要一次性向用户空间传递一个大的、复杂的C结构体,或者进行RPC式的复杂交互,应该创建一个字符设备并实现ioctl
  • 开发者调试信息:对于不稳定的、仅用于内核开发者调试的内部状态信息,应该使用debugfsdebugfs没有任何ABI稳定性保证,可以随意修改。
  • 进程相关信息:与进程强相关的信息,其传统和正确的归属地是procfs(例如/proc/<pid>/)。

对比分析

请将其 与 其他相似技术 进行详细对比。

特性 Sysfs (ksysfs.c) Procfs (/proc) Debugfs ioctl on Char Device
主要用途 结构化的内核对象模型视图,特别是设备模型。提供稳定的ABI。 进程信息系统状态/统计的混合体。 内核开发者调试无ABI保证 与特定设备进行双向I/O通信和控制
接口稳定性 稳定ABI。一旦添加,极难移除或更改。 混合。/proc/<pid>部分稳定,其他部分是历史遗留,新接口不推荐在此添加。 完全不稳定。可以随时更改,用户空间程序不应依赖它。 稳定ABIioctl命令集一旦定义,就不应改变。
数据模型 一个值一个文件。数据是简单的文本字符串。 格式化的文本文件。一个文件可能包含多行多列的结构化文本。 任意。可以是二进制blob,也可以是文本。 二进制命令/数据结构。通过命令码区分操作。
典型场景 查看/修改设备参数(/sys/class/net/eth0/mtu),udev集成。 ps, top, free, 查看/proc/meminfo, /proc/cpuinfo 查看驱动内部数据结构、手动触发调试功能。 配置硬件、发送/接收数据块、执行复杂操作(如刷新固件)。

rcu_expedited: 控制RCU宽限期加急处理的sysfs接口

此代码片段的作用是在 sysfs 中创建一个名为 rcu_expedited 的可读写文件 (/sys/kernel/rcu_expedited), 用于查询和控制RCU (Read-Copy-Update) 的宽限期 (Grace Period) 是否采用 “加急” (expedited) 模式。用户或脚本可以通过读写此文件来动态调整RCU的行为。

RCU是一种内核同步机制, 它允许在不加锁的情况下进行数据读取, 而更新操作则通过创建数据副本、修改副本、然后等待所有已有的读取操作完成后再用新副本替换旧副本来实现。这个等待所有已有读取方完成的阶段被称为”宽限期”。

  • 普通宽限期: 以较低的系统开销为代价, 等待宽限期自然结束。
  • 加急宽限期: 通过更积极的手段 (例如在其它CPU上强制调度) 来尽快结束宽限期, 但会带来更高的系统抖动和性能开销。

在单核抢占式内核中, 宽限期意味着需要等待所有可能在RCU读端临界区内被抢占的任务都经过一个”静止状态” (quiescent state, 例如上下文切换)。启用加急模式可能会更频繁地触发调度器, 以便更快地让这些被抢占的任务执行并退出其RCU临界区。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*
* 定义一个整型全局变量 rcu_expedited.
* 这个变量是一个标志位, 用于控制RCU宽限期的行为.
* 当它的值为 0 时, 内核使用标准的、低开销的RCU宽限期.
* 当它的值非 0 时, 内核会触发 "加急" 的RCU宽限期, 这会以更高的系统开销为代价来缩短宽限期的等待时间.
*/
int rcu_expedited;
/*
* rcu_expedited_show: 'rcu_expedited' sysfs 文件的读操作处理函数.
* 当用户空间程序执行 'cat /sys/kernel/rcu_expedited' 命令时, 内核会调用此函数.
*
* @kobj: 指向被读取属性所属的kobject的指针, 在这里是 kernel_kobj (/sys/kernel).
* @attr: 指向被读取属性的描述符结构体的指针, 在这里是 kobj_attr_rcu_expedited.
* @buf: 一个指向用户空间提供的缓冲区的指针, 函数的输出需要写入到这个缓冲区中.
* @return: 返回成功写入缓冲区的字节数, 或者一个负值的错误码.
*/
static ssize_t rcu_expedited_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
/*
* 调用 sysfs_emit 函数来安全地格式化字符串并将其写入用户缓冲区 buf.
* 这是一个替代 snprintf 的推荐方法, 能更好地处理 sysfs 缓冲区的边界.
* @ buf: 目标缓冲区.
* @ "%d\n": 格式化字符串, 表示将一个整数以十进制形式输出, 并附加一个换行符.
* @ READ_ONCE(rcu_expedited): 这是读取 rcu_expedited 全局变量值的操作.
* READ_ONCE 是一个特殊的宏, 它确保对变量的读取是一个原子操作.
* 它会阻止编译器进行可能导致多次读取或读取到陈旧值的优化,
* 保证了即使在有并发访问(例如在抢占式单核系统上被其他任务修改)的情况下, 也能获取到该变量在某个时间点的确切值.
*/
return sysfs_emit(buf, "%d\n", READ_ONCE(rcu_expedited));
}
/*
* rcu_expedited_store: 'rcu_expedited' sysfs 文件的写操作处理函数.
* 当用户空间执行 'echo "1" > /sys/kernel/rcu_expedited' 命令时, 内核会调用此函数.
*
* @kobj: 同上, 指向 /sys/kernel 对应的 kobject.
* @attr: 同上, 指向属性的描述符.
* @buf: 一个指向用户写入的数据的缓冲区的指针.
* @count: 写入数据的字节数.
* @return: 返回已成功处理的字节数, 或者一个负值的错误码.
*/
static ssize_t rcu_expedited_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
/*
* 调用 kstrtoint 函数, 尝试将用户传入的字符串 buf 转换为一个整型数,
* 并将结果直接存入 rcu_expedited 全局变量.
* @ buf: 输入的字符串.
* @ 0: 表示基数, 0 意味着函数会自动检测是十进制、八进制还是十六进制.
* @ &rcu_expedited: 指向目标整型变量的指针.
* 如果转换失败 (例如, 用户输入了 "hello" 这样的非数字字符串), kstrtoint 会返回一个非零的错误码.
*/
if (kstrtoint(buf, 0, &rcu_expedited))
/*
* 如果转换失败, 就向用户空间返回 -EINVAL (无效参数) 错误.
*/
return -EINVAL;

/*
* 如果转换成功, 就返回已处理的字节数 count.
* 这是向 sysfs 子系统表明写操作成功完成的标准方式.
*/
return count;
}
/*
* KERNEL_ATTR_RW 是一个辅助宏, 用于快速定义一个可读写的(RW)内核属性.
* 它会展开成一个名为 'kobj_attr_rcu_expedited' 的 static struct kobj_attribute 实例,
* 并自动将其 .show 回调设置为 rcu_expedited_show, .store 回调设置为 rcu_expedited_store,
* 同时设置文件的权限为 0644 (拥有者可读写, 组用户和其他用户只读).
*/
KERNEL_ATTR_RW(rcu_expedited);

ksysfs_init: 创建 /sys/kernel/ 目录及其文件

此函数在内核初始化期间被调用, 其核心职责是在sysfs虚拟文件系统中创建顶层的 /sys/kernel 目录。这个目录作为一个命名空间, 用于向用户空间导出各种内核全局参数、状态信息和控制开关。通过读写该目录下的文件, 用户空间程序或管理员可以查询和调整内核的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/*
* 此部分代码用于创建一个名为 'notes' 的二进制属性文件,
* 其内容直接对应内核镜像中 .notes ELF 段的原始数据.
* ELF notes 段通常包含一些元数据, 例如由编译器或构建系统添加的版本信息.
*/

/*
* extern 声明了两个由链接器脚本定义的符号: __start_notes 和 __stop_notes.
* 它们分别标记了内核 .notes 段在内存中的起始和结束地址.
* 'const void' 表明这些地址指向的是只读的、类型未知的数据.
*/
extern const void __start_notes;
extern const void __stop_notes;
/*
* 定义一个宏 notes_size, 用于计算 .notes 段的大小.
* 这是通过对结束地址和起始地址进行指针运算来实现的.
* 结果是这两个地址之间的字节差.
*/
#define notes_size (&__stop_notes - &__start_notes)
/*
* BIN_ATTR_SIMPLE_RO 是一个辅助宏, 用于快速定义一个只读的二进制 sysfs 属性.
* 它会创建一个名为 bin_attr_notes 的静态结构体, 用于描述 'notes' 这个二进制文件.
* __ro_after_init 是一个编译器属性, 它告诉编译器这个结构体的数据在内核初始化之后是只读的,
* 这有助于进行优化和安全检查.
*/
static __ro_after_init BIN_ATTR_SIMPLE_RO(notes);

/*
* 定义一个全局的 struct kobject 指针, kernel_kobj.
* 这个 kobject 将代表 sysfs 中的 /sys/kernel 目录.
* 它是许多内核全局属性的父对象.
*/
struct kobject *kernel_kobj;
/*
* EXPORT_SYMBOL_GPL 将 kernel_kobj 这个符号导出, 使得其他GPL许可证的内核模块可以使用它.
* 这允许其他模块在 /sys/kernel 目录下创建它们自己的 sysfs 文件或目录.
*/
EXPORT_SYMBOL_GPL(kernel_kobj);

/*
* 定义一个静态的 struct attribute 指针数组.
* 这个数组列出了所有将被创建在 /sys/kernel 目录下的文件的属性.
*/
static struct attribute * kernel_attrs[] = {
/* fscaps_attr 是与文件能力(File Capabilities)相关的属性. */
&fscaps_attr.attr,
/* uevent_seqnum_attr 是uevent事件的序列号. */
&uevent_seqnum_attr.attr,
/* cpu_byteorder_attr 显示CPU的字节序(如: little-endian). 对于STM32H750, 这将是小端序. */
&cpu_byteorder_attr.attr,
/* address_bits_attr 显示CPU的地址总线宽度. 对于ARMv7M架构, 这将是32位. */
&address_bits_attr.attr,
/*
* #ifdef 是一个预处理指令, 下面的属性只有在定义了相应的宏时才会被编译和创建.
* 这使得内核可以根据配置进行裁剪, 移除不需要的功能.
*/
#ifdef CONFIG_UEVENT_HELPER
/* 仅当配置了uevent助手(CONFIG_UEVENT_HELPER)时, 才创建此文件, 用于显示和设置uevent辅助程序路径. */
&uevent_helper_attr.attr,
#endif
#ifdef CONFIG_PROFILING
/* 仅当内核编译时启用了性能剖析(CONFIG_PROFILING)选项时, 此属性才会被包含, 用于控制内核剖析. */
&profiling_attr.attr,
#endif
#ifdef CONFIG_KEXEC_CORE
/* 仅当配置了kexec(CONFIG_KEXEC_CORE)时, 才创建此文件, 用于表示是否已加载一个新的内核准备执行. */
&kexec_loaded_attr.attr,
#ifdef CONFIG_CRASH_DUMP
/* kexec_crash_loaded_attr 表示是否已加载用于崩溃转储的内核. */
&kexec_crash_loaded_attr.attr,
/* kexec_crash_size_attr 表示为崩溃转储内核预留的内存大小. */
&kexec_crash_size_attr.attr,
#endif
#endif
#ifdef CONFIG_VMCORE_INFO
/* 仅当配置了VMCORE_INFO时创建, 用于保存崩溃转储(crash dump)所需的vmcore信息的位置和大小. */
&vmcoreinfo_attr.attr,
#ifdef CONFIG_CRASH_HOTPLUG
/* crash_elfcorehdr_size_attr 表示崩溃时ELF核心头的大小. */
&crash_elfcorehdr_size_attr.attr,
#endif
#endif
#ifndef CONFIG_TINY_RCU
/*
* 仅当没有使用TINY_RCU时, 才创建这两个文件.
* 在像STM32这样的单核系统上, 通常会启用 CONFIG_TINY_RCU 这一简化实现,
* 因此在这种情况下, 这两个用于控制标准RCU行为的属性将不会被创建.
*/
&rcu_expedited_attr.attr,
&rcu_normal_attr.attr,
#endif
/* 数组必须以 NULL 结尾, 作为结束的标记. */
NULL
};

/*
* 定义一个静态的、常量属性组结构体 kernel_attr_group.
* 它将上面定义的 kernel_attrs 数组包装起来.
*/
static const struct attribute_group kernel_attr_group = {
/* .attrs 成员指向属性指针数组. */
.attrs = kernel_attrs,
};

/*
* ksysfs_init: /sys/kernel 的初始化函数.
* 标记为 __init, 表示它仅在内核初始化期间执行, 其占用的内存之后可以被回收.
*/
static int __init ksysfs_init(void)
{
/* 定义一个整型变量 error, 用于存储错误码. */
int error;

/*
* 调用 kobject_create_and_add 函数来创建并注册一个新的 kobject.
* @ "kernel": 新创建的 kobject 的名字, 这将成为 sysfs 中的目录名.
* @ NULL: 父 kobject 指针. 因为这是在 /sys/ 下的顶级目录之一, 所以没有父 kobject.
* 创建的 kobject 地址被赋值给全局指针 kernel_kobj.
*/
kernel_kobj = kobject_create_and_add("kernel", NULL);
/* 检查 kobject 是否创建成功. 如果失败(通常因为内存不足), kernel_kobj 会是 NULL. */
if (!kernel_kobj) {
/* 将错误码设置为 -ENOMEM (内存不足). */
error = -ENOMEM;
/* 跳转到 exit 标签进行错误处理. */
goto exit;
}
/*
* 调用 sysfs_create_group 在 kernel_kobj 代表的目录下创建一组属性文件.
* @ kernel_kobj: 父 kobject, 即 /sys/kernel 目录.
* @ &kernel_attr_group: 包含所有要创建的文件的属性组.
* 如果创建成功, 返回0, 否则返回一个负的错误码.
*/
error = sysfs_create_group(kernel_kobj, &kernel_attr_group);
/* 检查文件组是否创建成功. */
if (error)
/* 如果失败, 跳转到 kset_exit 标签, 以便清理已创建的 kobject. */
goto kset_exit;

/* 检查 .notes 段的大小是否大于0, 即该段是否存在且不为空. */
if (notes_size > 0) {
/*
* 设置 'notes' 二进制属性的私有数据指针, 让它指向 .notes 段的起始地址.
* sysfs 在被读取时会使用这个指针来找到数据源.
*/
bin_attr_notes.private = (void *)&__start_notes;
/*
* 设置 'notes' 二进制属性的大小.
*/
bin_attr_notes.size = notes_size;
/*
* 调用 sysfs_create_bin_file 在 /sys/kernel 目录下创建 'notes' 这个二进制文件.
* @ kernel_kobj: 父 kobject.
* @ &bin_attr_notes: 要创建的二进制文件的属性描述符.
*/
error = sysfs_create_bin_file(kernel_kobj, &bin_attr_notes);
/* 检查二进制文件是否创建成功. */
if (error)
/* 如果失败, 跳转到 group_exit 标签, 以便清理已创建的属性组. */
goto group_exit;
}

/* 所有操作均成功, 返回0. */
return 0;

/*
* 下面是错误处理的 goto 标签, 用于在初始化失败时按相反的顺序清理已分配的资源.
*/
group_exit:
/* 如果二进制文件创建失败, 就移除之前成功创建的属性文件组. */
sysfs_remove_group(kernel_kobj, &kernel_attr_group);
kset_exit:
/* 如果属性组创建失败, 就释放并注销之前创建的 kobject. */
kobject_put(kernel_kobj);
exit:
/* 返回最终的错误码. */
return error;
}

/*
* core_initcall 是一个宏, 它将 ksysfs_init 函数注册为一个核心初始化函数.
* 这确保了 /sys/kernel 的创建会在内核启动过程中较早地发生.
*/
core_initcall(ksysfs_init);