[toc]
/drivers/base/cacheinfo.c:Linux 内核 CPU 缓存信息探测
介绍
/drivers/base/cacheinfo.c 是 Linux 内核驱动核心(driver core)的一部分,其主要功能是探测处理器的缓存信息,并通过 sysfs 文件系统将这些信息导出到用户空间。 这使得用户和应用程序能够方便地获取关于 CPU 缓存层级、大小、关联性等详细信息,而无需编写复杂的底层代码。该机制的设计旨在提供一个统一和通用的接口,以适应不同处理器架构的差异。
历史与背景
为了解决什么特定问题而诞生?
在 cacheinfo.c 通用框架出现之前,不同的处理器架构(如 x86, PowerPC, IA-64, S390)在 Linux 内核中拥有各自独立的缓存信息探测和导出实现。 这种碎片化的实现方式导致了代码冗余,并且为新的架构(如 ARM 和 ARM64)添加支持时带来了不便。因此,引入一个通用的 cacheinfo 框架是为了解决以下问题:
- 代码重复:消除不同架构下功能相似的代码。
- 缺乏统一接口:为用户空间工具和应用程序提供一个稳定、一致的方式来查询缓存信息。
- 可移植性差:简化向新处理器架构移植缓存信息探测功能的过程。
cacheinfo.c 的诞生,标志着 Linux 内核在处理器拓扑结构信息管理上向更通用、更标准化的方向发展。
发展经历了哪些重要的里程碑或版本迭代?
drivers/base/cacheinfo.c 的演进是 Linux 内核逐步统一和抽象硬件信息过程的一部分。
- 早期实现:最初,缓存信息的探测和导出逻辑散布在各个特定架构的代码中,例如
arch/x86/kernel/cpu/intel_cacheinfo.c。 - 通用框架的提出:为了解决代码冗余和接口不一的问题,社区提出了将缓存信息支持通用化的想法,类似于已有的 CPU 拓扑信息支持。
- v5 补丁集(2014年):由 Sudeep Holla 提交的补丁集是
cacheinfo.c通用化的一个重要里程碑。 这个补丁集基于 x86 的实现,创建了一个通用的cacheinfo基础设施,并将当时已有的 x86, ia64, powerpc 和 s390 架构的实现迁移到这个新框架下。同时,该补丁集也为 ARM 和 ARM64 架构添加了缓存信息支持。 - 后续完善:自通用框架建立以来,
cacheinfo.c不断进行着维护和完善,以支持新的处理器特性和固件接口(如 ACPI 和设备树)。
目前该技术的社区活跃度和主流应用情况如何?
cacheinfo.c 作为 Linux 内核中一个成熟且基础的功能,其代码相对稳定。社区的活跃度主要体现在为新出现的处理器架构和特性提供支持,以及在固件接口(ACPI, Device Tree)解析方面的持续改进。
该技术被主流 Linux 发行版广泛采用,是许多系统信息查看和性能分析工具的基础。例如,lscpu 命令以及一些性能剖析工具会利用 sysfs 中由 cacheinfo.c 导出的信息来展示处理器的缓存布局。
核心原理与设计
它的核心工作原理是什么?
cacheinfo.c 的核心工作原理可以概括为以下几个步骤:
- 初始化:在内核启动过程中,当 CPU 设备被添加到系统时,
cacheinfo子系统会为每个逻辑 CPU 进行初始化。 - 信息获取:它会调用特定于具体架构的函数来获取缓存信息。这些函数通过不同的方式获取信息:
- 硬件指令:在 x86 架构上,通常使用
CPUID指令来获取缓存的详细参数。 - 专用寄存器:在 ARM 架构上,则会读取像
CCSIDR,CLIDR,CSSELR等专用寄存器。 - 固件接口:通过解析设备树(Device Tree)或 ACPI 表来获取无法由 CPU 核心直接探测到的信息,例如多核之间共享的缓存(如 L3 缓存)的拓扑结构。
- 硬件指令:在 x86 架构上,通常使用
- 数据结构填充:获取到的缓存信息会被填充到内核的
struct cacheinfo数据结构中。每个缓存层级(L1, L2, L3 等)和类型(数据缓存, 指令缓存, 统一缓存)都会作为一个独立的 “leaf” 来描述。 - sysfs 接口导出:最后,内核会通过 sysfs 在
/sys/devices/system/cpu/cpuX/cache/目录下创建相应的目录和文件,将struct cacheinfo中的信息以文件的形式展现给用户空间。 例如,level,size,type,shared_cpu_map等文件。
它的主要优势体现在哪些方面?
- 统一性与标准化:为所有支持的处理器架构提供了一个统一的、标准的接口来访问缓存信息,简化了用户空间工具的开发。
- 通用性与可扩展性:基于驱动核心(driver core)的设计使其易于扩展,能够方便地为新的处理器架构添加支持。
- 无需特殊权限:用户可以通过标准的文件系统接口读取 sysfs 中的信息,而不需要特殊的权限或执行特权指令。
- 信息相对全面:能够结合硬件探测和固件信息,提供包括缓存大小、级别、类型、行大小、关联方式以及共享 CPU 列表等在内的丰富信息。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 静态信息:
cacheinfo.c提供的是处理器的物理缓存属性,这些是静态信息。它无法提供动态的缓存使用情况,如缓存命中率、未命中率等性能指标。 - 依赖于架构和固件的实现:导出信息的准确性和完整性高度依赖于底层特定架构代码的实现质量,以及固件(BIOS/UEFI, 设备树)所提供信息的准确性。如果固件信息不完整或不正确,
cacheinfo导出的信息也可能出错。 - 虚拟化环境下的限制:在虚拟机中,
cacheinfo.c显示的是虚拟机监控器(Hypervisor)暴露给客户机(Guest OS)的缓存信息,这可能与物理硬件的实际情况不符。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。
cacheinfo.c 是获取 CPU 物理缓存布局信息的首选方案。具体场景包括:
- 性能优化和分析:开发者和系统管理员可以根据缓存的大小和结构来调整其应用程序的数据结构和访问模式,以提高缓存命中率,从而提升程序性能。例如,了解 L1 数据缓存的大小有助于确定最优的循环分块大小。
- 系统信息和监控工具:需要展示系统硬件信息的工具,如
lscpu、hwloc等,都会读取 sysfs 中的缓存信息来向用户报告 CPU 的详细规格。 - 任务调度和资源绑定:在高性能计算(HPC)场景下,调度器可以利用
shared_cpu_map等信息来决定如何将进程或线程绑定到特定的 CPU 核心,以最大化地利用共享缓存,或避免不同任务在共享缓存上的相互干扰。 - 教学与研究:在操作系统和计算机体系结构的学习和研究中,
cacheinfo.c提供的接口是一个直观、便捷的工具,用于观察和理解不同处理器的缓存层次结构。
是否有不推荐使用该技术的场景?为什么?
不推荐单独使用 cacheinfo.c 导出的信息来进行动态的性能瓶颈分析。
- 分析缓存命中/未命中率:如前所述,
cacheinfo.c只提供静态的硬件参数。要分析程序运行时的缓存效率,应该使用性能剖析工具,如perf。perf可以利用处理器的性能监控单元(PMU)来直接测量缓存命中、未命中等硬件事件。 - 实时性能监控:对于需要实时监控缓存使用情况的场景,同样应该依赖
perf等能够提供动态性能计数器的工具。
对比分析
请将其 与 其他相似技术 进行详细对比。
| 特性 | /drivers/base/cacheinfo.c (通过 sysfs) |
perf 工具 |
CPUID 指令 (x86) / 专用寄存器 |
|---|---|---|---|
| 实现方式 | 内核驱动,读取硬件/固件信息并导出到 sysfs 文件系统。 | 内核子系统,利用处理器的性能监控单元(PMU)来采样或计数硬件事件。 | 直接在用户空间或内核空间执行特定的 CPU 指令或访问 MSR。 |
| 信息类型 | 静态的物理缓存属性(大小、级别、关联性、共享关系等)。 | 动态的性能事件(缓存命中数、未命中数、访存延迟等)。 | 静态的物理缓存属性,与 sysfs 类似,但需要自行解析。 |
| 性能开销 | 极低。仅在读取 sysfs 文件时有少量开销。 | 较低(采样模式)到中等(计数模式)。持续监控会对系统产生一定开销。 | 极低。单次指令执行的开销可忽略不计。 |
| 资源占用 | 极低。在 sysfs 中表现为一些文本文件。 | 采样模式下会产生数据文件,可能占用较大磁盘空间。 | 几乎没有额外的资源占用。 |
| 隔离级别 | 用户态可直接访问,无需特殊权限。 | 通常需要 root 权限来访问系统范围的性能事件。 | 在用户态执行 CPUID 通常受限,可能需要内核模块或特殊权限。 |
| 启动速度 | 信息在系统启动时生成,读取速度快。 | 启动和附加到进程需要一定时间。 | 指令执行速度极快。 |
| 易用性 | 非常高。简单的文件读写操作即可获取信息。 | 较高。命令行工具,功能强大但参数较多。 | 低。需要编写汇编或特定的 C 代码,并要处理不同 CPU 型号的差异。 |
总结
请为我总结其关键特性,并提供学习该技术的要点建议。
关键特性总结:
- 核心功能:作为 Linux 内核的一部分,负责探测 CPU 缓存信息并将其通过 sysfs 导出到用户空间。
- 统一接口:为用户空间提供了一个跨架构的、标准的、基于文件的接口来查询静态的 CPU 缓存属性。
- 信息全面:提供包括缓存级别、大小、类型、行大小、关联方式和 CPU 共享关系在内的详细信息。
- 实现通用:采用通用的驱动框架,简化了对新处理器架构的支持。
学习该技术的要点建议:
- 理解 sysfs:首先要熟悉 Linux 的 sysfs 文件系统,理解其作为内核与用户空间交互桥梁的角色。亲手去浏览
/sys/devices/system/cpu/目录,特别是cpuX/cache/子目录,查看其中的文件和内容。 - 实践操作:使用
cat命令读取level,size,type,coherency_line_size,ways_of_associativity,shared_cpu_list等文件,并将这些信息与lscpu命令的输出进行比对,以建立直观的认识。 - 区分静态与动态信息:明确
cacheinfo提供的是静态的硬件参数,而perf等工具提供的是动态的性能数据。理解两者的应用场景和界限是关键。 - 内核源码阅读(进阶):如果想深入理解其工作原理,可以阅读
drivers/base/cacheinfo.c的源码,并结合某一特定架构的实现代码(如arch/x86/kernel/cpu/cacheinfo.c)来理解信息获取的完整流程。这将有助于理解内核如何与硬件和固件进行交互。
核心数据结构与缓存共享判断 (get_cpu_cacheinfo, cache_leaves_are_shared, last_level_cache_is_valid, last_level_cache_is_shared)
核心功能
该部分代码定义了用于存储每个CPU缓存信息的关键数据结构 cpu_cacheinfo,并提供了一系列宏和函数来访问这些数据。它还实现了用于判断不同CPU之间或同一CPU的不同缓存层级之间是否存在共享关系的核心逻辑。get_cpu_cacheinfo 用于获取指定CPU的缓存信息结构体指针。cache_leaves_are_shared 判断两个缓存条目(leaf)是否属于同一个物理缓存。last_level_cache_is_valid 和 last_level_cache_is_shared 则专门用于检查并判断末级缓存(LLC)的有效性和共享状态。
实现原理分析
cache_leaves_are_shared 函数的实现包含了两种策略。在不使用设备树(DT)或ACPI的系统中,它采用一种简化的假设:仅L1缓存是CPU独占的,所有其他级别的缓存(L2, L3等)都被视为系统范围内所有核心共享。在现代嵌入式系统中,通常使用DT或ACPI。此时,该函数依赖于固件提供的信息来判断共享关系。它优先比较 id 字段(如果 CACHE_ID 属性被设置),这是一个唯一的缓存标识符。如果 id 不可用,它会比较 fw_token。fw_token 通常被设置为指向设备树中描述该缓存的设备节点 (device_node) 的指针。如果两个缓存条目的 fw_token 相同,意味着它们由设备树中的同一个节点描述,因此它们必然是同一个物理缓存的不同端口或视图,即它们是共享的。这种通过比较固件节点指针来确定硬件共享性的方法是一种高效且可靠的技巧。
特定场景分析 (单核、无MMU的 STM32H750)
在STM32H750这样的单核(single-core)平台上,关于多CPU之间缓存共享的逻辑实际上是被简化或无效化的。
- 单核行为: 由于系统中只有一个核心(CPU0),
cache_shared_cpu_map(后续代码将分析) 这个位掩码中将永远只设置第0位。函数last_level_cache_is_shared(cpu_x, cpu_y)如果被调用,其参数cpu_x和cpu_y必然都为0,因此其判断失去了“多核间共享”的意义,仅仅是检查自身LLC的有效性。整个共享判断机制在此场景下主要用于正确识别和关联由设备树定义的、属于这唯一核心的各级缓存。 - 无MMU影响: 无MMU(内存管理单元)的设定主要影响虚拟内存和物理内存的映射关系。此部分代码专注于通过固件(设备树)接口读取硬件的拓扑结构和物理属性(如大小、行大小、关联度),不涉及内存地址的转换。因此,MMU的存在与否对
cacheinfo子系统的这部分逻辑没有直接影响。 - 设备树依赖: 在STM32H750平台上,
cacheinfo的所有信息来源将是设备树(Device Tree)。CONFIG_OF必须被使能。代码中cache_leaves_are_shared函数将执行sib_leaf->fw_token == this_leaf->fw_token的判断逻辑。因此,STM32H750的设备树文件(.dts) 中对缓存节点的正确描述是该功能能够正常工作的根本前提。
源码及逐行注释
1 |
|
从设备树 (OF) 解析缓存属性 (init_of_cache_level, cache_setup_of_node)
核心功能
该部分代码实现了从设备树中发现、计数并解析缓存节点属性的功能。init_of_cache_level 遍历与一个CPU关联的缓存层级链(通过 next-level-cache 属性链接),计算出总的缓存级别数(num_levels)和缓存条目数(num_leaves)。cache_setup_of_node 则负责再次遍历这个链条,并对每一个缓存节点,调用辅助函数(如 cache_size, cache_get_line_size, cache_nr_sets 等)来读取具体的硬件参数(如大小、行大小、组数),计算关联度,并为共享缓存分配一个唯一的ID,最终将这些信息填充到对应CPU的 cacheinfo 结构体中。
实现原理分析
缓存拓扑遍历: 内核通过
of_find_next_cache_node函数在设备树中遍历缓存层级。这个遍历的起点是CPU节点本身(可能描述L1缓存),然后通过节点中的next-level-cache属性(一个指向下一个缓存节点的句柄,即phandle)来找到L2缓存节点,再从L2找到L3,以此类推,形成一个缓存拓扑链。缓存属性读取: 代码定义了一个
cache_type_info结构体数组,用于映射不同缓存类型(统一、指令、数据)到设备树中不同的属性名(例如,统一缓存的大小属性是 “cache-size”,指令缓存是 “i-cache-size”)。这种结构化的方式使得代码可以灵活地处理不同类型的缓存。关联度的计算: 缓存的关联度(ways of associativity)不是直接从设备树读取的,而是通过一个重要的数学公式计算得出:
关联度 = (缓存总大小 / 缓存组数) / 缓存行大小
即ways_of_associativity = (size / number_of_sets) / coherency_line_size。
这个公式基于缓存组织的基本原理:缓存总大小 = 组数 × 每组的行数(即关联度) × 行大小。通过读取总大小、组数和行大小,就可以反推出关联度。这是一个典型的利用硬件基本原理来推导高级参数的技巧。共享缓存ID的确定:
cache_of_set_id函数实现了一个为共享缓存确定唯一ID的机制。它会遍历系统中所有的CPU,检查每个CPU是否与当前的缓存节点相关联(通过match_cache_node)。对于所有共享此缓存的CPU,它会获取它们的硬件ID(hwid),并选择其中最小的那个ID作为这个共享缓存的唯一标识符。这确保了一个共享物理缓存,无论从哪个核心的角度去看,都获得相同的、稳定的ID。
特定场景分析 (单核、无MMU的 STM32H750)
在STM32H750平台上,这部分代码的行为是确定且至关重要的。
设备树是唯一信息源: 由于没有ACPI,
of_have_populated_dt()会返回真,因此系统将完全依赖这部分代码来获取缓存信息。STM32H750的设备树文件(.dts)必须精确地描述其缓存结构。例如,CPU节点需要有next-level-cache属性指向描述其L1缓存的节点。如果存在L2缓存,L1缓存节点也需要有next-level-cache指向L2节点。缓存节点描述: Cortex-M7内核通常具有分离的L1指令缓存和数据缓存。因此,在设备树中,CPU节点本身(或其直接关联的L1节点)需要包含类似
i-cache-size和d-cache-size的属性。of_count_cache_leaves函数会根据这些属性的存在来确定L1层级有两个缓存条目(leaves)。单核简化:
cache_of_set_id函数的逻辑虽然会执行,但由于for_each_of_cpu_node循环只会找到一个CPU(CPU0),因此min_id的计算会变得非常简单,最终结果就是CPU0的硬件ID。函数调用流程: 当CPU0上线时,
detect_cache_attributes(后续分析) 会被调用,它会触发init_of_cache_level(0)来确定缓存级别和条目数。然后,cache_setup_of_node(0)会被调用,它将遍历设备树中的缓存节点,并用解析出的属性(大小、行大小、组数等)填充per_cpu(ci_cpu_cacheinfo, 0)这个全局变量。
源码及逐行注释
1 |
|
缓存信息获取与填充总控 (fetch_cache_info, detect_cache_attributes)
核心功能
这组函数是 cacheinfo 子系统的核心调度器。fetch_cache_info 的主要任务是在系统启动早期或CPU热插拔的初始阶段,通过查询固件(ACPI或设备树)或架构特定的方法,来确定一个CPU拥有多少缓存级别(num_levels)和缓存条目(num_leaves),并为此分配核心数据结构 cacheinfo 数组的内存。detect_cache_attributes 则是更完整的初始化入口,它不仅确保了内存的分配,还进一步调用架构相关的函数(populate_cache_leaves)和通用的固件解析函数(cache_shared_cpu_map_setup -> cache_setup_of_node)来彻底填充所有缓存的详细属性,并建立CPU间的共享关系图。
实现原理分析
分阶段初始化: 缓存信息的获取被设计成一个分阶段的过程,以适应不同的系统启动阶段和硬件发现机制。
fetch_cache_info通常在早期被调用,它只做最基础的两件事:确定缓存数量并分配内存。而detect_cache_attributes则在稍晚的阶段(当CPU完全上线时)被调用,进行完整和详细的属性填充。这种设计分离了内存分配和属性填充,增强了系统的灵活性。信息来源的优先级:
fetch_cache_info函数体现了信息来源的优先级策略。它首先尝试ACPI,如果ACPI被禁用或失败,它会调用init_of_cache_level尝试从设备树获取信息。如果两者都失败,它会调用early_cache_level作为一个备选方案,这通常是一个由具体架构(如ARM64, x86)提供的、可能基于寄存器读取的早期探测函数。这种多重后备(fallback)机制确保了在不同配置的系统上都能最大可能地获取到缓存信息。幂等性与热插拔支持:
init_level_allocate_ci函数的设计体现了对幂等性(重复调用无副作用)和CPU热插拔的支持。它首先检查per_cpu_cacheinfo(cpu)是否已经分配了内存。如果已分配,并且不是由“早期”方法分配的,它会直接返回,避免重复工作。这个检查对于CPU下线再上线(hotplug)的场景至关重要,保证了初始化只执行一次。如果发现之前由early_cache_level分配的信息可能不准确,它会调用init_cache_level给予架构代码一个修正的机会,并可能重新分配内存,这增强了系统的健壮性。
特定场景分析 (单核、无MMU的 STM32H750)
对于STM32H750平台,这些总控函数的执行路径是清晰且确定的。
启动流程: 当Linux内核在STM32H750上启动并初始化CPU0时,
cacheinfo_cpu_online(后续分析) 回调函数会被触发。此函数会调用detect_cache_attributes(0)作为主入口。函数调用链:
detect_cache_attributes(0)首先调用init_level_allocate_ci(0)。- 在
init_level_allocate_ci(0)内部,由于per_cpu_cacheinfo(0)此时为NULL,它会调用init_cache_level(0)。在通用的ARM平台上,这通常是一个空的弱符号函数,直接返回。 - 然后,最重要的,它会调用
init_of_cache_level(0)(上一节已分析),该函数将从设备树中读取并计算出num_levels和num_leaves。 - 计算出数量后,
allocate_cache_info(0)被调用,使用kcalloc分配cache_leaves(0)个cacheinfo结构体所需的内存。 - 回到
detect_cache_attributes(0),它接着调用populate_cache_leaves(0)。这同样是一个架构相关的弱符号函数,在STM32H750这种依赖设备树的平台上,它可能只做一些基本的类型和级别填充,具体的属性依赖后续的设备树解析。 - 最后,
detect_cache_attributes(0)调用cache_shared_cpu_map_setup(0)。此函数会发现LLC信息尚未有效,于是调用cache_setup_properties(0),由于of_have_populated_dt()为真,最终会调用cache_setup_of_node(0)(上一节已分析)。正是这一步,真正地从设备树中读取了所有缓存的大小、行大小、组数等详细信息,并填充到刚刚分配的内存中。
总而言之,在STM32H750上,detect_cache_attributes 启动了一个清晰的链式反应:确定数量 -> 分配内存 -> 填充详细属性,而所有信息的最终来源都是设备树。
源码及逐行注释
1 | /** |
缓存共享关系建立与维护 (cache_shared_cpu_map_setup, cache_shared_cpu_map_remove)
核心功能
这组函数的核心任务是构建和拆除每个缓存条目(leaf)的 shared_cpu_map。cache_shared_cpu_map_setup 负责为一个刚刚上线的CPU,遍历其所有的缓存层级,并与系统中其他所有已在线的CPU进行比较。如果发现两个不同CPU的某个缓存条目实际上是同一个物理缓存(通过 cache_leaves_are_shared 判断),它就会在各自的 shared_cpu_map 中记录下对方的存在。cache_shared_cpu_map_remove 则在CPU下线时执行相反的操作,即从所有曾经与它共享缓存的兄弟CPU的映射表中,将自己移除,以保持系统拓扑信息的一致性。
实现原理分析
双重迭代发现机制:
cache_shared_cpu_map_setup的核心是一个双重迭代循环。外层循环 (index) 遍历当前CPU (cpu) 的所有缓存条目。内层循环 (for_each_online_cpu(i)) 遍历系统中所有其他在线的CPU。对于每一对CPU,它会再次遍历对方CPU的缓存条目 (sib_index)。高效剪枝: 在比较之前,代码使用
if (sib_leaf->level != this_leaf->level || sib_leaf->type != this_leaf->type)进行了一次关键的“剪枝”操作。它确保只有在缓存级别和类型都完全相同的情况下才进行后续的共享判断。这极大地减少了不必要的比较次数,例如,一个CPU的L1指令缓存永远不会和一个兄弟CPU的L2统一缓存进行共享比较。对称更新: 当通过
cache_leaves_are_shared确认两个缓存条目是共享的时,代码会执行对称的更新操作:cpumask_set_cpu(cpu, &sib_leaf->shared_cpu_map);和cpumask_set_cpu(i, &this_leaf->shared_cpu_map);。这意味着不仅在兄弟CPU (i) 的缓存条目中记录了当前CPU (cpu),也在当前CPU的条目中记录了兄弟CPU。这种双向记录确保了无论从哪个CPU的角度查询共享关系,都能得到一致的结果。全局参数跟踪: 在建立共享关系的同时,
cache_shared_cpu_map_setup还顺带完成了一个任务:if (this_leaf->coherency_line_size > coherency_max_size)。它会持续跟踪并更新全局变量coherency_max_size,以记录整个系统中所有缓存中最大的缓存行大小。这个值对于DMA操作和内存一致性维护非常重要。
特定场景分析 (单核、无MMU的 STM32H750)
在STM32H750这个单核平台上,这部分用于处理多核共享的复杂逻辑被极大地简化了,其行为变得非常简单和确定。
循环的退化: 核心的
for_each_online_cpu(i)循环只会迭代一次,即i等于cpu(逻辑编号为0)。当进入循环体内部时,if (i == cpu ...)这个条件会立即成立并执行continue。因此,整个用于寻找兄弟核心(sibling CPU)的复杂循环实际上不会执行任何有效的比较操作。最终的
shared_cpu_map: 由于找不到任何“兄弟”核心,唯一对shared_cpu_map起作用的代码行是循环开始前的cpumask_set_cpu(cpu, &this_leaf->shared_cpu_map);。其结果是,对于CPU0的每一个缓存层级(L1i, L1d等),其对应的shared_cpu_map中将只有一个比特位被设置,那就是代表CPU0自己的那一位。cache_shared_cpu_map_remove的行为: 当系统关机,CPU0下线时,cache_shared_cpu_map_remove会被调用。同样,for_each_cpu(sibling, &this_leaf->shared_cpu_map)循环也只会找到CPU0自己,if (sibling == cpu ...)条件会为真,因此不会执行任何清除兄弟映射表的操作。函数的主要作用退化为简单地将this_cpu_ci->cpu_map_populated标志位复位。
综上所述,在单核环境下,这部分代码的功能正确地退化为:为每个缓存层级创建一个只包含其自身的共享映射表,这完全符合单核系统的物理现实。
源码及逐行注释
1 | /** |
Sysfs 接口创建与 CPU 热插拔管理 (cacheinfo_sysfs_init, cacheinfo_cpu_online, cacheinfo_cpu_pre_down)
核心功能
cacheinfo_sysfs_init 是整个 cacheinfo sysfs 功能的入口,它通过 cpuhp_setup_state 函数向内核的CPU热插拔框架注册了两个回调函数:cacheinfo_cpu_online 和 cacheinfo_cpu_pre_down。
当一个CPU成功上线后,cacheinfo_cpu_online 回调函数会被触发。它的职责是:
- 调用
detect_cache_attributes来全面探测并填充该CPU的缓存信息。 - 调用
cache_add_dev在sysfs中为该CPU创建相应的目录结构,如/sys/devices/system/cpu/cpuX/cache/,并在其下为每个缓存条目(leaf)创建子目录indexY。 - 在每个
indexY目录下,创建一系列属性文件(如level,size,type,shared_cpu_map等),将内核中cacheinfo结构体的数据暴露出来。
当一个CPU即将下线时,cacheinfo_cpu_pre_down 回调函数会被触发。它负责执行清理工作:
- 调用
cpu_cache_sysfs_exit来移除sysfs中与该CPU相关的所有缓存目录和文件。 - 调用
free_cache_attributes来更新缓存共享拓扑,将该CPU从中移除。
实现原理分析
设备模型与
sysfs的集成: 代码利用了Linux的设备模型(device model)来创建sysfs条目。cpu_device_create是一个辅助函数,它可以在一个父设备(这里是CPU设备,如cpu0)下创建一个新的子设备。cache_add_dev首先创建了一个顶层的cache目录,然后在其下为每个cacheinfo条目(leaf)创建了一个以indexY命名的设备。属性文件的动态创建:
DEVICE_ATTR_RO宏是一个强大的工具,它能以一种简洁的方式定义一个只读的sysfs属性。例如,static DEVICE_ATTR_RO(level);这一行代码会自动生成一个名为dev_attr_level的device_attribute结构体,并隐式地关联一个名为level_show的函数。当用户cat这个属性文件时,内核会调用对应的_show函数。_show函数的实现: 像level_show这样的函数(通过show_one宏统一定义)的实现模式非常清晰:
a. 通过dev_get_drvdata(dev)从设备结构体中获取与之关联的cacheinfo结构体指针。这个关联是在cpu_device_create时建立的。
b. 访问cacheinfo结构体中对应的成员(如this_leaf->level)。
c. 使用sysfs_emit(一个sprintf的安全封装) 将数据格式化成字符串,并写入用户提供的缓冲区。属性的按需可见性:
cache_default_attrs_is_visible函数是一个非常精巧的设计。它使得sysfs属性文件的创建是动态和条件性的。例如,如果一个缓存在固件中没有提供id,那么this_leaf->attributes & CACHE_ID就为假,is_visible回调就会返回0,内核因此就不会在sysfs中创建id这个文件。这避免了sysfs中出现内容为空或无意义的文件,使得接口更加整洁和健壮。与热插拔框架的集成:
cpuhp_setup_state是将模块功能挂接到CPU生命周期事件中的标准方式。通过注册ONLINE和PRE_DOWN两个状态的回调,cacheinfo子系统实现了其功能的完全动态化,完美支持多核系统中的CPU热插拔操作。
特定场景分析 (单核、无MMU的 STM32H750)
在STM32H750单核平台上,热插拔的动态特性虽然不存在,但这个框架依然是CPU初始化流程的标准组成部分。
启动时的调用: 内核启动过程中,在初始化CPU0时,热插拔框架会模拟一次“上线”事件。这会触发
cacheinfo_cpu_online(0)的调用。sysfs的创建:cacheinfo_cpu_online(0)会执行完整的缓存探测和sysfs创建流程。假设STM32H750的Cortex-M7内核有分离的32KB L1指令缓存和32KB L1数据缓存,那么在sysfs中将会创建如下结构:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/sys/devices/system/cpu/cpu0/cache/
|-- index0/
| |-- level (内容: 1)
| |-- type (内容: Instruction)
| |-- size (内容: 32K)
| |-- coherency_line_size (例如: 64)
| |-- ways_of_associativity (例如: 4)
| |-- number_of_sets (例如: 128)
| `-- shared_cpu_list (内容: 0)
`-- index1/
|-- level (内容: 1)
|-- type (内容: Data)
|-- size (内容: 32K)
|-- coherency_line_size (例如: 64)
|-- ways_of_associativity (例如: 4)
|-- number_of_sets (例如: 128)
`-- shared_cpu_list (内容: 0)shared_cpu_list(或_map) 的内容将只包含CPU 0,这准确地反映了单核系统的拓扑结构。关机时的调用: 在系统关闭(shutdown)流程中,内核会模拟CPU的“下线”事件,这将触发
cacheinfo_cpu_pre_down(0)。该函数会负责将上面创建的所有sysfs目录和文件清理干净。
因此,即使在没有真正热插拔的单核嵌入式系统上,这个基于热插拔框架的设计依然提供了一个标准、健壮且模块化的方式来管理硬件资源的发现和展现。
源码及逐行注释
1 | /* ... (前面的 show_one 宏和 _show 函数定义) ... */ |









