[TOC]

drivers/base/cpu.c CPU设备管理(CPU Device Management) CPU热插拔的核心实现

drivers/base/cpu.c 是Linux内核设备模型中的一个基础文件,其核心职责是将系统中的各个CPU(中央处理器)抽象为标准的设备,并通过sysfs文件系统进行管理。该文件最主要的功能是为Linux内核提供CPU热插拔(CPU Hotplug)机制的通用框架,允许在系统运行时动态地使CPU核心上线(online)或离线(offline)。

历史与背景

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

CPU热插拔机制的出现主要是为了满足日益复杂的系统需求:

  • RAS(可靠性、可用性、可服务性):在高端服务器和NUMA(非统一内存访问)硬件中,系统需要能够在不宕机的情况下,物理上移除发生故障的CPU节点,或添加新的计算资源。
  • 电源管理:在负载较低时,可以将闲置的CPU核心完全离线,从而彻底切断其电源,实现比普通待机模式更深层次的节能。
  • 资源灵活性与虚拟化:在虚拟化环境中,可以根据虚拟机的负载动态增加或减少vCPU(虚拟CPU)的数量,实现计算资源的弹性伸缩,优化资源利用率。
  • 系统启动与休眠:在多核(SMP)系统中,CPU热插拔的机制也被用于简化系统的启动(例如,通过maxcpus内核参数限制启动时在线的CPU数量)和挂起/恢复流程。

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

CPU热插拔功能并非Linux内核与生俱来。早期内核中的所有硬件均在启动时一次性完成检测和初始化。随着模块化机制的引入,Linux开始具备运行时加载/卸载驱动的能力,为更复杂的硬件热插拔奠定了基础。 针对CPU的热插拔最初是为特定高端服务器硬件开发的补丁,后来逐渐演变为一个通用的内核子系统。 其发展过程中的一个重要演进是从简单的CPU上下线操作,发展为一个精细、分阶段的状态机,允许内核的其他子系统(如调度器、定时器、中断等)在CPU状态变化的特定节点注册回调函数,从而确保整个系统在CPU数量动态变化时保持稳定和一致。

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

CPU热插拔是一项成熟且仍在持续维护的内核核心技术。它被广泛应用于:

  • 云计算和虚拟化:各大云服务商的核心功能之一,用于实现虚拟机的弹性计算。
  • 数据中心:用于物理服务器的故障隔离、维护和动态资源配置。
  • 移动和嵌入式设备:通过在低负载时关闭部分CPU核心来显著延长电池续航。
  • 高性能计算(HPC):根据计算任务的并行度需求,动态调整参与计算的CPU核心数量。

核心原理与设计

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

drivers/base/cpu.c 的核心原理是围绕一个严格的状态机来管理CPU的生命周期。

  1. CPU设备抽象:内核启动时,会为每个“可能存在”的CPU在sysfs文件系统下创建对应的设备目录,通常位于 /sys/devices/system/cpu/cpuX
  2. 用户空间触发:管理员或自动化脚本通过向 /sys/devices/system/cpu/cpuX/online 文件写入 0(离线)或 1(上线)来发起热插拔请求。
  3. 热插拔线程:为了避免在中断或关键上下文中执行耗时的热插拔操作,系统为每个CPU都创建了一个名为 cpuhp/X 的内核线程,专门用于执行该CPU的上线或离线回调任务。
  4. 状态迁移与回调
    • 离线过程:当一个CPU被请求离线时,系统会启动一个多阶段的移除流程。首先,内核会将该CPU上的所有进程迁移到其他在线的CPU上。接着,绑定在该CPU上的中断、定时器等资源也会被迁移。 然后,系统会按照预定义的逆序调用一系列已注册的回调函数,通知内核各子系统(调度器、内存管理、驱动等)为该CPU的离线做准备。在所有准备工作完成后,该CPU最终进入深度休眠或断电状态。
    • 上线过程:过程与离线相反。目标CPU被唤醒,并顺序执行一系列启动回调函数,用于初始化其 per-CPU 数据结构和相关子系统状态。当所有回调成功执行后,该CPU被标记为 online 状态,正式加入调度器的可用CPU池中。

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

  • 灵活性:无需重启系统即可动态调整计算资源。
  • 高节能性:将CPU核心完全下电是最高效的CPU节能方式。
  • 可靠性:能够安全地隔离有故障的CPU,防止其影响整个系统的稳定性。
  • 资源优化:在虚拟化场景中,通过动态调整vCPU,可以显著提高物理资源的利用率。

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

  • 性能开销:CPU上线和离线的过程涉及大量复杂操作,如任务迁移、缓存刷新和资源重新分配,会带来不可忽略的延迟和性能开销。
  • 软件兼容性问题:并非所有软件都能很好地处理CPU数量的动态变化。一些设计不佳的应用程序或内核驱动可能会在CPU热插拔后出现功能错误或性能下降。
  • 对vNUMA的负面影响:在虚拟化环境中(如VMware),为虚拟机启用vCPU热添加功能,在vSphere 8.0之前通常会自动禁用vNUMA(虚拟非统一内存访问)。 这会导致虚拟机操作系统失去对底层NUMA拓扑的感知,对于大型数据库等对内存访问延迟敏感的应用可能造成性能下降。
  • 启动CPU的限制:通常情况下,系统的引导CPU(通常是CPU0)不能被离线,因为它承担了一些特殊的系统初始化和管理任务。但在某些架构(如x86)上,可以通过特定的内核参数(如cpu0_hotplug)来解除这一限制。

