news 2026/9/18 14:37:52

嵌入式Linux SMP移植实战:从设备树到多核启动

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式Linux SMP移植实战:从设备树到多核启动

简介:这份PDF文献面向嵌入式Linux系统开发人员与内核移植学习者,聚焦多核处理器(SMP)环境下系统移植的关键技术难题。内容围绕SMP硬件结构、启动流程与设备树机制展开,系统梳理了从硬件分析、设备树构建、内核配置到多核同步、调度器优化及测试验证的完整移植路径,并以Linux-3.17.2内核与ARM平台为例进行剖析,适合具备一定内核基础、希望深入理解对称多处理架构的开发者参考。资源包内含1个PDF文件,大小约3.21MB,为正式期刊论文格式,含摘要、体系结构框图与启动流程图示,便于对照研读。目前已有195人学习下载,可作为嵌入式系统移植方向的专业参考文献与工程实践指导。

1. 从单核到双核:为什么嵌入式 Linux 移植绕不开 SMP

手上有一块 Cortex-A9 双核板子,跑的是 Linux 3.17.2,串口打印里只有 CPU0 在干活,cat /proc/cpuinfo只列出一个 processor,top里第二个核永远是 0.0% idle——这是很多嵌入式 Linux 系统移植项目里最常见的开局。问题不在驱动,也不在文件系统,而是内核根本没把第二个核拉起来。SMP(Symmetric Multi-Processing,对称多处理)要解决的就是这件事:让多个对等的 CPU 核共享同一片内存和 I/O 空间,由内核统一调度,而不是各跑各的。

单核时代靠拉主频提性能,工艺到顶之后这条路走不通了,多核并行成了主流。但嵌入式领域能直接吃下多核架构的操作系统并不多,Linux 因为开源、内核从 2.0 起就持续为 SMP 做改造,成了最现实的选择。这篇围绕 Linux-3.17.2、ARM 平台,把 SMP 移植里真正要动手的部分——设备树怎么描述多核、BSP 里smp_operations怎么填、内核怎么配、镜像怎么下——拆开讲清楚,适合正在做板级移植、或者被"第二个核起不来"卡住的嵌入式 Linux 开发者。

2. SMP 硬件结构与内核启动流程拆解

2.1 SMP 体系结构:共享内存与对称访问

SMP 的逻辑框图里,所有 CPU 核和 I/O 设备挂在同一条总线上,共享一个全局存储空间,任意核访问内存的代价是对称的。这一点和 NUMA 有本质区别:NUMA 里每个核到不同内存区域的延迟不一样,而 SMP 里不存在这种"本地内存"概念。代价是总线带宽会成为瓶颈,核越多,缓存一致性维护的开销越大。

Cortex-A9 MPCore 这类多核 SoC 里,维护数据 cache 一致性的关键部件是 SCU(Snoop Control Unit)。它监听各核的 cache 访问,保证一个核改了数据,其他核的 cache 里对应行会失效或更新。移植时如果 SCU 没使能,多核跑起来会出现数据错乱——这种 bug 极难定位,因为单核测试完全正常。所以smp_prepare_cpus里第一件事往往就是scu_enable

2.2 三阶段启动:从自解压到 start_kernel

Bootloader 完成寄存器、外设、存储器映射和堆栈初始化后,通过函数指针携带三个参数跳进内核:r0=0,r1=机器 ID,r2=标签列表或设备树在内存中的首地址。内核启动分三个阶段:

第一阶段是自解压,执行arch/arm/boot/compressed/head.Sstart标号,调用decompress_kernel()把压缩镜像解开。因为解压后的内核可能覆盖当前运行代码,所以要解压到zImage + 堆栈 0x10000这段空间的末尾。

第二阶段是处理器相关代码,入口在arch/arm/kernel/head.Sstext段。此时 MMU 和 D-cache 必须关闭,代码负责获取处理器类型、建页表、开 MMU,然后跳到start_kernel()

第三阶段是处理器无关代码,从 C 语言的start_kernel开始,初始化所有内核部件。SMP 的关键在于:CPU0 作为主核先启动,初始化全部资源、配好启动页表映射;次核从指定区域启动,只初始化与自身相关的资源,然后进 IDLE 进程,绝不重复初始化 CPU0 已经搞定的东西。

2.3 主从核的唤醒时序

次核的启动不是自动的,得由 CPU0 主动唤醒。流程是:CPU0 在smp_init阶段通过核间中断或写寄存器的方式,向次核发送唤醒信号;次核收到后从secondary_startup进入,先v7_invalidate_l1清掉自己的 L1 cache,再走secondary_startup完成自身 MMU、cache 初始化,最后进 IDLE。这个时序如果错位——比如次核在 CPU0 还没建好页表时就启动——会直接跑飞。所以smp_boot_secondary里传的启动地址必须是物理地址,因为那时次核的 MMU 还没开。

3. 用设备树描述 SMP:cpus 节点与 dtb 编译

3.1 为什么 ARM 要引入设备树

