[TOC]

driver/base/topology.c CPU拓扑(CPU Topology) 通过sysfs导出CPU物理布局与亲和性信息

本代码文件的核心功能是通过Linux的sysfs虚拟文件系统,向用户空间导出CPU的拓扑结构信息。这些信息包括物理封装ID(physical_package_id)、核心ID(core_id)、Die ID等,以及与当前CPU相关的“兄弟”CPU集合,例如共享同一个物理核心的其他线程(thread_siblings)或共享同一个物理封装的所有核心(package_cpus)等。用户和应用程序可以通过读取/sys/devices/system/cpu/cpuX/topology/目录下的文件,来获取处理器的硬件布局,从而进行性能优化、任务调度等高级操作。

实现原理分析

该文件的实现严重依赖于宏定义(define_id_show_func, define_siblings_read_func)来自动化地生成大量功能相似的sysfs属性文件所需的回调函数。其基本工作流程如下:

  1. 宏定义生成函数:通过宏自动生成用于读取特定拓扑ID和CPU掩码的showread函数。这些函数最终会调用体系结构相关的底层函数(如topology_physical_package_id())来获取实际硬件信息。
  2. 属性定义与分组:使用DEVICE_ATTR_ROBIN_ATTR_RO等宏将上一步生成的函数与sysfs中的文件名进行绑定,创建出设备属性结构体。随后,将所有定义的属性组织到一个attribute_group结构体(topology_attr_group)中,该结构体统一管理了所有将在topology目录下创建的文件。
  3. 注册与注销:利用内核的CPU热插拔框架,注册topology_add_devtopology_remove_dev两个回调函数。
    • CPUHP (CPU Hotplug)框架: Linux内核中用于管理CPU动态上线(online)和下线(offline)的机制。
    • 当一个CPU上线时,topology_add_dev会被调用,它通过sysfs_create_group在该CPU对应的sysfs目录下(例如/sys/devices/system/cpu/cpu0/)创建topology子目录及其中所有的属性文件。
    • sysfs: 一个基于内存的虚拟文件系统,用于向用户空间导出内核对象(kobject)及其属性(attribute)。
    • 当CPU下线时,topology_remove_dev则负责清理这些文件。
  4. CPU容量(Capacity)导出:除了拓扑信息,该文件还实现了cpu_capacity属性的创建,用于表示CPU的计算能力或性能等级。这通常与CPU的频率或微架构相关,为上层调度器提供决策依据。

在整个机制中,以下几个概念至关重要:

  • kobject: 内核中用于表示设备等对象的嵌入式结构,是sysfs中目录的基础。每个CPU设备(struct device)都包含一个kobject。
  • cpumask (CPU掩码): 一种位图数据结构,用于高效地表示系统中的一个CPU集合。例如,core_siblings属性文件内容就源于一个cpumask。

特定场景分析:单核、无MMU的STM32H750平台

在STM32H750这样的平台上,整个拓扑结构被极大地简化,该代码的行为和意义也相应地发生变化:

单一拓扑结构

由于只有一个核心,所有与多核、多封装、多线程相关的概念都变得平凡。

  • physical_package_id, die_id, core_id 等标识符几乎都会是 0
  • thread_siblings, core_siblings, package_cpus 等表示CPU集合的cpumask,将只包含CPU 0自身。读取这些文件将返回一个只标记了CPU 0的位图(例如1)或列表(例如0)。

CPU热插拔框架的静态应用

STM32H750的CPU核心是固定的,不存在物理上的热插拔。因此,cpuhp_setup_state注册的回调函数实际上只会在系统启动过程中,当唯一的CPU 0被激活时执行一次topology_add_devtopology_remove_dev函数在正常运行期间永远不会被调用。整个机制从动态管理退化为一次性的静态初始化。

内存分配 (无MMU影响)

define_siblings_read_func宏中使用了alloc_cpumask_var(&mask, GFP_KERNEL)来动态分配内存。在一个没有MMU的系统上(例如运行uClinux),内存分配器(如slab/slob)直接管理物理内存。GFP_KERNEL标志的行为与有MMU的系统有所不同,它仍然表示可以阻塞的内核内存请求,但分配的将是物理连续的内存,不存在虚拟地址到物理地址的转换。对于这个文件来说,其内存分配逻辑本身不受影响,但底层实现依赖于特定的无MMU内存管理策略。

实际意义