使用场景

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

  • 云主机弹性伸缩:当网站流量激增时,云平台可以自动为承载网站的虚拟机热添加vCPU以应对高负载;当流量回落时,再热移除多余的vCPU以节约成本。
  • 服务器硬件维护:当监控系统检测到某个物理CPU核心出现大量硬件错误时,系统管理员可以将其热插拔离线,以防止错误扩散,同时保持服务器在线服务。
  • 移动设备电源管理:智能手机在执行轻量级任务(如阅读)时,可以自动将高性能核心离线,只保留能效核心工作,从而最大化地延长电池续航。

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

  • 硬实时系统:CPU热插拔过程中的延迟是不可预测的,这会破坏硬实时系统所需的时间确定性,因此不推荐使用。
  • 对NUMA拓扑高度敏感的应用:对于大型数据库、科学计算等需要精细优化NUMA局部性的应用,如果启用CPU热插拔会导致vNUMA被禁用,那么静态配置CPU资源通常是更好的选择,以避免跨NUMA节点的内存访问所带来的性能损失。
  • 缺乏充分测试的复杂环境:在运行了大量未经CPU热插拔兼容性测试的应用程序的生产环境中,随意使用该功能可能会引入潜在的稳定性和性能风险。

对比分析

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

在Linux中,与CPU资源和功耗管理相关的技术主要还有CPUIdle和CPU Frequency Scaling (cpufreq)。它们的目标相似,但机制和适用场景完全不同。

特性 CPU Hotplug (CPU热插拔) CPUIdle (CPU空闲状态) CPU Frequency Scaling (cpufreq)
功能概述 将CPU核心从操作系统中完全移除或加入,使其在线(online)或离线(offline)。 当CPU没有任务时,让其进入不同的低功耗睡眠状态(C-states)。 根据系统负载动态调整CPU的工作频率和电压(P-states)。
实现方式 drivers/base/cpu.c主导的复杂状态机,涉及任务迁移和多级内核子系统回调。 cpuidle框架和驱动管理,调度器在CPU空闲时调用。 cpufreq框架、驱动和调速器(governor)策略共同决定。
性能开销 。状态转换是重量级操作,耗时可达毫秒甚至秒级。 。进出C-state的延迟通常在微秒级别,是轻量级操作。 中等。改变频率和电压的速度快于热插拔,但慢于进入Idle状态。
资源占用 内核中维护状态机和per-CPU线程,开销相对较大。 框架本身开销极小。 框架和调速器开销极小。
隔离级别 完全隔离。离线的CPU对调度器完全不可见,不参与任何计算。 不隔离。CPU仍在线,随时可以被中断或新任务唤醒。 不隔离。CPU持续在线并处理任务,只是运行在不同的性能水平上。
转换速度 非常快
主要用途 长期的资源调整、故障隔离、最大化节能。 短期的、频繁的、机会性的节能。 在性能和功耗之间取得实时动态平衡。

