news 2026/2/9 23:52:13

[Linux]学习笔记系列 -- [drivers][base]topology

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
[Linux]学习笔记系列 -- [drivers][base]topology

title: topology
categories:

  • linux
  • drivers
  • base
    tags:
  • linux
  • drivers
  • base
    abbrlink: 1198610c
    date: 2025-10-03 09:01:49

https://github.com/wdfk-prog/linux-study

文章目录

  • driver/base/topology.c CPU拓扑(CPU Topology) 通过sysfs导出CPU物理布局与亲和性信息
    • 实现原理分析
    • 特定场景分析:单核、无MMU的STM32H750平台
      • 单一拓扑结构
      • CPU热插拔框架的静态应用
      • 内存分配 (无MMU影响)
      • 实际意义
    • 核心宏定义:用于生成sysfs回调函数
    • 拓扑属性实例化:通过宏定义具体的sysfs文件接口
    • 属性分组与可见性控制
    • CPU热插拔回调与初始化
    • CPU计算能力(Capacity)相关实现

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回调函数

// 宏定义,用于生成一个名为 name##_show 的sysfs属性读取函数。// @name: 属性的名称,例如 physical_package_id。// @fmt: 输出格式化字符串,例如 "%d"。// 这个宏会创建一个函数,该函数调用 topology_##name(dev->id) 获取CPU的拓扑ID,并使用sysfs_emit格式化输出到buf中。#definedefine_id_show_func(name,fmt)\staticssize_tname##_show(structdevice*dev,\structdevice_attribute*attr,char*buf)\{\returnsysfs_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掩码。#definedefine_siblings_read_func(name,mask)\staticssize_tname##_read(structfile*file,structkobject*kobj,\conststructbin_attribute*attr,char*buf,\loff_toff,size_tcount)\{\structdevice*dev=kobj_to_dev(kobj);\cpumask_var_tmask;\ssize_tn;\\/* 动态分配一个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);\\returnn;\}\\staticssize_tname##_list_read(structfile*file,structkobject*kobj,\conststructbin_attribute*attr,char*buf,\loff_toff,size_tcount)\{\structdevice*dev=kobj_to_dev(kobj);\cpumask_var_tmask;\ssize_tn;\\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);\\returnn;\}

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

// 使用宏生成 physical_package_id_show 函数。define_id_show_func(physical_package_id,"%d");// 定义一个只读的设备属性 physical_package_id,并将其与上面的show函数关联。staticDEVICE_ATTR_RO(physical_package_id);// 如果定义了TOPOLOGY_DIE_SYSFS,则创建 die_id 相关属性。#ifdefTOPOLOGY_DIE_SYSFSdefine_id_show_func(die_id,"%d");staticDEVICE_ATTR_RO(die_id);#endif// 如果定义了TOPOLOGY_CLUSTER_SYSFS,则创建 cluster_id 相关属性。#ifdefTOPOLOGY_CLUSTER_SYSFSdefine_id_show_func(cluster_id,"%d");staticDEVICE_ATTR_RO(cluster_id);#endif// 创建 core_id 相关属性。在STM32H750上,其值恒为0。define_id_show_func(core_id,"%d");staticDEVICE_ATTR_RO(core_id);// 创建 ppin (Processor Proximity Identification Number) 相关属性。define_id_show_func(ppin,"0x%llx");staticDEVICE_ATTR_ADMIN_RO(ppin);// 仅管理员可读。// 使用宏生成 thread_siblings_read 和 thread_siblings_list_read 函数。// 在STM32H750上,线程兄弟就是CPU 0自身。define_siblings_read_func(thread_siblings,sibling_cpumask);// 定义名为 thread_siblings 的只读二进制属性,用于以位图格式显示。staticconstBIN_ATTR_RO(thread_siblings,CPUMAP_FILE_MAX_BYTES);// 定义名为 thread_siblings_list 的只读二进制属性,用于以列表格式显示。staticconstBIN_ATTR_RO(thread_siblings_list,CPULIST_FILE_MAX_BYTES);// 定义 core_cpus 和 core_cpus_list 属性,表示同一核心下的所有CPU(线程)。define_siblings_read_func(core_cpus,sibling_cpumask);staticconstBIN_ATTR_RO(core_cpus,CPUMAP_FILE_MAX_BYTES);staticconstBIN_ATTR_RO(core_cpus_list,CPULIST_FILE_MAX_BYTES);// 定义 core_siblings 和 core_siblings_list 属性,表示同一物理封装下所有核心的CPU。define_siblings_read_func(core_siblings,core_cpumask);staticconstBIN_ATTR_RO(core_siblings,CPUMAP_FILE_MAX_BYTES);staticconstBIN_ATTR_RO(core_siblings_list,CPULIST_FILE_MAX_BYTES);// ... 其他类似属性的定义 ...#ifdefTOPOLOGY_CLUSTER_SYSFSdefine_siblings_read_func(cluster_cpus,cluster_cpumask);staticconstBIN_ATTR_RO(cluster_cpus,CPUMAP_FILE_MAX_BYTES);staticconstBIN_ATTR_RO(cluster_cpus_list,CPULIST_FILE_MAX_BYTES);#endif#ifdefTOPOLOGY_DIE_SYSFSdefine_siblings_read_func(die_cpus,die_cpumask);staticconstBIN_ATTR_RO(die_cpus,CPUMAP_FILE_MAX_BYTES);staticconstBIN_ATTR_RO(die_cpus_list,CPULIST_FILE_MAX_BYTES);#endifdefine_siblings_read_func(package_cpus,core_cpumask);staticconstBIN_ATTR_RO(package_cpus,CPUMAP_FILE_MAX_BYTES);staticconstBIN_ATTR_RO(package_cpus_list,CPULIST_FILE_MAX_BYTES);#ifdefTOPOLOGY_BOOK_SYSFSdefine_id_show_func(book_id,"%d");staticDEVICE_ATTR_RO(book_id);define_siblings_read_func(book_siblings,book_cpumask);staticconstBIN_ATTR_RO(book_siblings,CPUMAP_FILE_MAX_BYTES);staticconstBIN_ATTR_RO(book_siblings_list,CPULIST_FILE_MAX_BYTES);#endif#ifdefTOPOLOGY_DRAWER_SYSFSdefine_id_show_func(drawer_id,"%d");staticDEVICE_ATTR_RO(drawer_id);define_siblings_read_func(drawer_siblings,drawer_cpumask);staticconstBIN_ATTR_RO(drawer_siblings,CPUMAP_FILE_MAX_BYTES);staticconstBIN_ATTR_RO(drawer_siblings_list,CPULIST_FILE_MAX_BYTES);#endif

