ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

嵌入式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只列出一个 processortop里第二个核永远是 0.0% idle——这是很多嵌入式 Linux 系统移植项目里最常见的开局。问题不在驱动也不在文件系统而是内核根本没把第二个核拉起来。SMPSymmetric 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 一致性的关键部件是 SCUSnoop Control Unit。它监听各核的 cache 访问保证一个核改了数据其他核的 cache 里对应行会失效或更新。移植时如果 SCU 没使能多核跑起来会出现数据错乱——这种 bug 极难定位因为单核测试完全正常。所以smp_prepare_cpus里第一件事往往就是scu_enable。2.2 三阶段启动从自解压到 start_kernelBootloader 完成寄存器、外设、存储器映射和堆栈初始化后通过函数指针携带三个参数跳进内核r00r1机器 IDr2标签列表或设备树在内存中的首地址。内核启动分三个阶段第一阶段是自解压执行arch/arm/boot/compressed/head.S的start标号调用decompress_kernel()把压缩镜像解开。因为解压后的内核可能覆盖当前运行代码所以要解压到zImage 堆栈 0x10000这段空间的末尾。第二阶段是处理器相关代码入口在arch/arm/kernel/head.S的stext段。此时 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. 用设备树描述 SMPcpus 节点与 dtb 编译3.1 为什么 ARM 要引入设备树早期 ARM Linux 的arch/arm/plat-xxx和arch/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; cpu0 { device_type cpu; compatible arm,cortex-a9; reg 0; clocks clkc 0; }; cpu1 { device_type cpu; compatible arm,cortex-a9; reg 1; clocks clkc 0; }; };#address-cells 1表示cpus子节点的reg是一个 32 位无符号整数#size-cells 0表示只分配单独地址、不带大小字段。所以cpu0和cpu1的reg分别是 0 和 1对应两个核的编号。compatible arm,cortex-a9让设备和 cortex-a9 相关驱动绑定。核数要和实际 SoC 一致写多了内核会去唤醒不存在的核卡在超时。3.3 编译 dtb 并挂进 MakefileDTS 要编译成 dtb 二进制才能被内核解析。在arch/arm/boot/dts/Makefile里加一行dtb-$(CONFIG_ARCH_XXX) xxx.dtb选中ARCH_XXX目标板后在内核根目录执行make dtbs ARCHarm CROSS_COMPILEarm-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_START和MACHINE_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只要命中这个列表这组初始化函数就会被执行。.smp把xxx_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_cpus用scu_get_core_count从 SCU 寄存器读出实际核数逐个set_cpu_possible标记为可用。xxx_smp_prepare_cpus使能 SCU这是多核 cache 一致性的前提。xxx_boot_secondary从xxx_secondary_startup唤醒次核注意virt_to_phys转换——次核此刻 MMU 没开只能认物理地址。4.3 headsmp.S 里的次核入口xxx_secondary_startup定义在arch/arm/mach-xxx/headsmp.SENTRY(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-ProcessingMaximum number of CPUs设为 2和 dtb 里的核数一致按需选Multi-core scheduler support、Support cpu topology definition在 Boot options 下选中Flattened Device Tree support。配置完保存退出。5.2 编译 uImagemake uImage ARCHarm CROSS_COMPILEarm-linux- LOADADDR0x80008000LOADADDR是内核加载地址由具体 SoC 决定填错会导致解压后跳转地址不对。生成物在arch/arm/boot/uImage。5.3 下载与启动把xxx.dtb和uImage通过 Bootloader 下到 RAMtftpboot 0x80000100 xxx.dtb fdt addr 0x80000100 tftpboot 0x80008000 uImage bootm 0x80008000 - 0x80000100fdt 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_cpus里scu_get_core_count读出来的值对不对。注意Allow booting SMP kernel on uniprocessor systems这个选项在调试阶段可以开方便单核先跑通再上多核但量产配置里一般关掉。6. 多核跑起来之后的排错与调优技巧多核能启动只是第一步真正难的是跑稳。几个我踩过的点值得单独说。次核卡在 IDLE 不动。现象是cat /proc/cpuinfo有两个核但top里第二个核长期 0%。先确认smp_boot_secondary传的是物理地址再确认xxx_secondary_startup里v7_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 或锁上比盲目加打印高效得多。本文还有配套的精品资源点击获取
返回列表