register_cpu: 注册一个CPU设备并创建其sysfs接口

此函数是Linux内核中将一个CPU正式注册为“设备”的核心步骤。它接收一个描述CPU属性的struct cpu对象和一个CPU编号,然后初始化其中内嵌的struct device,并将其注册到CPU子系统(总线)中。这个注册动作的最终结果是在sysfs文件系统中创建出代表该CPU的设备目录(如/sys/devices/system/cpu/cpu0)以及其所有的属性文件。

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
/*
* register_cpu -为一个CPU设置一个sysfs设备.
* @cpu - cpu->hotpluggable 字段被设为1时, 会为此CPU在sysfs中生成一个控制文件.
* @num - 创建设备时使用的CPU编号.
*
* 初始化并注册CPU设备.
*/
int register_cpu(struct cpu *cpu, int num)
{
/*
* 定义一个整型变量 error, 用于存储函数调用的返回值.
*/
int error;

/*
* 设置CPU所属的NUMA节点ID. 对于非NUMA架构的STM32, cpu_to_node(0)会返回0.
*/
cpu->node_id = cpu_to_node(num);
/*
* 使用0x00字节串初始化cpu结构体中内嵌的device成员.
* 这是一个关键的步骤, 确保在填充字段前所有成员都处于已知的零状态.
*/
memset(&cpu->dev, 0x00, sizeof(struct device));
/*
* 设置设备的ID. 对于CPU 0, 这里设置为0.
* 总线类型会用这个ID和它的dev_name("cpu")来生成最终的设备名 "cpu0".
*/
cpu->dev.id = num;
/*
* 将此设备关联到cpu_subsys总线. 这告诉内核驱动核心如何管理这个设备.
*/
cpu->dev.bus = &cpu_subsys;
/*
* 设置release回调函数. 当此设备的最后一个引用被释放时, 内核会调用cpu_device_release
* 来清理资源. 对于永不释放的CPU设备, 这主要是为了模型的完整性和正确性.
*/
cpu->dev.release = cpu_device_release;
/*
* 设置 offline_disabled 标志. 因为STM32H750的CPU不支持热插拔(hotpluggable为false),
* !cpu->hotpluggable 为 true, 表示此设备禁止被下线.
*/
cpu->dev.offline_disabled = !cpu->hotpluggable;
/*
* 设置设备的初始离线状态. cpu_online(num) 检查CPU当前是否在线.
* 在启动期间, CPU 0肯定是在线的, 所以cpu_online(0)为true, !true为false.
* cpu->dev.offline被设为false, 准确反映其在线状态.
*/
cpu->dev.offline = !cpu_online(num);
/*
* 获取与此CPU编号关联的设备树节点. of_get_cpu_node() 会在设备树中查找
* /cpus/cpu@<num> 节点. 这是将物理硬件描述与内核设备对象关联的关键步骤.
*/
cpu->dev.of_node = of_get_cpu_node(num, NULL);
/*
* 默认情况下, 将设备的属性组指向通用CPU属性组(common_cpu_attr_groups).
*/
cpu->dev.groups = common_cpu_attr_groups;
/*
* 检查CPU是否支持热插拔. 对于STM32H750, 此条件为false.
*/
if (cpu->hotpluggable)
/*
* 如果支持热插拔, 则覆盖为支持热插拔的属性组, 其中会包含 'online' 等控制文件.
* 这段代码在STM32H750上不会被执行.
*/
cpu->dev.groups = hotplugable_cpu_attr_groups;
/*
* 调用 device_register(), 将这个完全配置好的device对象注册到内核驱动核心.
* 此调用执行后, /sys/devices/system/cpu/cpu0 目录及其属性文件将出现在sysfs中.
*/
error = device_register(&cpu->dev);
if (error) {
/*
* 如果注册失败, 调用 put_device() 来减少引用计数并触发清理, 然后返回错误.
*/
put_device(&cpu->dev);
return error;
}

/*
* 将新注册的device对象的指针保存到一个per-cpu数组中.
* 这为内核其他部分提供了一个快速访问特定CPU设备对象的途径.
*/
per_cpu(cpu_sys_devices, num) = &cpu->dev;
/*
* 在NUMA节点的sysfs目录下创建一个指向CPU设备的符号链接.
* 对于STM32, 这会创建 /sys/devices/system/node/node0/cpu0 -> ../../cpu/cpu0.
*/
register_cpu_under_node(num, cpu_to_node(num));
/*
* 为该设备暴露一个电源管理服务质量(PM QoS)的延迟限制接口.
* PM_QOS_RESUME_LATENCY_NO_CONSTRAINT表示此CPU默认没有特定的恢复延迟要求.
*/
dev_pm_qos_expose_latency_limit(&cpu->dev,
PM_QOS_RESUME_LATENCY_NO_CONSTRAINT);
/*
* 调用 set_cpu_enabled, 更新调度器相关的CPU掩码,
* 正式通知调度器此CPU现在可用.
*/
set_cpu_enabled(num, true);

return 0;
}