属性分组与可见性控制

// 定义一个包含所有二进制属性指针的数组,用于属性组的创建。staticconststructbin_attribute*constbin_attrs[]={&bin_attr_core_cpus,&bin_attr_core_cpus_list,// ... 其他二进制属性 ...NULL// 数组以NULL结尾。};// 定义一个包含所有默认文本属性指针的数组。staticstructattribute*default_attrs[]={&dev_attr_physical_package_id.attr,// ... 其他文本属性 ...NULL// 数组以NULL结尾。};// topology_is_visible: is_visible回调函数,用于动态决定某个属性文件是否应该在sysfs中可见。staticumode_ttopology_is_visible(structkobject*kobj,structattribute*attr,intunused){// 如果属性是 ppin 并且底层硬件不支持(返回0),则文件不可见。if(attr==&dev_attr_ppin.attr&&!topology_ppin(kobj_to_dev(kobj)->id))return0;// 返回0表示文件不可见。returnattr->mode;// 否则返回属性默认的模式(权限)。}// 定义topology属性组,它包含了上面定义的文本属性和二进制属性。staticconststructattribute_grouptopology_attr_group={.attrs=default_attrs,.bin_attrs=bin_attrs,.is_visible=topology_is_visible,.name="topology"// 在sysfs中创建的子目录名。};

CPU热插拔回调与初始化

// topology_add_dev: CPU上线回调函数,为指定的CPU设备添加topology sysfs接口。staticinttopology_add_dev(unsignedintcpu){// 获取指定CPU号的device结构体指针。structdevice*dev=get_cpu_device(cpu);// 在该设备的kobject下创建topology属性组。returnsysfs_create_group(&dev->kobj,&topology_attr_group);}// topology_remove_dev: CPU下线回调函数,为指定的CPU设备移除topology sysfs接口。staticinttopology_remove_dev(unsignedintcpu){structdevice*dev=get_cpu_device(cpu);// 从该设备的kobject下移除topology属性组。sysfs_remove_group(&dev->kobj,&topology_attr_group);return0;}// topology_sysfs_init: topology sysfs接口的模块初始化函数。staticint__inittopology_sysfs_init(void){// 注册CPU热插拔状态的回调函数。// 当CPU准备就绪时调用topology_add_dev,当CPU被移除前调用topology_remove_dev。// 在STM32H750上,这会在启动时为CPU 0调用一次topology_add_dev。returncpuhp_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)相关实现