尽管导出的拓扑信息非常简单,但为应用程序提供了一个与硬件无关的、标准化的接口来查询CPU信息。这保持了软件的可移植性,一个为多核系统编写的、需要查询CPU信息的程序,无需修改就可以在STM32H750上运行,并正确地识别出这是一个单核系统。cpu_capacity属性依然有意义,它可以用来表示该微控制器在不同时钟频率下的相对性能,为上层功耗管理或调度策略提供依据。

核心宏定义:用于生成sysfs回调函数

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
// 宏定义,用于生成一个名为 name##_show 的sysfs属性读取函数。
// @name: 属性的名称,例如 physical_package_id。
// @fmt: 输出格式化字符串,例如 "%d"。
// 这个宏会创建一个函数,该函数调用 topology_##name(dev->id) 获取CPU的拓扑ID,并使用sysfs_emit格式化输出到buf中。
#define define_id_show_func(name, fmt) \
static ssize_t name##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
return sysfs_emit(buf, fmt "\n", topology_##name(dev->id)); \
}

// 宏定义,用于生成读取CPU掩码(cpumask)的sysfs二进制属性的read函数。
// @name: 属性的名称,例如 thread_siblings。
// @mask: 对应的拓扑掩码类型,例如 sibling_cpumask。
// 这个宏会生成两个函数:
// 1. name##_read: 以位图(bitmap)格式读取CPU掩码。
// 2. name##_list_read: 以列表(list)格式读取CPU掩码。
#define define_siblings_read_func(name, mask) \
static ssize_t name##_read(struct file *file, struct kobject *kobj, \
const struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
struct device *dev = kobj_to_dev(kobj); \
cpumask_var_t mask; \
ssize_t n; \
\
/* 动态分配一个CPU掩码变量。在STM32H750上,这会在内核堆上请求一小块内存。*/ \
if (!alloc_cpumask_var(&mask, GFP_KERNEL)) \
return -ENOMEM; \
\
/* 从拓扑结构中拷贝对应的CPU掩码。对于STM32H750,这里只会拷贝包含CPU 0的掩码。*/ \
cpumask_copy(mask, topology_##mask(dev->id)); \
/* 将CPU掩码以位图的格式打印到缓冲区。*/ \
n = cpumap_print_bitmask_to_buf(buf, mask, off, count); \
/* 释放之前分配的CPU掩码变量。*/ \
free_cpumask_var(mask); \
\
return n; \
} \
\
static ssize_t name##_list_read(struct file *file, struct kobject *kobj, \
const struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
struct device *dev = kobj_to_dev(kobj); \
cpumask_var_t mask; \
ssize_t n; \
\
if (!alloc_cpumask_var(&mask, GFP_KERNEL)) \
return -ENOMEM; \
\
cpumask_copy(mask, topology_##mask(dev->id)); \
/* 将CPU掩码以列表的格式(如 "0" 或 "0-3")打印到缓冲区。*/ \
n = cpumap_print_list_to_buf(buf, mask, off, count); \
free_cpumask_var(mask); \
\
return n; \
}

拓扑属性实例化:通过宏定义具体的sysfs文件接口

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
// 使用宏生成 physical_package_id_show 函数。
define_id_show_func(physical_package_id, "%d");
// 定义一个只读的设备属性 physical_package_id,并将其与上面的show函数关联。
static DEVICE_ATTR_RO(physical_package_id);

// 如果定义了TOPOLOGY_DIE_SYSFS,则创建 die_id 相关属性。
#ifdef TOPOLOGY_DIE_SYSFS
define_id_show_func(die_id, "%d");
static DEVICE_ATTR_RO(die_id);
#endif

// 如果定义了TOPOLOGY_CLUSTER_SYSFS,则创建 cluster_id 相关属性。
#ifdef TOPOLOGY_CLUSTER_SYSFS
define_id_show_func(cluster_id, "%d");
static DEVICE_ATTR_RO(cluster_id);
#endif

// 创建 core_id 相关属性。在STM32H750上,其值恒为0。
define_id_show_func(core_id, "%d");
static DEVICE_ATTR_RO(core_id);

// 创建 ppin (Processor Proximity Identification Number) 相关属性。
define_id_show_func(ppin, "0x%llx");
static DEVICE_ATTR_ADMIN_RO(ppin); // 仅管理员可读。