arch_register_cpu: 注册一个CPU设备(架构相关的弱实现)

此函数为一个指定的CPU编号创建一个struct cpu设备对象,并调用核心的register_cpu函数将其注册到CPU子系统中。__weak属性表示这是一个可以被特定架构(如ARM、x86)提供的更强实现所覆盖的默认版本。

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
/*
* 函数 arch_register_cpu
* __weak 关键字表示这是一个弱符号. 如果链接时存在一个同名的非弱符号(强符号),
* 链接器将使用强符号的版本, 忽略此弱符号版本. 这是一种允许架构(arch)
* 覆盖通用默认实现的机制.
* @cpu: 将要被注册的CPU的逻辑编号.
* @return: 成功时返回0, 失败时返回负的错误码.
*/
int __weak arch_register_cpu(int cpu)
{
/*
* 定义一个指向 struct cpu 的指针 c.
* per_cpu 是一个宏, 用于访问指定CPU的per-cpu变量.
* 此行代码的作用是获取全局per-cpu数组 cpu_devices 中, 索引为'cpu'的那个元素,
* 并将其地址赋值给 c. 每个CPU在系统中都有一个对应的 struct cpu 实例来描述它.
*/
struct cpu *c = &per_cpu(cpu_devices, cpu);

/*
* 设置 c->hotpluggable 字段.
* 这个字段标识了该CPU是否支持热插拔 (即在系统运行时动态地添加或移除).
* 它调用 arch_cpu_is_hotpluggable() 函数来获取这个信息.
* 对于像STM32H750这样的嵌入式处理器, CPU是固定的, 此函数将返回false.
*/
c->hotpluggable = arch_cpu_is_hotpluggable(cpu);

/*
* 调用核心的 register_cpu 函数, 将这个描述CPU的设备对象(c)注册到内核中.
* 这是实际执行注册操作, 创建sysfs条目等工作的函数.
*/
return register_cpu(c, cpu);
}

cpu_dev_register_generic: 注册所有存在的CPU设备

此函数是一个__init函数,在内核启动时被调用一次。它负责遍历所有被硬件识别出的CPU,并为每一个CPU调用arch_register_cpu来进行注册。

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
/*
* 函数 cpu_dev_register_generic
* static 关键字表示此函数仅在当前文件中可见.
* __init 关键字告诉编译器将此函数放入特殊的初始化代码段,
* 在内核启动完成后, 这段代码所占用的内存会被释放, 以节约内存.
* void 表示此函数无参数和返回值.
*/
static void __init cpu_dev_register_generic(void)
{
/*
* 定义两个整型变量. i 用作循环计数器(CPU索引), ret 用于保存函数调用的返回值.
*/
int i, ret;

/*
* IS_ENABLED 是一个宏, 用于在编译时检查一个内核配置选项(Kconfig)是否被启用.
* 如果 CONFIG_GENERIC_CPU_DEVICES 没有被启用, 则直接返回, 不执行任何操作.
* 这样做可以裁剪掉不需要的功能, 减小内核体积.
*/
if (!IS_ENABLED(CONFIG_GENERIC_CPU_DEVICES))
return;

/*
* for_each_present_cpu 是一个宏, 用于遍历所有当前"存在"(被物理检测到)的CPU.
* 在像STM32H750这样的单核系统上, 这个循环只会执行一次, 变量 i 的值将是 0.
*/
for_each_present_cpu(i) {
/*
* 对当前CPU(i), 调用架构相关的注册函数.
*/
ret = arch_register_cpu(i);
/*
* 检查注册操作的返回值. 如果 ret 不为0 (表示有错误发生),
* 并且错误不是 -EPROBE_DEFER (表示一个依赖项尚未就绪, 需要稍后重试),
* 那么就打印一条警告信息.
*/
if (ret && ret != -EPROBE_DEFER)
pr_warn("register_cpu %d failed (%d)\n", i, ret);
}
}