// 为每个CPU定义一个名为cpu_scale的变量,并初始化为调度器容量的默认值。// 在STM32H750上,这只会实例化一个变量,即per_cpu(cpu_scale, 0)。DEFINE_PER_CPU(unsignedlong,cpu_scale)=SCHED_CAPACITY_SCALE;// 导出该per-cpu变量符号,以便其他模块可以使用。EXPORT_PER_CPU_SYMBOL_GPL(cpu_scale);// topology_set_cpu_scale: 设置函数,用于更新指定CPU的计算能力(scale)值。voidtopology_set_cpu_scale(unsignedintcpu,unsignedlongcapacity){per_cpu(cpu_scale,cpu)=capacity;}// cpu_capacity_show: cpu_capacity属性的show函数,用于在sysfs中显示CPU的计算能力。staticssize_tcpu_capacity_show(structdevice*dev,structdevice_attribute*attr,char*buf){structcpu*cpu=container_of(dev,structcpu,dev);returnsysfs_emit(buf,"%lu\n",topology_get_cpu_scale(cpu->dev.id));}// 定义一个名为cpu_capacity的只读设备属性。staticDEVICE_ATTR_RO(cpu_capacity);// cpu_capacity_sysctl_add: CPU上线回调,为指定CPU添加cpu_capacity的sysfs文件。staticintcpu_capacity_sysctl_add(unsignedintcpu){structdevice*cpu_dev=get_cpu_device(cpu);if(!cpu_dev)return-ENOENT;// 在CPU设备下创建 cpu_capacity 文件。device_create_file(cpu_dev,&dev_attr_cpu_capacity);return0;}// cpu_capacity_sysctl_remove: CPU下线回调,为指定CPU移除cpu_capacity的sysfs文件。staticintcpu_capacity_sysctl_remove(unsignedintcpu){structdevice*cpu_dev=get_cpu_device(cpu);if(!cpu_dev)return-ENOENT;// 从CPU设备下移除 cpu_capacity 文件。device_remove_file(cpu_dev,&dev_attr_cpu_capacity);return0;}// register_cpu_capacity_sysctl: 初始化函数,注册用于创建cpu_capacity sysfs文件的热插拔回调。staticintregister_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);return0;}// 使用subsys_initcall宏在子系统初始化阶段调用注册函数。subsys_initcall(register_cpu_capacity_sysctl);
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/2 14:21:05

真实项目落地案例:基于IndexTTS-2的智能播报系统搭建教程

真实项目落地案例:基于IndexTTS-2的智能播报系统搭建教程 1. 引言:为什么需要一个工业级语音播报系统? 在很多实际业务场景中,我们都需要把文字自动变成自然流畅的语音。比如商场的广播通知、物流配送的提醒播报、教育平台的有声…

作者头像 李华
网站建设 2026/2/8 20:18:22

Linux 针对 MySQL 专用服务器的 OOM 预防策略配置

对于只运行 MySQL 的服务器,如果触发 OOM,无论怎样设置,数据库进程被杀死几乎是必然的。这是因为: 为什么 MySQL 总是首当其冲?内存占用最大 在专用 MySQL 服务器上,MySQL 通常占用 80-99% 的物理内存&…

作者头像 李华
网站建设 2026/2/7 21:11:31

YOLOv12官版镜像上线!立即体验注意力驱动的检测黑科技

YOLOv12官版镜像上线!立即体验注意力驱动的检测黑科技 在自动驾驶系统识别行人与障碍物的关键瞬间,传统目标检测模型还在逐层提取特征时,YOLOv12已经凭借注意力机制完成了对复杂场景的全局理解——这不是未来构想,而是今天就能实…

作者头像 李华
网站建设 2026/2/5 20:13:53

Qwen1.5-0.5B输入长度限制:长文本分块处理教程

Qwen1.5-0.5B输入长度限制:长文本分块处理教程 1. 为什么0.5B模型也要关心输入长度? 你可能已经试过直接把一篇2000字的用户反馈、一份3页的产品需求文档,或者一段密密麻麻的会议纪要丢给Qwen1.5-0.5B——结果不是卡在加载,就是…

作者头像 李华
网站建设 2026/2/6 2:30:05

Qwen3-4B怎么快速调用?网页推理访问保姆级操作指南

Qwen3-4B怎么快速调用?网页推理访问保姆级操作指南 1. 认识Qwen3-4B-Instruct-2507:不只是一个文本生成模型 你可能已经听说过Qwen3-4B,但这次的 Qwen3-4B-Instruct-2507 版本,是阿里开源体系中一次实实在在的升级。它不是简单地…

作者头像 李华
网站建设 2026/2/8 5:03:06

DeepSeek-R1-Distill-Qwen-1.5B降本方案:GPU按需计费节省50%费用

DeepSeek-R1-Distill-Qwen-1.5B降本方案:GPU按需计费节省50%费用 1. 为什么小模型也能撑起生产服务? 你可能已经注意到,现在越来越多团队在用1.5B参数量的模型做真实业务——不是测试,不是Demo,而是每天处理上百次用…

作者头像 李华