// 使用宏生成 thread_siblings_read 和 thread_siblings_list_read 函数。
// 在STM32H750上,线程兄弟就是CPU 0自身。
define_siblings_read_func(thread_siblings, sibling_cpumask);
// 定义名为 thread_siblings 的只读二进制属性,用于以位图格式显示。
static const BIN_ATTR_RO(thread_siblings, CPUMAP_FILE_MAX_BYTES);
// 定义名为 thread_siblings_list 的只读二进制属性,用于以列表格式显示。
static const BIN_ATTR_RO(thread_siblings_list, CPULIST_FILE_MAX_BYTES);

// 定义 core_cpus 和 core_cpus_list 属性,表示同一核心下的所有CPU(线程)。
define_siblings_read_func(core_cpus, sibling_cpumask);
static const BIN_ATTR_RO(core_cpus, CPUMAP_FILE_MAX_BYTES);
static const BIN_ATTR_RO(core_cpus_list, CPULIST_FILE_MAX_BYTES);

// 定义 core_siblings 和 core_siblings_list 属性,表示同一物理封装下所有核心的CPU。
define_siblings_read_func(core_siblings, core_cpumask);
static const BIN_ATTR_RO(core_siblings, CPUMAP_FILE_MAX_BYTES);
static const BIN_ATTR_RO(core_siblings_list, CPULIST_FILE_MAX_BYTES);

// ... 其他类似属性的定义 ...
#ifdef TOPOLOGY_CLUSTER_SYSFS
define_siblings_read_func(cluster_cpus, cluster_cpumask);
static const BIN_ATTR_RO(cluster_cpus, CPUMAP_FILE_MAX_BYTES);
static const BIN_ATTR_RO(cluster_cpus_list, CPULIST_FILE_MAX_BYTES);
#endif

#ifdef TOPOLOGY_DIE_SYSFS
define_siblings_read_func(die_cpus, die_cpumask);
static const BIN_ATTR_RO(die_cpus, CPUMAP_FILE_MAX_BYTES);
static const BIN_ATTR_RO(die_cpus_list, CPULIST_FILE_MAX_BYTES);
#endif

define_siblings_read_func(package_cpus, core_cpumask);
static const BIN_ATTR_RO(package_cpus, CPUMAP_FILE_MAX_BYTES);
static const BIN_ATTR_RO(package_cpus_list, CPULIST_FILE_MAX_BYTES);

#ifdef TOPOLOGY_BOOK_SYSFS
define_id_show_func(book_id, "%d");
static DEVICE_ATTR_RO(book_id);
define_siblings_read_func(book_siblings, book_cpumask);
static const BIN_ATTR_RO(book_siblings, CPUMAP_FILE_MAX_BYTES);
static const BIN_ATTR_RO(book_siblings_list, CPULIST_FILE_MAX_BYTES);
#endif

#ifdef TOPOLOGY_DRAWER_SYSFS
define_id_show_func(drawer_id, "%d");
static DEVICE_ATTR_RO(drawer_id);
define_siblings_read_func(drawer_siblings, drawer_cpumask);
static const BIN_ATTR_RO(drawer_siblings, CPUMAP_FILE_MAX_BYTES);
static const BIN_ATTR_RO(drawer_siblings_list, CPULIST_FILE_MAX_BYTES);
#endif

属性分组与可见性控制

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
// 定义一个包含所有二进制属性指针的数组,用于属性组的创建。
static const struct bin_attribute *const bin_attrs[] = {
&bin_attr_core_cpus,
&bin_attr_core_cpus_list,
// ... 其他二进制属性 ...
NULL // 数组以NULL结尾。
};

// 定义一个包含所有默认文本属性指针的数组。
static struct attribute *default_attrs[] = {
&dev_attr_physical_package_id.attr,
// ... 其他文本属性 ...
NULL // 数组以NULL结尾。
};

// topology_is_visible: is_visible回调函数,用于动态决定某个属性文件是否应该在sysfs中可见。
static umode_t topology_is_visible(struct kobject *kobj,
struct attribute *attr, int unused)
{
// 如果属性是 ppin 并且底层硬件不支持(返回0),则文件不可见。
if (attr == &dev_attr_ppin.attr && !topology_ppin(kobj_to_dev(kobj)->id))
return 0; // 返回0表示文件不可见。

return attr->mode; // 否则返回属性默认的模式(权限)。
}