cpu_dev_init: 初始化CPU子系统并创建sysfs接口

此函数在内核启动的早期被调用,其核心作用是在sysfs文件系统中建立起代表CPU的目录结构。它注册了一个名为“cpu”的子系统(总线),并在/sys/devices/system/cpu/下创建了代表系统中每个CPU的设备节点(如cpu0),以及一些用于查询和控制CPU状态的全局属性文件。

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
#define _CPU_ATTR(name, map) \
{ __ATTR(name, 0444, show_cpus_attr, NULL), map }

/*
* 定义一个 cpu_attr 结构体数组. 这个数组中的每一项都代表一个 sysfs 属性文件,
* 这些文件显示了系统中不同状态的CPU掩码.
* _CPU_ATTR 是一个宏, 用于方便地定义一个属性, 它将属性名和对应的CPU掩码变量关联起来.
*/
static struct cpu_attr cpu_attrs[] = {
/*
* 定义 "online" 属性. 它关联到 __cpu_online_mask 变量, 该变量是一个位掩码,
* 表示当前哪些CPU处于在线(可调度任务)状态.
* 在单核系统中, 该掩码的值如果CPU在线则为1 (二进制...01).
*/
_CPU_ATTR(online, &__cpu_online_mask),
/*
* 定义 "possible" 属性. 关联到 __cpu_possible_mask, 表示系统启动时内核认为
* 可能存在的CPU. 这个掩码在系统启动后通常是固定的.
* 在单核系统中, 该值为1.
*/
_CPU_ATTR(possible, &__cpu_possible_mask),
/*
* 定义 "present" 属性. 关联到 __cpu_present_mask, 表示实际被硬件检测到的CPU.
* 在单核系统中, 该值为1.
*/
_CPU_ATTR(present, &__cpu_present_mask),
};

/*
* 定义一个属性指针数组, 它集合了将要被注册在 /sys/devices/system/cpu/ 目录下的
* 所有全局属性文件的指针.
*/
static struct attribute *cpu_root_attrs[] = {
/*
* 这是一个条件编译块. 仅当内核配置了 CONFIG_ARCH_CPU_PROBE_RELEASE 时,
* probe 和 release 这两个属性文件才会被包含进来. 它们用于动态地探测和释放CPU.
* 对于固定的单核MCU, 此配置通常被禁用.
*/
#ifdef CONFIG_ARCH_CPU_PROBE_RELEASE
&dev_attr_probe.attr,
&dev_attr_release.attr,
#endif
/*
* 将上面定义的 online, possible, present 三个属性添加到数组中.
*/
&cpu_attrs[0].attr.attr,
&cpu_attrs[1].attr.attr,
&cpu_attrs[2].attr.attr,
/*
* &dev_attr_kernel_max.attr: 代表内核支持的最大CPU索引号 (nr_cpu_ids - 1).
* &dev_attr_offline.attr: 用于手动下线CPU的接口 (需要CONFIG_HOTPLUG_CPU).
* &dev_attr_enabled.attr: 打印可用(enabled)的CPU, 用于任务分配.
* &dev_attr_isolated.attr: 用于内核隔离(isocpus)功能的CPU.
*/
&dev_attr_kernel_max.attr,
&dev_attr_offline.attr,
&dev_attr_enabled.attr,
&dev_attr_isolated.attr,
/*
* 仅当配置了 CONFIG_NO_HZ_FULL (用于无动态时钟节拍的全速CPU)时, 才包含此属性.
*/
#ifdef CONFIG_NO_HZ_FULL
&dev_attr_nohz_full.attr,
#endif
/*
* 仅当配置了 CONFIG_CRASH_HOTPLUG (用于在系统崩溃时保留CPU状态)时, 才包含此属性.
*/
#ifdef CONFIG_CRASH_HOTPLUG
&dev_attr_crash_hotplug.attr,
#endif
/*
* 仅当配置了 CONFIG_GENERIC_CPU_AUTOPROBE (通用CPU自动探测)时, 才包含modalias属性,
* 用于CPU驱动的自动加载.
*/
#ifdef CONFIG_GENERIC_CPU_AUTOPROBE
&dev_attr_modalias.attr,
#endif
/*
* 数组以NULL指针结尾, 这是内核属性数组的标准格式.
*/
NULL
};

