ARTICLE DETAIL

资讯详情

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

【linux内核专栏 02】内核启动流程

【linux内核专栏 02】内核启动流程 本篇定位:从上电到用户 shell 跑起来,完整链路。本篇把Linux 启动对你已知启动流程的增量讲清:多了 Bootloader(U-Boot)、内核解压、start_kernel 60 子系统初始化、init 进程、设备树。读完能配启动参数、能调启动死机、能读懂 dmesg。[[07-启动与 bare-metal 流程]] 的 RISC-V 启动链:BootROM → Bootloader → App_start→ main。Linux 启动链几乎一样:BootROM → U-Boot → Kernel_start→start_kernel→ init。增量是:① U-Boot 比你 bootloader 多了加载内核镜像 传设备树 传命令行;②start_kernel比你 main 多了60 子系统初始化;③ 多了 init 进程(用户态祖师)。你的启动经验直接迁移,只学增量。目录一、启动链路总览各级职责分工二、BootROM 阶段三、U-Boot 阶段(嵌入式主力 Bootloader)3.1 U-Boot 做的事3.2 U-Boot 的 bootcmd3.3 U-Boot 传给内核的东西3.4 桌面/服务器的 Bootloader:GRUB四、Kernel 入口(head.S,汇编阶段)4.1 head.S 做的事(RISC-V 版)4.2 对照你 RISC-V 启动 5 件事4.3 内核解压4.4 早期页表与开 MMU五、start_kernel(init/main.c)⭐ 核心5.1 start_kernel 的逻辑分组(1) 早期核心(架构 锁 内存)(2) 调度与中断(3) 时间(4) 控制台与调试(5) 进程上下文(6) 驱动与设备5.2 kernel_init 线程:驱动 挂根 fs 启 init5.3 initcall 机制:驱动初始化的注册表六、设备树解析(嵌入式必懂)6.1 设备树是什么6.2 设备树节点示例6.3 内核怎么用七、内核命令行(cmdline)7.1 cmdline 哪来7.2 常用参数7.3 cmdline 怎么解析八、init 进程:用户态祖师8.1 init 是什么8.2 init 实现流派8.3 嵌入式 BusyBox init8.4 systemd(主流)九、启动调试与死机排查9.1 看 dmesg9.2 启动死机定位9.3 启动死机调试三件套十、多核启动10.1 SMP 启动流程10.2 主核唤醒从核(RISC-V)10.3 maxcpus 调试十一、ARM vs RISC-V vs x86 启动对照十二、本篇小结速查表一、启动链路总览上电 │ ▼ ① BootROM(芯片厂商固化) │ - 硬件复位向量跳这 │ - 最小初始化(时钟/PLL/DDR training) │ - 从 Flash/SD/eMMC 加载下一级到 RAM │ - 跳转 ▼ ② Bootloader(U-Boot / GRUB / Coreboot) │ - 初始化更多外设(串口/网络/存储) │ - 加载内核镜像(zImage/Image)到 RAM │ - 加载设备树(DTB)到 RAM │ - 设置内核命令行(cmdline) │ - 跳到内核入口(stext) ▼ ③ Kernel 入口(stext / head.S) │ - 仍汇编:设 sp/gp/关闭 MMU(若开)/清 bss │ - 解压内核(若是压缩镜像) │ - 初始化页表、开 MMU │ - 跳到 C 代码 start_kernel ▼ ④ start_kernel(init/main.c) │ - 60 子系统初始化(sched/mm/irq/... ) │ - 初始化设备树解析 │ - 初始化驱动(遍历设备树 probe) │ - 挂载根文件系统 │ - 启动 init 进程(第一个用户进程) ▼ ⑤ init(systemd / sysvinit / busybox init) │ - 用户态祖师(PID 1) │ - 读配置(/etc/inittab 或 systemd unit) │ - 启动服务(网络/ssh/login) │ - 启动登录 shell ▼ 用户登录,系统就绪各级职责分工级谁做干什么你的活BootROM芯片厂商复位向量最小初始化加载下一级AEGIX BootROM 你写过U-Boot你/厂商外设初始化加载内核传 DTB/cmdline嵌入式 BSP 常改Kernel head.S内核汇编初始化开 MMU跳 C一般不改,能读懂start_kernel内核子系统初始化驱动 probe挂根 fs调启动死机要看init用户态启动服务systemd 配置二、BootROM 阶段硬件复位向量(地址固定,如 0xFFFF0000 或 0x0)最小硬件初始化:时钟/PLL/DDR training/最少 UART多启动源选择:从 SD/eMMC/NAND/网络/USB 选一个加载下一级(按 GPIO 拨码或 BCR 寄存器)安全启动验证:验签下一级(若使能 secure boot)跳到下一级入口嵌入式视角:MCU BootROM 做的:复位初始化 加载 Bootloader 跳转,和通用 BootROM 一样。Linux 芯片的 BootROM 多了安全启动验签和多源选择,但核心同。三、U-Boot 阶段(嵌入式主力 Bootloader)3.1 U-Boot 做的事第二阶段硬件初始化:串口/网络/存储/DDR 详细配置环境变量管理:bootcmd/bootargs 等(存 env 分区)加载内核镜像:从存储读Image/zImage/uImage到 RAM 指定地址加载设备树 DTB:从存储读.dtb到 RAM构造内核命令行 cmdline:从 bootargs 环境变量跳转:按 ARM/RISC-V 的 calling convention,把 DTB 地址、cmdline、其他参数传给内核入口3.2 U-Boot 的 bootcmd# U-Boot 环境变量示例(嵌入式)bootargsconsolettyS0,115200root/dev/mmcblk0p2 rw rootwait earlyconbootcmdload mmc0:1${kernel_addr_r}Image;\load mmc0:1${fdt_addr_r}board.dtb;\booti${kernel_addr_r}-${fdt_addr_r}bootargs:内核命令行(传给内核的参数)bootcmd:U-Boot 自动执行的命令(加载内核 DTB 跳转)booti:U-Boot 跳到 ARM64/RISC-V 内核的命令3.3 U-Boot 传给内核的东西按 RISC-V/ARM64 的 SBI/boot 协议,通过寄存器传:传什么RISC-V 怎么传ARM64 怎么传内核入口a0hartid, a1DTB 物理地址x0DTB 物理地址cmdline在 DTB 的 /chosen 节点同左RAM 范围DTB 的 /memory 节点同左initrd 地址DTB 的 /chosen 节点同左设备树是 Bootloader 给内核的硬件清单裸机/FreeRTOS:硬件信息写死在代码里(#define UART_BASE 0x40000000)。Linux:硬件信息放设备树 DTB,Bootloader 传给内核,内核运行时解析。这是 Linux 可移植性的关键——同一个内核镜像,配不同 DTB 跑不同板子。你 BSP 改板子,改 DTB 不用改内核(11 篇详讲设备树)。3.4 桌面/服务器的 Bootloader:GRUBx86 桌面/服务器用 GRUB,不做硬件初始化(BIOS/UEFI 已做),只:① 选内核;② 加载内核 initramfs;③ 传 cmdline。和 U-Boot 职责类似,但场景不同。四、Kernel 入口(head.S,汇编阶段)内核镜像入口(_start/stext),在arch/arch/kernel/head.S。这阶段还是汇编,做的事和你 RISC-V 启动汇编 5 件事惊人相似:4.1 head.S 做的事(RISC-V 版)// arch/riscv/kernel/head.S(简化) _start: # 1. 处理 secondary CPU(多核,见 §七) csrr t0, mhartid bnez t0, .Lsecondary_park # 非主核去 park # 2. 保存 DTB 地址(U-Boot 传来的 a1) mv s1, a1 # 3. 关中断、设初始 sp csrw mie, 0 la sp, init_thread_union THREAD_SIZE # 4. 清 BSS la t0, __bss_start la t1, __bss_stop ... # 5. 建立早期页表(只映射内核镜像 DTB) call setup_vm # 6. 开 MMU(satp 写入,TLB flush) call relocate # 切到虚拟地址 # 7. 跳到 C 代码 start_kernel call start_kernel4.2 对照你 RISC-V 启动 5 件事你 ⑤ 件事(裸机)Linux head.S 对应设 sp设 sp(用 init_thread_union)设 gp(防 relaxation)同(gp 在内核也用)拷 .datahead.S 不拷(内核镜像已就位,但可能 relocate)清 .bss清 .bss配 mtvec配 stvec(Linux 在 S 态,用 sstvec)增量:Linux head.S 多了:① 多核主从判断;② 建立 early page table 并开 MMU(Linux 必须用 MMU,你裸机可不开);③ 解压(若压缩镜像);④ relocate(切到虚拟地址)。4.3 内核解压内核镜像常压缩(gzip/xz/lz4),入口先自解压:zImage/bzImage(ARM/x86):有解压头Image(ARM64/RISC-V):常不压缩或独立解压器解压后跳到真入口stext4.4 早期页表与开 MMULinux 必须开 MMU(每进程虚拟地址空间需要)。head.S 建早期页表——只线性映射内核镜像和 DTB,够 start_kernel 早期用。后期paging_init重建完整页表(04 篇详讲)。嵌入式视角:你 RISC-V MMU 知识的落地[[06-内存 PMP-MMU-RVWMO]] 学过 Sv39 三级页表、satp。head.S 建 early page table 就是写 Sv39 页表项 写 satp。你能直接看懂arch/riscv/mm/init.c的setup_vm。五、start_kernel(init/main.c)⭐ 核心start_kernel是内核 C 代码入口,60 子系统初始化在这调用。这是启动流程的重头戏。5.1 start_kernel 的逻辑分组init/main.c的start_kernel()函数,按顺序调用一堆xxx_init()。逻辑分 7 组:(1) 早期核心(架构 锁 内存)setup_arch(command_line);// 架构初始化(解析 DTB, early MMU)setup_command_line();// 保存 cmdlinemm_init_owner();// 内存管理初始化setup_per_cpu_areas();// percpu 变量build_all_zonelists(NULL);// 内存 zone 初始化(buddy 前置)mm_init();// buddy/slab 分配器就绪 ★(2) 调度与中断sched_init();// 调度器初始化 ★(03 篇)early_irq_init();// 中断描述符表init_IRQ();// 中断子系统 ★(05 篇)tick_init();// tick 子系统(08 篇)rcu_init();// RCU ★(07 篇)(3) 时间time_init();// 时钟/clocksource(08 篇)timer_init();// 定时器hrtimers_init();// 高精度定时器(4) 控制台与调试console_init();// 控制台(终于能 printk 到串口)这之前 printk 只缓存(console 没就绪)console_init后,dmesg 才实时输出调启动死机,看卡在 console_init 前还是后(见 §九)(5) 进程上下文fork_init();// fork 机制(03 篇)proc_caches_init();// task_struct 等 slab 缓存buffer_init();// buffer cachevfs_caches_init();// VFS 缓存(12 篇)signals_init();// 信号(6) 驱动与设备rest_init();// 见下start_kernel末尾调rest_init(),里面:rest_init(){// 创建内核线程 kernel_init(PID 1 候选)pidkernel_thread(kernel_init,...);// 创建内核线程 kthreadd(PID 2,内核线程祖师)pidkernel_thread(kthreadd,...);// 当前 CPU 进入 idlecpu_startup_entry(CPUHP_ONLINE);// 跑 idle 循环}5.2 kernel_init 线程:驱动 挂根 fs 启 initkernel_init是 PID 1 候选,做最后阶段:kernel_init(){// 1. 调用所有 do_initcall 级别驱动初始化do_basic_setup();// 跑 initcall 链(device/driver 注册)// 2. 挂载根文件系统if(ramdisk_execute_command)...// 先试 initramfsprepare_namespace();// 挂 root fs(root 参数)// 3. 找 init 程序,跳用户态if(execute_command)run_init_process(execute_command);// 默认找 /sbin/init, /etc/init, /bin/init, /bin/sh}5.3 initcall 机制:驱动初始化的注册表驱动初始化怎么被调用?靠initcall机制:// 驱动代码里module_init(my_driver_init);// 或 device_initcall/late_initcallmodule_init宏把my_driver_init函数指针放进.initcall段(链接脚本安排)。kernel_init的do_initcall遍历这段,逐个调用。initcall 有级别(按顺序):级别宏时机典型pure_initcall最早纯初始化core_initcall核心子系统核心postcore_initcallarch_initcall架构IRQ 控制器subsys_initcall子系统设备模型fs_initcall文件系统device_initcall设备大部分驱动 ★late_initcall最晚收尾initcall 是 Linux 驱动注册的核心机制你写驱动用module_init,在编进内核时它就是device_initcall。start_kernel不直接调你驱动,而是通过 initcall 段间接调。这机制让驱动注册解耦——你加驱动不用改 start_kernel,只要module_init注册即可。09 篇详讲模块/initcall。六、设备树解析(嵌入式必懂)6.1 设备树是什么DTS(Device Tree Source):文本,描述硬件(CPU/内存/外设/中断/时钟)DTC:编译器,DTS → DTB(二进制)DTB(Device Tree Blob):二进制,U-Boot 传给内核的6.2 设备树节点示例/dts-v1/; / { model My Board; compatible myvendor,myboard; cpus { cpu0 { device_type cpu; compatible riscv; reg 0; }; }; memory80000000 { device_type memory; reg 0x80000000 0x10000000; /* 256MB */ }; uart10000000 { compatible myvendor,uart; reg 0x10000000 0x100; interrupts 10; clocks clk 5; }; };6.3 内核怎么用setup_arch解析 DTB,建内部 device tree 结构匹配驱动:uart10000000的compatible myvendor,uart→ 内核找注册了同compatible的驱动 → 触发 probeprobe 时从节点读reg(寄存器地址)/interrupts(中断号)/clocks等嵌入式视角:设备树替代了板级代码老 Linux(2.6 前)每板子一套arch/arm/mach-xxx/board.c硬编码硬件。设备树把硬件描述挪出代码——同内核镜像配不同 DTB 跑不同板。你 BSP 改板子,改 DTS 不改内核,大幅提升可移植性。11 篇详讲设备树写法和驱动匹配。七、内核命令行(cmdline)7.1 cmdline 哪来U-Boot 的bootargs环境变量 → 传给内核 → 存/proc/cmdline。7.2 常用参数参数作用consolettyS0,115200控制台串口root/dev/mmcblk0p2根文件系统设备rootwait等根设备就绪(异步存储)rw根 fs 挂载读写init/sbin/init指定 init 程序earlycon早期控制台(调试用)loglevel8printk 日志级别mem256M限制可用内存maxcpus1限制启动 CPU 数(调试多核)initcall_debug打印每个 initcall 调用(调试启动)norandmaps关闭 ASLR(调试)7.3 cmdline 怎么解析内核用__setup()宏注册参数处理:// 内核某处staticint__initmy_debug_setup(char*str){my_debug1;return1;}__setup(mydebug,my_debug_setup);// cmdline 有 mydebug 则调start_kernel早期parse_early_param()遍历__setup表,匹配 cmdline 调对应 handler。调启动死机用 earlycon initcall_debugearlycon让串口在极早期(console_init 之前)就输出 → 看 head.S/start_kernel 早期卡哪initcall_debug打印每个 initcall 调用 → 看卡在哪个驱动 probe八、init 进程:用户态祖师8.1 init 是什么内核kernel_init最后run_init_process(/sbin/init)跳到用户态init 是 PID 1,所有用户进程的祖先init 死了 → 内核 panic(“Attempted to kill init”)init 职责:读配置,启动服务,管理运行级别8.2 init 实现流派init 实现用途特点systemd主流桌面/服务器并行启动,依赖管理,复杂强大sysvinit传统串行,简单,老嵌入式BusyBox init嵌入式轻量,读 /etc/inittabOpenRCGentoo 等介于 systemd 和 sysvinit8.3 嵌入式 BusyBox init# /etc/inittab(busybox)::sysinit:/etc/init.d/rcS# 系统初始化(跑脚本)::respawn:/sbin/getty115200ttyS0# 串口登录::shutdown:/etc/init.d/rcKsysinit:启动时跑一次(rcS 脚本挂载 fs/起网络/起服务)respawn:进程死了重启(登录 shell)体积小动态链接版本通常几百 KB静态编译版本也多在 1MB 左右非常适合 Flash/内存受限设备。高度可裁剪通过 make menuconfig 选择需要的 applet不用的功能可以关掉。支持交叉编译可以为 ARM、MIPS、RISC-V 等不同架构编译。可当 initBusyBox 自带 init能配合 /etc/inittab 和 /etc/init.d/rcS 启动系统。生态广泛Buildroot、OpenWrt、OpenEmbedded/Yocto、Alpine Linux、Android Root 工具等都大量使用它。BusyBox 是一个开源、轻量级的 Unix/Linux 工具集合被称为 “嵌入式 Linux 的瑞士军刀”。它把 ls、cat、cp、mv、grep、find、mount、telnet、ps、ifconfig、init、sh 等几百个常用命令压缩进一个单一的可执行文件中。类别典型命令用途文件操作ls,cat,cp,mv,rm,tar文件管理、归档文本处理grep,sed,awk,head,tail日志查看、文本过滤系统管理ps,kill,mount,umount,init进程、文件系统、启动网络工具ifconfig,ping,telnet,wget,udhcpc网络配置、调试Shell 环境sh,ash提供命令行交互和脚本执行设备与内核mdev,modprobe,insmod,rmmod设备节点、模块加载和 GNU 工具的区别BusyBox 命令更精简选项更少。GNU coreutils 功能更全但体积更大依赖更多。嵌入式根文件系统里常用 BusyBox桌面/服务器发行版更常用 GNU 工具。由来BusyBox 最初由 Bruce Perens 在 1996 年前后为 Debian GNU/Linux 安装盘编写。当时的目标是在一张 1.4MB 软盘上做出一个可引导的 Linux 系统既能当安装盘也能当急救盘。因为每个 ELF 可执行文件本身都有几 KB 开销如果把 ls、cp、mount、sh 都做成独立程序会浪费不少空间。BusyBox 把这些工具合并成一个二进制共享公共代码从而显著减小体积。后来它从 Debian 安装盘工具逐渐变成嵌入式 Linux、路由器固件、轻量发行版和容器镜像的基础组件。8.4 systemd(主流)并行启动(不串行),启动快unit 文件(.service/.socket/.target)描述服务依赖管理(A 依赖 B,先起 B)systemctl命令管理嵌入式也越来越多用(systemd 可裁剪)嵌入式视角:你 BSP 会改 init 配置你做 BSP,根文件系统常基于 BusyBox 或 Yocto/Buildroot。改启动行为:① BusyBox 改/etc/inittab/etc/init.d/rcS;② systemd 加/改 unit 文件。这是 BSP “让系统跑起来并启动我要的服务” 的活。九、启动调试与死机排查9.1 看 dmesgdmesg# 内核日志缓冲区dmesg|head# 启动早期journalctl-k# systemd 系统的内核日志dmesg 顺序就是启动顺序,看子系统初始化输出。9.2 启动死机定位卡在哪现象排查BootROM无任何输出硬件/电源/复位U-Boot 早期无 U-Boot bannerDDR/时钟/串口U-Boot 加载内核有 banner,卡在 booti内核镜像错/地址错head.S(开 MMU 前)无内核输出earlycon,看汇编卡哪start_kernel 早期(console_init 前)earlycon 有,串口无earlycon 看 initcall_debugstart_kernel(console_init 后)串口有 printk看最后一行,卡在哪个 initcall驱动 probeinitcall_debug 看卡哪个驱动那个驱动的 probe 死循环/死锁挂根 fs“VFS: Cannot open root device”root 参数错/驱动没编/根 fs 坏init“No init found”init 程序缺/C 库缺/架构错9.3 启动死机调试三件套earlycon:极早期串口输出(console_init 前)bootargs... earlyconuart,mmio,0x10000000initcall_debug:打印每个 initcallbootargs... initcall_debug loglevel8kgdb:内核 gdb(若支持),JTAG 类似kgdb 不是独立调试器而是 Linux 内核里的“GDB 远程调试桩”普通 gdb 主要调试用户态程序kgdb 则是把 gdb 的源码级调试能力延伸到内核态。维度普通 gdbkgdb调试对象用户态程序、进程Linux 内核、内核模块运行位置本机运行通常双机Host 跑 gdbTarget 跑带 kgdb 的内核依赖机制ptrace内核 kgdb stub GDB 远程串行协议能否断点内核一般不能可以在内核代码设断点、单步、看变量典型场景应用调试、core 分析驱动调试、内核崩溃、中断/调度/内存问题定位是否需要特殊内核不需要需要CONFIG_KGDB、CONFIG_DEBUG_INFO等为什么叫 kgdbgdb 本身不能直接调试运行中的内核因为内核没有 ptrace 接口而且内核崩溃时用户态工具也无法介入。kgdb 在内核里加入了一个 debug stub当内核命中断点、异常或手动进入调试模式时stub 会接管 CPU把寄存器、栈、内存等信息通过串口/网络发给 gdb。Host: gdb vmlinux | GDB Remote Serial Protocol | Target: Linux kernel kgdb stub和 kdb 的关系kdb 是内核自带的轻量命令行调试器可以直接在目标机上看寄存器、内存、调用栈但不是源码级调试器kgdb 才是配合 gdb 做源码级断点、单步、变量查看的内核调试前端。嵌入式场景里怎么用做驱动或 BSP 调试时常见组合是QEMU/KVM 双机调试或者串口 kgdboc。这样不用改硬件就能在宿主机上用 gdb 单步跟踪目标板内核比反复加 printk 效率高很多。你死机五步法的 Linux 版[[10-调试与性能计数器]] 的死机五步(mepc/mcause/mtval/栈/addr2line)。Linux 启动死机版:① 看最后一行 dmesg(卡哪);② earlycon 看更早;③ initcall_debug 看哪个 initcall;④ kgdb/JTAG 断点;⑤ 那个驱动/子系统单读。方法论同,工具换 Linux 的。十、多核启动10.1 SMP 启动流程主核(BSP, Bootstrap Processor): BootROM → U-Boot → head.S → start_kernel → 跑完 → 启 init 从核(AP, Application Processors): BootROM: 上电进 park(WFI) 主核 start_kernel 后期:发 IPI(核间中断)/SBI 调用唤醒从核 从核醒来:跳 secondary_entry → secondary_start_kernel → 进 idle10.2 主核唤醒从核(RISC-V)// arch/riscv/kernel/smp.c(简化)// 主核写从核的启动地址 发 IPIfor_each_present_cpu(cpu){if(cpusmp_processor_id())continue;// SBI 调用:让 SBI(M 态)唤醒从核,跳到 secondary_entrysbi_send_ipi_single(cpu);}从核入口secondary_entry(汇编)→secondary_start_kernel©:设自己的 sp/tp(percpu)开中断进调度器 idle10.3 maxcpus 调试cmdlinemaxcpus1只起主核,排除多核问题。嵌入式视角:[[07-启动与 bare-metal 流程]] 多核启动:mhartid 判主从,从核 park,主核唤醒。Linux SMP 启动同构:主核跑 start_kernel,从核 park,主核后期唤醒从核。增量是:① 唤醒用 SBI 调用(经 M 态)而非直接 MSIP;② 从核醒来要建自己 percpu 数据、进调度器。你已懂多核启动,Linux 是放大版。十一、ARM vs RISC-V vs x86 启动对照ARM(嵌入式)RISC-Vx86BootROM芯厂商固化芯厂商固化BIOS/UEFIBootloaderU-BootU-BootGRUB/systemd-boot内核入口head.S(stext)head.S(_start)startup_32/startup_64DTB 传法r2 寄存器(32位)/x0(64位)a1 寄存器ACPI 表(不用 DTB)多核唤醒PSCI(SMC 调用)SBI HSM 扩展SIPI/INIT早期控制台earlyconearlyconearlyprintkRISC-V Linux 用 SBI,Linux 不在 M 态关键:RISC-V Linux 内核在 S 态,M 态给OpenSBI(SBI 实现)。内核要操作 M 态资源(时钟/复位/关机/多核唤醒),走 SBI 调用(ecall to M),不直接碰 M 寄存器。这和裸机你在 M 态直接写 mtimecmp 不同。你 RISC-V 专题学的 M 态 CSR,在 Linux 里多由 SBI 管,内核不直接碰。十二、本篇小结启动链路:BootROM → U-Boot → head.S → start_kernel → initU-Boot 增量:加载内核 加载 DTB 传 cmdlinehead.S(汇编):多核判断/清 bss/建早期页表/开 MMU/跳 C——和你 RISC-V 启动 5 件事同构,增量是开 MMUstart_kernel:60 子系统初始化,逻辑 7 组(架构/内存/调度中断/时间/控制台/进程上下文/驱动设备)rest_init建 kernel_init(PID1 候选) kthreadd(PID2 内核线程祖师) idleinitcall 机制:驱动注册module_init→.initcall段 →do_initcall遍历调用,分级别设备树 DTB:Bootloader 传的硬件清单,内核解析匹配驱动,替代板级代码cmdline:U-Boot bootargs 传,__setup注册 handler;earlycon/initcall_debug 调试init 进程:PID1 用户态祖师,systemd(主流)/BusyBox init(嵌入式)多核启动:主核跑 start_kernel,从核 park,主核后期 SBI/IPI 唤醒RISC-V Linux 在 S 态,M 态给 OpenSBI,内核走 SBI 调用操作 M 资源速查表想干啥怎么做看启动日志dmesg / journalctl -k看内核命令行cat /proc/cmdline调启动死机(早期)earlycon loglevel8调驱动 probe 卡死initcall_debug只起一核调试maxcpus1看卡在哪个 initcallinitcall_debug 看最后打印挂根 fs 失败检查 root/ 驱动是否编进/根 fs 是否坏init 找不到检查 init/sbin/init 程序和 C 库改启动行为(BusyBox)/etc/inittab /etc/init.d/rcS改启动行为(systemd)systemctl enable/disable unit 文件看 DTB 内容dtc -I dtb -O dts board.dtbRISC-V 内核入口arch/riscv/kernel/head.Sstart_kernel 位置init/main.c技术之路漫漫分享是为了更好地交流。如果本文的内容对你有启发希望能得到你的点赞 和收藏 ⭐。如果你在调试过程中遇到了其他问题欢迎在评论区 留言我们一起探讨。也欢迎关注 我一起交流底层开发的那些事儿。
返回列表