// 定义topology属性组,它包含了上面定义的文本属性和二进制属性。
static const struct attribute_group topology_attr_group = {
.attrs = default_attrs,
.bin_attrs = bin_attrs,
.is_visible = topology_is_visible,
.name = "topology" // 在sysfs中创建的子目录名。
};

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
// topology_add_dev: CPU上线回调函数,为指定的CPU设备添加topology sysfs接口。
static int topology_add_dev(unsigned int cpu)
{
// 获取指定CPU号的device结构体指针。
struct device *dev = get_cpu_device(cpu);

// 在该设备的kobject下创建topology属性组。
return sysfs_create_group(&dev->kobj, &topology_attr_group);
}

// topology_remove_dev: CPU下线回调函数,为指定的CPU设备移除topology sysfs接口。
static int topology_remove_dev(unsigned int cpu)
{
struct device *dev = get_cpu_device(cpu);

// 从该设备的kobject下移除topology属性组。
sysfs_remove_group(&dev->kobj, &topology_attr_group);
return 0;
}

// topology_sysfs_init: topology sysfs接口的模块初始化函数。
static int __init topology_sysfs_init(void)
{
// 注册CPU热插拔状态的回调函数。
// 当CPU准备就绪时调用topology_add_dev,当CPU被移除前调用topology_remove_dev。
// 在STM32H750上,这会在启动时为CPU 0调用一次topology_add_dev。
return cpuhp_setup_state(CPUHP_TOPOLOGY_PREPARE,
"base/topology:prepare", topology_add_dev,
topology_remove_dev);
}

// 使用device_initcall宏将topology_sysfs_init注册为内核初始化函数。
device_initcall(topology_sysfs_init);

CPU计算能力(Capacity)相关实现

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
// 为每个CPU定义一个名为cpu_scale的变量,并初始化为调度器容量的默认值。
// 在STM32H750上,这只会实例化一个变量,即per_cpu(cpu_scale, 0)。
DEFINE_PER_CPU(unsigned long, cpu_scale) = SCHED_CAPACITY_SCALE;
// 导出该per-cpu变量符号,以便其他模块可以使用。
EXPORT_PER_CPU_SYMBOL_GPL(cpu_scale);

// topology_set_cpu_scale: 设置函数,用于更新指定CPU的计算能力(scale)值。
void topology_set_cpu_scale(unsigned int cpu, unsigned long capacity)
{
per_cpu(cpu_scale, cpu) = capacity;
}

// cpu_capacity_show: cpu_capacity属性的show函数,用于在sysfs中显示CPU的计算能力。
static ssize_t cpu_capacity_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cpu *cpu = container_of(dev, struct cpu, dev);

return sysfs_emit(buf, "%lu\n", topology_get_cpu_scale(cpu->dev.id));
}

// 定义一个名为cpu_capacity的只读设备属性。
static DEVICE_ATTR_RO(cpu_capacity);

// cpu_capacity_sysctl_add: CPU上线回调,为指定CPU添加cpu_capacity的sysfs文件。
static int cpu_capacity_sysctl_add(unsigned int cpu)
{
struct device *cpu_dev = get_cpu_device(cpu);

if (!cpu_dev)
return -ENOENT;

// 在CPU设备下创建 cpu_capacity 文件。
device_create_file(cpu_dev, &dev_attr_cpu_capacity);

return 0;
}

// cpu_capacity_sysctl_remove: CPU下线回调,为指定CPU移除cpu_capacity的sysfs文件。
static int cpu_capacity_sysctl_remove(unsigned int cpu)
{
struct device *cpu_dev = get_cpu_device(cpu);

if (!cpu_dev)
return -ENOENT;

// 从CPU设备下移除 cpu_capacity 文件。
device_remove_file(cpu_dev, &dev_attr_cpu_capacity);

return 0;
}

// register_cpu_capacity_sysctl: 初始化函数,注册用于创建cpu_capacity sysfs文件的热插拔回调。
static int register_cpu_capacity_sysctl(void)
{
// 同样使用CPU热插拔框架,在CPU上线后调用cpu_capacity_sysctl_add。
cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "topology/cpu-capacity",
cpu_capacity_sysctl_add, cpu_capacity_sysctl_remove);

return 0;
}
// 使用subsys_initcall宏在子系统初始化阶段调用注册函数。
subsys_initcall(register_cpu_capacity_sysctl);