/*
* 将属性数组封装进一个属性组结构体中.
*/
static const struct attribute_group cpu_root_attr_group = {
.attrs = cpu_root_attrs,
};

/*
* 创建一个属性组指针数组. 这样做是为了将来可以方便地添加更多的属性组.
*/
static const struct attribute_group *cpu_root_attr_groups[] = {
&cpu_root_attr_group,
NULL,
};

/*
* 这是CPU子系统总线类型的核心定义. 它描述了 "cpu" 总线的所有行为.
*/
const struct bus_type cpu_subsys = {
/*
* .name = "cpu": 总线的名称. 这将导致在sysfs中创建 /sys/bus/cpu 目录.
*/
.name = "cpu",
/*
* .dev_name = "cpu": 作为设备名称的前缀. 注册的CPU设备会被命名为 cpu0, cpu1 等.
*/
.dev_name = "cpu",
/*
* .match: 指向一个匹配函数(cpu_subsys_match). 当有新的CPU设备或驱动注册时,
* 内核会调用此函数来判断它们是否匹配.
*/
.match = cpu_subsys_match,
/*
* 这是一个条件编译块. 这些回调函数仅在内核配置了CPU热插拔(CONFIG_HOTPLUG_CPU)时才存在.
* 对于像STM32H750这样的固定单核系统, 此配置被禁用, 这两个字段将不存在于最终的结构体中.
*/
#ifdef CONFIG_HOTPLUG_CPU
/*
* .online: CPU上线操作的回调函数.
*/
.online = cpu_subsys_online,
/*
* .offline: CPU下线操作的回调函数.
*/
.offline = cpu_subsys_offline,
#endif
#ifdef CONFIG_GENERIC_CPU_AUTOPROBE
/*
* .uevent: 用于生成uevent事件的回调函数, 以便udev等用户空间程序可以自动加载CPU驱动.
*/
.uevent = cpu_uevent,
#endif
};
/*
* 将 cpu_subsys 的符号导出, 以便其他GPL许可证的内核模块可以引用它.
*/
EXPORT_SYMBOL_GPL(cpu_subsys);


/*
* 定义一个内联函数 cpu_register_vulnerabilities.
* 对于不涉及相关安全漏洞的简单CPU架构(如ARMv7-M), 此函数被定义为一个空函数体,
* 编译器会将其优化掉, 不产生任何开销.
*/
static inline void cpu_register_vulnerabilities(void) { }
/*
* 函数 cpu_dev_init
* __init 标记表示这是一个内核初始化函数. 在内核启动完成后,
* 该函数所占用的内存会被释放, 这对于内存有限的嵌入式系统至关重要.
*/
void __init cpu_dev_init(void)
{
/*
* 调用 subsys_system_register() 来注册CPU子系统.
* 这个函数会在 /sys/devices/system/ 目录下创建 "cpu" 目录,
* 并将 cpu_root_attr_groups 中定义的全局属性文件创建在其中.
* 同时, 它也会注册 cpu_subsys 总线类型, 创建 /sys/bus/cpu 目录.
*/
if (subsys_system_register(&cpu_subsys, cpu_root_attr_groups))
/*
* 如果注册失败, 这是一个致命错误, 因为CPU是系统的核心.
* 调用 panic() 函数会使内核停止运行并打印错误信息.
*/
panic("Failed to register CPU subsystem");

/*
* 调用 cpu_dev_register_generic() 函数, 它会遍历系统中的所有CPU
* (在单核系统中只有一个), 并为每个CPU创建一个对应的设备 (即 /sys/devices/system/cpu/cpu0).
*/
cpu_dev_register_generic();
/*
* 调用一个空函数, 用于注册CPU安全漏洞相关信息. 在这里无任何操作.
*/
cpu_register_vulnerabilities();
}