4. Linux内核与RISC-V支持
在RISC-V多核Linux系统开发中,内核能否正确启动、能否识别板级硬件、能否把从核依次拉起并支持运行时热插拔,决定了整个系统是否具备可调试、可扩展、可量产的工程基础。本章围绕内核启动流程、设备树、平台适配、多核启动与CPU热插拔五个关键点展开,重点说明它们之间的依赖关系与常见落地方法。
一、内核启动流程:从入口到init
RISC-V Linux的启动可以划分为“固件阶段—内核解压与早期初始化—体系结构初始化—驱动与子系统初始化—用户空间”五个层次。上电后,BootROM加载SPL/U-Boot等固件,固件完成DDR、时钟、串口等最基础初始化,随后把内核镜像与设备树地址传给内核。内核入口通常位于arch/riscv/kernel/head.S,此时MMU尚未完全建立,代码运行在物理地址空间,需要先设置页表、开启分页并跳转到虚拟地址空间。
进入C语言阶段后,setup_arch()负责解析设备树、初始化内存节点、建立memblock、识别hart数量与启动hart。随后start_kernel()依次完成调度器、中断控制器、定时器、RCU、工作队列等核心子系统初始化,最后通过kernel_init挂载根文件系统并启动init进程。对开发者而言,串口早期日志、earlycon参数和initcall_debug是定位启动卡死最直接的手段。
# 典型启动日志关键节点
[ 0.000000] Linux version 6.6.0-riscv
[ 0.000000] OF: fdt: Ignoring memory range 0x80000000 - 0x80200000
[ 0.000000] riscv: ISA extensions acdfimsu
[ 0.000000] smp: Brought up 1 node, 4 CPUs
[ 0.210000] Run /sbin/init as init process
二、设备树:硬件描述与平台解耦
设备树(Device Tree)是RISC-V平台适配的核心机制。它把CPU数量、内存布局、中断控制器、时钟、串口、GPIO、外设寄存器基址等硬件信息从内核代码中剥离出来,使同一份内核镜像可以适配不同SoC和板卡。设备树源文件.dts经DTC编译为.dtb,由固件在启动时传递给内核。
一个最小RISC-V设备树通常包含/cpus、/memory、/soc、/chosen等节点。其中/cpus下的每个cpu@N节点通过reg和riscv,isa描述hart编号与指令集扩展;/chosen中的bootargs和stdout-path决定内核命令行与调试串口。多核平台尤其要注意enable-method,它告诉内核如何唤醒从核,常见值为riscv,clint或sifive,fu540-c000。
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
device_type = "cpu";
reg = <0>;
riscv,isa = "rv64imafdc";
enable-method = "riscv,clint";
};
cpu@1 {
device_type = "cpu";
reg = <1>;
riscv,isa = "rv64imafdc";
enable-method = "riscv,clint";
};
};
三、平台适配:从板级文件到驱动匹配
平台适配的本质是“设备树描述 + 驱动匹配 + 平台初始化”。内核启动时,OF(Open Firmware)子系统把设备树展开为device_node,再根据compatible属性与驱动中的of_device_id表进行匹配。匹配成功后调用驱动的probe(),完成寄存器映射、时钟使能、中断注册等操作。
对于RISC-V平台,常见适配工作包括:在arch/riscv/boot/dts/下新增板级DTS;在drivers/中补充或复用外设驱动;在defconfig中打开对应配置项;必要时通过arch/riscv/kernel/下的平台钩子处理复位、电源域或特殊中断路由。下表给出平台适配的检查清单。
| 检查项 | 关键内容 | 常见问题 |
|---|---|---|
| 设备树 | CPU节点、内存、CLINT/PLIC、串口 | hart编号与reg不一致 |
| 驱动匹配 | compatible与of_device_id | 字符串拼写错误导致probe不执行 |
| 时钟与复位 | clocks、resets、power-domains | 外设时钟未使能,读写超时 |
| 中断 | PLIC中断号、interrupt-parent | 中断号偏移错误,无法响应 |
| 内核配置 | defconfig、CONFIG_SMP、CONFIG_HOTPLUG_CPU | 多核或热插拔功能未编译进内核 |
四、多核启动与CPU热插拔
RISC-V多核启动通常由主核(boot hart)完成早期初始化,再通过CLINT的msip寄存器向从核发送IPI,从核在secondary_start_sbi或secondary_start入口被唤醒,随后各自初始化本地中断、页表和调度器,最终进入cpu_startup_entry()。设备树中的enable-method和cpu@N节点必须与硬件实际hart数量一致,否则会出现“只起来一个核”或“从核卡在入口”的现象。
CPU热插拔则是在运行时把某个CPU下线或上线。下线时,内核把该CPU上的任务迁移到其他CPU,关闭本地中断,执行cpu_die()并进入低功耗或等待状态;上线时通过cpu_up()重新走一遍从核启动流程。RISC-V平台需要实现cpu_ops中的cpu_disable、cpu_die、cpu_boot等回调,并确保CLINT、PLIC和定时器在CPU上下线时状态一致。
# 查看与操作CPU热插拔
cat /sys/devices/system/cpu/online
echo 0 > /sys/devices/system/cpu/cpu3/online # 下线CPU3
echo 1 > /sys/devices/system/cpu/cpu3/online # 上线CPU3
dmesg | grep -i "cpu3"
调试多核启动时,优先确认三件事:设备树CPU节点数量是否正确、enable-method是否与固件实现匹配、从核入口地址是否被正确写入。热插拔调试则要关注CONFIG_HOTPLUG_CPU是否开启,以及cpu_ops回调是否完整。
常见误区:把设备树当成“可有可无的配置文件”,直接在驱动里硬编码寄存器地址;或者只改DTS却忘记同步更新defconfig和驱动匹配表。前者导致平台移植困难,后者表现为驱动“看起来写了但probe不执行”。
五、综合案例与复盘
假设一块四核RISC-V开发板启动后只识别到两个CPU,且/sys/devices/system/cpu/下只有cpu0和cpu1。排查顺序应为:先看设备树/cpus下是否真的声明了四个cpu@N节点;再确认enable-method与固件使用的唤醒方式是否一致;然后检查内核配置中CONFIG_SMP和CONFIG_NR_CPUS是否足够;最后通过earlycon观察从核是否进入secondary_start。若从核卡住,往往是CLINT基址、hart ID映射或页表初始化顺序有误。
复盘时建议把“设备树—内核配置—驱动匹配—启动日志—运行时状态”串成一条链:设备树决定硬件描述,内核配置决定功能是否编译,驱动匹配决定外设是否工作,启动日志和/sys、/proc状态则用于验证结果。只有这条链完整闭合,RISC-V多核Linux平台才具备稳定运行和后续热插拔扩展的基础。