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平台才具备稳定运行和后续热插拔扩展的基础。

RISC-V 多核 Linux 启动与热插拔关系图 BootROM / U-Boot DDR、时钟、串口初始化 设备树 .dtb CPU节点 / 内存 / CLINT / PLIC head.S 入口 页表建立 / 跳转虚拟地址 start_kernel() 调度器 / 中断 / 定时器 Boot Hart (CPU0) 早期初始化 / 解析DTB 发送 IPI 唤醒从核 Secondary Hart (CPU1~N) secondary_start 入口 本地中断 / 页表 / 调度器 CLINT / PLIC msip 寄存器 / 中断路由 enable-method 匹配 IPI 中断注册 CPU 热插拔状态机 ONLINE 任务运行中 OFFLINE 任务迁移 / 中断关闭 cpu_up() 重新走从核启动 cpu_down() cpu_up() 热插拔循环:下线后再次上线 设备树描述硬件 → 内核解析并初始化 → 主核唤醒从核 → 运行时支持 CPU 上下线