早期 ARM Linux 的arch/arm/plat-xxxarch/arm/mach-xxx目录里塞满了板级细节代码,大量内容只是在描述"这块板子有几个核、中断怎么连",属于硬编码。扁平设备树 FDT 从 PowerPC 体系引入 ARM 后,这些硬件细节可以直接通过设备树传给内核,板级代码大幅精简。对 SMP 移植来说,设备树让"描述多核"变成写几行 DTS,而不是改一堆 C 代码。

3.2 cpus 节点的写法与参数含义

arch/arm/boot/dts/下新建xxx.dts,根节点/下加cpus子节点:

cpus { #address-cells = <1>; #size-cells = <0>; cpu@0 { device_type = "cpu"; compatible = "arm,cortex-a9"; reg = <0>; clocks = <&clkc 0>; }; cpu@1 { device_type = "cpu"; compatible = "arm,cortex-a9"; reg = <1>; clocks = <&clkc 0>; }; };

#address-cells = <1>表示cpus子节点的reg是一个 32 位无符号整数,#size-cells = <0>表示只分配单独地址、不带大小字段。所以cpu@0cpu@1reg分别是 0 和 1,对应两个核的编号。compatible = "arm,cortex-a9"让设备和 cortex-a9 相关驱动绑定。核数要和实际 SoC 一致,写多了内核会去唤醒不存在的核,卡在超时。

3.3 编译 dtb 并挂进 Makefile

DTS 要编译成 dtb 二进制才能被内核解析。在arch/arm/boot/dts/Makefile里加一行:

dtb-$(CONFIG_ARCH_XXX) += xxx.dtb

选中ARCH_XXX目标板后,在内核根目录执行:

make dtbs ARCH=arm CROSS_COMPILE=arm-linux-

生成物在arch/arm/boot/dts/xxx.dtb。这里ARCH指定目标架构,CROSS_COMPILE是交叉工具链前缀,两个参数漏一个都会报找不到编译器或架构不匹配。

提示:dtb 里的核数和内核配置里的CONFIG_NR_CPUS要对得上,前者描述硬件实际有几个核,后者是内核支持的最大核数,配小了多出来的核不会被枚举。

4. BSP 移植:smp_operations 与 platsmp.c 实现

4.1 DT_MACHINE_START 与设备树匹配

引入设备树后,每块板子用DT_MACHINE_STARTMACHINE_END包一组回调。在arch/arm/mach-xxx/board.c里:

DT_MACHINE_START(XXX, "XXX Board") .smp = smp_ops(xxx_smp_ops), .map_io = xxx_map_io, .init_irq = xxx_irq_init, .init_machine = xxx_init_machine, .init_time = xxx_timer_init, .dt_compat = xxx_dt_match, .reserve = xxx_memory_init, .restart = xxx_system_reset, MACHINE_END

.dt_compat是关键:它列出本机器兼容的 root 节点compatible字符串。Bootloader 传进来的设备树 root 节点compatible只要命中这个列表,这组初始化函数就会被执行。.smpxxx_smp_ops挂上去,SMP 的启动逻辑就接进来了。

4.2 smp_operations 四个成员函数

struct smp_operations定义在arch/arm/include/asm/smp.h,四个成员各管一段:

成员函数作用
smp_init_cpus设置 SoC 中 CPU 核的个数
smp_prepare_cpus设置次核的启动地址映射
smp_secondary_init初始化指定核的运行环境
smp_boot_secondary完成指定核的启动

arch/arm/mach-xxx/platsmp.c里实现:

static int xxx_boot_secondary(unsigned int cpu, struct task_struct *idle) { /* 次核 MMU 未开,必须传物理地址 */ return xxx_cpu_start(virt_to_phys(xxx_secondary_startup), cpu); } static void __init xxx_smp_init_cpus(void) { int i, ncores = scu_get_core_count(xxx_scu_base); for (i = 0; i < ncores && i < CONFIG_NR_CPUS; i++) set_cpu_possible(i, true); } static void __init xxx_smp_prepare_cpus(unsigned int max_cpus) { scu_enable(xxx_scu_base); /* 使能 SCU,维护 cache 一致性 */ }

xxx_smp_init_cpusscu_get_core_count从 SCU 寄存器读出实际核数,逐个set_cpu_possible标记为可用。xxx_smp_prepare_cpus使能 SCU,这是多核 cache 一致性的前提。xxx_boot_secondaryxxx_secondary_startup唤醒次核,注意virt_to_phys转换——次核此刻 MMU 没开,只能认物理地址。

4.3 headsmp.S 里的次核入口

xxx_secondary_startup定义在arch/arm/mach-xxx/headsmp.S

ENTRY(xxx_secondary_startup) bl v7_invalidate_l1 b secondary_startup ENDPROC(xxx_secondary_startup)

先清 L1 cache,再跳到通用的secondary_startup。清 cache 这步不能省,否则次核可能带着脏数据进内核,导致随机崩溃。

4.4 Makefile、Kconfig 与 Makefile.boot

arch/arm/mach-xxx/下补三个文件:Makefile决定哪些文件编进内核,Kconfig让这块板子出现在配置菜单里,Makefile.boot指定内核启动的物理起始地址。三个文件缺一个,make menuconfig里就找不到目标板,或者编译时链接不到 BSP 代码。

5. 内核配置、镜像下载与多核验证

5.1 menuconfig 里的 SMP 选项

进内核根目录执行make menuconfig,在 "Kernel Features" 下:

  • 选中Symmetric Multi-Processing
  • Maximum number of CPUs设为 2(和 dtb 里的核数一致)
  • 按需选Multi-core scheduler supportSupport cpu topology definition

在 "Boot options" 下选中Flattened Device Tree support。配置完保存退出。

5.2 编译 uImage

make uImage ARCH=arm CROSS_COMPILE=arm-linux- LOADADDR=0x80008000

LOADADDR是内核加载地址,由具体 SoC 决定,填错会导致解压后跳转地址不对。生成物在arch/arm/boot/uImage

5.3 下载与启动

xxx.dtbuImage通过 Bootloader 下到 RAM:

tftpboot 0x80000100 xxx.dtb fdt addr 0x80000100 tftpboot 0x80008000 uImage bootm 0x80008000 - 0x80000100

fdt addr告诉 Bootloader 设备树在内存的位置,bootm的第三个参数把 dtb 地址传给内核。串口终端能看到内核启动打印。

5.4 验证多核是否真的起来了

启动后执行:

cat /proc/cpuinfo | grep processor

正常应列出 processor 0 和 1。再看dmesg | grep -i smp,会打印 "SMP: Total of 2 processors activated" 之类的信息。如果只有一个核,先查 dtb 里 cpus 节点核数、再查CONFIG_NR_CPUS、最后查smp_init_cpusscu_get_core_count读出来的值对不对。

注意:Allow booting SMP kernel on uniprocessor systems这个选项在调试阶段可以开,方便单核先跑通再上多核,但量产配置里一般关掉。

6. 多核跑起来之后的排错与调优技巧

多核能启动只是第一步,真正难的是跑稳。几个我踩过的点值得单独说。

次核卡在 IDLE 不动。现象是cat /proc/cpuinfo有两个核,但top里第二个核长期 0%。先确认smp_boot_secondary传的是物理地址,再确认xxx_secondary_startupv7_invalidate_l1没被优化掉。如果次核起来了但没进调度,查Multi-core scheduler support是否选中。

随机崩溃、数据错乱。八成是 SCU 没使能或使能时机不对。scu_enable必须在次核启动前调用,放在smp_prepare_cpus里是标准做法。另外确认xxx_scu_base的地址映射正确,映射错会读到垃圾值。

核间中断收不到。SMP 靠 IPI(核间中断)做调度和同步,中断控制器配置不对,次核就收不到唤醒信号。查 GIC 的配置,确认每个核的 CPU interface 都使能了。

性能不升反降。核多了但负载没均衡,或者锁竞争太激烈。用perf top看热点,如果大量时间耗在 spinlock 上,说明临界区太大。常见做法是缩小锁粒度,或者把频繁访问的共享数据改成 per-cpu 变量。

调试手段echo l > /proc/sysrq-trigger能打印所有核的调用栈,卡死时特别有用。/sys/devices/system/cpu/下能看到每个核的在线状态,echo 0 > cpu1/online可以热下线一个核做对比测试,快速判断问题是不是多核引入的。

一个具体技巧:怀疑是 cache 一致性问题时,临时把内核配成单核跑同样的负载。单核正常、多核出错,基本可以锁定在 SCU、cache 或锁上,比盲目加打印高效得多。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 14:37:48

tmux detach 后 Claude Code 接着跑,Key 用 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 14:33:56

服饰部收银员实操手册:SKU、条码与权限的数字化落地

简介&#xff1a;胖东来服饰部收银员实操手册是一份面向新员工与在职收银员的岗位培训演示文稿&#xff0c;系统讲解收银工作全流程。该手册从企业文化理念和人员基本要求切入&#xff0c;逐步覆盖仪容仪表、环境卫生、岗位职责、服务规范、工作流程、特殊情况处理、各类卡票操…

作者头像 李华
网站建设 2026/9/18 14:30:11

【Stable Diffusion】Animatediff V2静态图像生成视频

随着生成式 AI 技术的不断进步,动态效果生成变得更加便捷与高效。AnimateDiff V2 的集成,使得用户可以在 WebUI 中像生成静态图像一样简单地创建动画 GIF。这一创新不仅提升了用户体验,减少了依赖额外工具的需求,同时通过在生成过程中加入运动元素,为原本静态的图像赋予了…

作者头像 李华
网站建设 2026/9/18 14:29:29

物流史PPT重字恢复:从数据清洗到结构化分析

简介&#xff1a;这份物流基础课件以《物流的产生和发展》为主题&#xff0c;适合物流管理专业学生、教师及对现代物流起源感兴趣的自学者使用。PPT 从历史视角梳理物流概念的形成脉络&#xff0c;涵盖 1905 年美国琼西贝克提出军事后勤概念、1915 年阿奇萧在《市场流通中的若干…

作者头像 李华