[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属性文件所需的回调函数。其基本工作流程如下:
- 宏定义生成函数:通过宏自动生成用于读取特定拓扑ID和CPU掩码的
show
或read
函数。这些函数最终会调用体系结构相关的底层函数(如topology_physical_package_id()
)来获取实际硬件信息。
- 属性定义与分组:使用
DEVICE_ATTR_RO
和BIN_ATTR_RO
等宏将上一步生成的函数与sysfs中的文件名进行绑定,创建出设备属性结构体。随后,将所有定义的属性组织到一个attribute_group
结构体(topology_attr_group
)中,该结构体统一管理了所有将在topology
目录下创建的文件。
- 注册与注销:利用内核的CPU热插拔框架,注册
topology_add_dev
和topology_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
则负责清理这些文件。
- 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_dev
。topology_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
|
#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)); \ }
#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; \ \ \ if (!alloc_cpumask_var(&mask, GFP_KERNEL)) \ return -ENOMEM; \ \ \ cpumask_copy(mask, topology_##mask(dev->id)); \ \ n = cpumap_print_bitmask_to_buf(buf, mask, off, count); \ \ 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)); \ \ 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
| define_id_show_func(physical_package_id, "%d");
static DEVICE_ATTR_RO(physical_package_id);
#ifdef TOPOLOGY_DIE_SYSFS define_id_show_func(die_id, "%d"); static DEVICE_ATTR_RO(die_id); #endif
#ifdef TOPOLOGY_CLUSTER_SYSFS define_id_show_func(cluster_id, "%d"); static DEVICE_ATTR_RO(cluster_id); #endif
define_id_show_func(core_id, "%d"); static DEVICE_ATTR_RO(core_id);
define_id_show_func(ppin, "0x%llx"); static DEVICE_ATTR_ADMIN_RO(ppin);
define_siblings_read_func(thread_siblings, sibling_cpumask);
static const BIN_ATTR_RO(thread_siblings, CPUMAP_FILE_MAX_BYTES);
static const BIN_ATTR_RO(thread_siblings_list, CPULIST_FILE_MAX_BYTES);
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);
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 };
static struct attribute *default_attrs[] = { &dev_attr_physical_package_id.attr, NULL };
static umode_t topology_is_visible(struct kobject *kobj, struct attribute *attr, int unused) { if (attr == &dev_attr_ppin.attr && !topology_ppin(kobj_to_dev(kobj)->id)) return 0;
return attr->mode; }
static const struct attribute_group topology_attr_group = { .attrs = default_attrs, .bin_attrs = bin_attrs, .is_visible = topology_is_visible, .name = "topology" };
|
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
| static int topology_add_dev(unsigned int cpu) { struct device *dev = get_cpu_device(cpu);
return sysfs_create_group(&dev->kobj, &topology_attr_group); }
static int topology_remove_dev(unsigned int cpu) { struct device *dev = get_cpu_device(cpu);
sysfs_remove_group(&dev->kobj, &topology_attr_group); return 0; }
static int __init topology_sysfs_init(void) { return cpuhp_setup_state(CPUHP_TOPOLOGY_PREPARE, "base/topology:prepare", topology_add_dev, topology_remove_dev); }
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
|
DEFINE_PER_CPU(unsigned long, cpu_scale) = SCHED_CAPACITY_SCALE;
EXPORT_PER_CPU_SYMBOL_GPL(cpu_scale);
void topology_set_cpu_scale(unsigned int cpu, unsigned long capacity) { per_cpu(cpu_scale, cpu) = capacity; }
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)); }
static DEVICE_ATTR_RO(cpu_capacity);
static int cpu_capacity_sysctl_add(unsigned int cpu) { struct device *cpu_dev = get_cpu_device(cpu);
if (!cpu_dev) return -ENOENT;
device_create_file(cpu_dev, &dev_attr_cpu_capacity);
return 0; }
static int cpu_capacity_sysctl_remove(unsigned int cpu) { struct device *cpu_dev = get_cpu_device(cpu);
if (!cpu_dev) return -ENOENT;
device_remove_file(cpu_dev, &dev_attr_cpu_capacity);
return 0; }
static int register_cpu_capacity_sysctl(void) { cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "topology/cpu-capacity", cpu_capacity_sysctl_add, cpu_capacity_sysctl_remove);
return 0; }
subsys_initcall(register_cpu_capacity_sysctl);
|