久久派龙芯k平台内核交叉编译与安全升级实践
2026/9/9 5:23:50 网站建设 项目流程

上一篇把板子点亮之后,后台一直有朋友催更:出厂内核用着能用,但总觉得不踏实,想自己编译一版内核,又不知道从哪下手;还有人问,设备树改了之后要不要重新烧整个系统?这一篇就专门填这些坑。主题很明确:基于手头这块龙芯k平台的久久派,把交叉编译内核的完整流程走通,同时把升级动作拆到“即使失败也不会变砖”的安全范围内。

这篇笔记默认你已经完成了上篇的基础工作:主机上能通过串口登录板子,板子能正常进入Linux,TF卡或eMMC上有可用的根文件系统。如果你还在“点不亮”阶段,先回头把串口和启动介质搞定,否则后面的一切都没有载体。这篇文章适合两类人:刚拿到久久派、想尽快进入驱动开发或外设调试的入门者,以及用过其他嵌入式板子、想转到龙芯k平台上来的老手。

1. 动手之前,先弄明白内核升级到底在做什么

1.1 为什么不能一直用出厂内核

出厂内核最大的问题是“不可控”。板厂为了方便演示,通常会把大量驱动编进内核,配置保守、功能冗余,跑起来占用高不说,有些外设明明板子上没有,驱动却还在加载;反过来,真到了你想接一个自己设计的传感器、想改一个引脚复用时,出厂内核里往往没有对应的配置项,或者设备树里根本没留节点。

我自己的体会是:拿到一块新开发板,前两周用出厂内核完全没问题,但第三周开始你一定会有自己编译内核的冲动。原因不外乎这几类:

  • 需要开启某个内核配置,但出厂内核没有打开,比如某些网络协议、文件系统支持。
  • 需要修改设备树里的引脚功能、外设节点,不改内核就没法生效。
  • 需要打上游补丁或做内核版本升级,用新特性或修复已知问题。
  • 内核出问题时需要开调试信息、加打印,快速定位是驱动问题还是硬件问题。

无论是哪种,逻辑上最终都会走到同一步:拿到官方或板厂提供的内核源码,用交叉编译工具链编出一个适合自己板子的内核,然后把旧内核换掉。这一步跨过去,后续所有和硬件打交道的开发都会顺畅很多。

1.2 从源码到板端,完整的工作流长什么样

在写第一行命令之前,先在脑子里把整个流程走一遍。我的经验是:嵌入式开发里,绝大部分“编译不过”“启动卡死”都是因为对整个链条理解不完整,而不是某一条命令打错了。

一次常规的内核升级,完整链路可以拆成这样:

  1. 主机上准备交叉编译工具链,确认架构和板子匹配。
  2. 获取内核源码,切到板级对应的分支或 tag。
  3. 用默认配置或自定义配置生成 .config。
  4. 用 make 交叉编译内核镜像和设备树 dtb。
  5. 编译内核模块,并安装到根文件系统的对应目录。
  6. 把新内核镜像、dtb 备份后传到板子的启动分区。
  7. 重启板子,确认新内核启动成功,验证外设功能。

这个链路里,第 1 到 5 步都在主机上做,第 6 到 7 步才真正碰到开发板。换句话说,哪怕内核编出来有问题,在烧进去之前你都还有补救空间;一旦到了第 6 步,就要像拆炸弹一样小心了。我在后面的章节里会把每一步容易踩的坑单独拎出来说。

1.3 版本与分支规划,先想清楚再动手

很多新手最容易犯的错是:不管三七二十一,从内核官网拉一个 latest stable 就开始编。编完才发现,板子的 Bootloader 不认这个格式,或者某个外设驱动在主线内核上根本没适配。

龙芯k平台的内核开发和普通 ARM 板还不太一样。它虽然也走 Linux 主线,但很多板级支持代码、设备树、BSP 驱动可能在厂商的维护分支上才够完整。我的建议是:

  • 第一优先:使用板厂/龙芯 SDK 里配套的内核源码版本。这个版本和出厂固件、Bootloader 配合最稳定。
  • 第二优先:使用龙芯开源社区维护的 loongarch 内核分支,但要注意看板级 defconfig 和 dts 是否覆盖你的板子。
  • 第三优先:自己尝试上游主线,适合想折腾新特性、有能力自己适配设备树的老手,新手不推荐从这里起步。

怎么确认当前板子上跑的内核版本?登录板子执行:

uname -a cat /proc/version

记下这个版本号,后面选源码分支就对着它来。比如当前跑的是 5.10.x,那在源码仓库里优先找 5.10 对应的分支或 tag,而不是直接跳到 6.x。要记住:内核升级讲究的是“稳”,不是“新”。

2. 编译环境补充与源码准备

2.1 主机依赖,缺什么补什么

上篇我们已经在主机上装过一部分开发工具,但内核编译对主机环境有额外要求,尤其是老一点的 Ubuntu/Debian 系统,很可能缺几个关键包。先执行一遍,缺啥补啥,省得编译到一半报错再回头装:

sudo apt update sudo apt install -y git make gcc flex bison bc \ libssl-dev libncurses-dev libelf-dev \ device-tree-compiler u-boot-tools

逐个解释一下这些包是干嘛的:

  • flex、bison:内核的 Kconfig 和 DTS 解析需要用到词法/语法分析器,生成解析代码。缺了它们,make menuconfig 那一步就可能直接报错。
  • bc:内核编译脚本里有用到计算器处理数值,少了会报“bc: not found”。
  • libssl-dev:编译内核时需要生成签名证书相关代码,老版本内核对这个依赖比较强。
  • libncurses-dev:menuconfig 的图形界面依赖它,不装的话 make menuconfig 起不来。
  • device-tree-compiler:提供 dtc 工具,编译设备树必须用它。
  • u-boot-tools:提供 mkimage,用于制作 U-Boot 认识的镜像格式,后面打包内核时会用到。

这一套装完,主机层面的依赖基本就齐了。如果你用的是 Fedora/Arch 这类发行版,包名略有差异,按对应包管理器搜一下即可。

2.2 获取内核源码,先对版本再谈编译

获取源码的路径取决于你手上板子的 BSP 来源。常见的有三种:

  • 龙芯官方或板厂提供的 SDK 压缩包,解压后里面就有 kernel 目录。
  • Git 仓库,可能是龙芯开源社区的 loongarch 内核仓库,也可能是板厂自己的仓库。
  • 上游 kernel.org 主线仓库,但这种适合做移植,新手慎选。

以 Git 仓库为例,拉取时可以加--depth=1只拉最新提交,省时间也省空间。但如果要切到某个历史 tag 或分支,还是建议完整拉取,否则后面切分支会遇到对象缺失的问题。

git clone https://github.com/loongson/linux.git cd linux git branch -a git tag | grep 5.10 | tail

看到分支列表之后,选一个和你当前系统版本最接近的分支,比如:

git checkout -b my-kernel-5.10 origin/loongarch-5.10

这里必须强调一点:别急着开编。先花 10 分钟确认源码目录下有arch/loongarch目录,并且arch/loongarch/configs/里能找到一个可用的板级 defconfig 文件。如果这两个条件不满足,说明这份源码根本不支持龙芯k平台,后面全是白干。

2.3 交叉编译环境变量,一次性配好

交叉编译和本地编译最大的区别在于:需要在编译命令里告诉 make 三个关键信息:目标架构是什么、编译器前缀是什么、编译器路径在哪。我习惯写成一个环境变量脚本,放到自己的工作目录下,每次开新终端直接 source 一下,不用重复敲:

# 放在 env.sh 里 export ARCH=loongarch export CROSS_COMPILE=loongarch64-linux-gnu- export PATH=/opt/loongarch64-toolchain/bin:$PATH

其中:

  • ARCH=loongarch:告诉内核 Makefile 使用 loongarch 架构的代码目录。
  • CROSS_COMPILE=loongarch64-linux-gnu-:指定交叉编译器前缀。实际编译时 make 会去找loongarch64-linux-gnu-gccloongarch64-linux-gnu-ld这些工具。
  • PATH里的路径要根据你的交叉工具链实际解压位置调整,我这边放在/opt/loongarch64-toolchain,你按实际情况改。

配好后先验证工具链可用:

loongarch64-linux-gnu-gcc -v

如果能正常打印版本信息,说明工具链没问题。这里有一个小坑,我提示一下:网上有些教程会让你直接export CROSS_COMPILE=loongarch64-linux-gnu-但前面的PATH没配好,结果系统里找不到这个命令,报错还特别隐蔽。所以 verify 一步不能省。

3. 内核配置:从默认配置到你的裁剪清单

3.1 找到适合久久派的默认配置

内核源码拿到手之后不要急着 menuconfig,而是先找板级的默认配置。在 ARM 平台这叫xxx_defconfig,在 loongarch 平台上通常也会有一批类似文件放在arch/loongarch/configs/下面。

找配置文件有个最笨也最有效的方法:

ls arch/loongarch/configs/ grep -ri "久久派\|2k\|龙芯k" arch/loongarch/configs/ arch/loongarch/boot/dts/

如果你的板厂比较讲究,配置文件名里就会直接体现板子型号;如果找不到,也可以用出厂内核自带的配置来生成。做法是登录板子,把/proc/config.gz拷出来(前提是内核开启了 CONFIG_IKCONFIG),然后放到源码目录下解压成.config

# 板子上执行 zcat /proc/config.gz > kernel.config # 把这个文件传到主机,放到内核源码目录 cp kernel.config .config

这个方法相当于“继承”了出厂内核的全部配置,再在小范围内做调整,对新手来说是最可靠的上手方式。当然,前提是源码版本要和出厂内核版本接近,否则旧 config 里的很多选项在新源码里可能已经被移除了。

3.2 menuconfig 里应该关注哪些选项

拿到初始配置后,执行:

make menuconfig

这个命令会打开一个终端图形界面,本质上就是在编辑.config文件。新手进去之后容易迷失在几千个选项里,我建议第一批只需要关注下面几个方向:

  • 确认板级平台相关选项被正确选中。通常在 Device Drivers -> Character devices 或 Platform specific devices 下面,不同源码组织方式差异很大,建议直接搜索关键字。
  • 文件系统。确认你根文件系统用的 ext4、squashfs、overlayfs 等都被编进内核或编译为模块。
  • 串口驱动。必须确认你调试用的串口控制器驱动被编译进去。如果编成模块,内核启动早期还没有根文件系统挂载,模块加载不了,你就永远看不到启动日志。
  • 网络驱动。如果后面要 SSH 登录或 NFS 启动,确认网卡驱动开启。
  • initramfs/initrd 相关选项。取决于你的启动方案。

在 menuconfig 里按/可以直接搜索关键字,非常方便。比如想知道某个配置项在哪,搜索后按数字键就能跳过去。

3.3 裁剪原则:模块化优于全部编译

说到裁剪,很多人的第一反应是“尽量把不用的都编成模块,能省内存”。这个思路本身没错,但在实际开发阶段我建议反过来:核心启动路径上能编进内核的就不要编成模块。

原因很简单:模块是在根文件系统挂载之后才加载的,如果根文件系统本身有问题,或者模块版本和内核不匹配,设备就会启动到一半就“消失”。把串口、存储控制器、根文件系统相关驱动都编进内核,等于给系统留了一条最稳的保底路径。

一旦系统能稳定启动,再慢慢把可以模块化的部分拆出来。我在实际开发中见过太多人一上来就把驱动全模块化,结果 rootfs 里的模块路径没配对,板子卡在挂载根文件系统的错误上,排查半天才发现是驱动没起来。这属于典型的“优化过早反受其害”。

3.4 保存配置,记录你的每一次变更

配置改完之后,menuconfig 默认保存到.config。但.config这个名字太通用,不方便做版本管理。我习惯在每次确认能编译通过、能正常启动后,把配置备份一份:

cp .config arch/loongarch/configs/my_jingjiupai_defconfig

下次想重新编译或者分享给别人,直接:

make my_jingjiupai_defconfig

就能恢复到你保存的那个状态。这相当于给你的内核配置做了一个快照,非常实用。因为内核的配置项有依赖关系,如果哪次手滑在 menuconfig 里误删了某个选项,导致依赖它的配置全部消失,恢复起来会非常痛苦,有快照就能一键回到稳定版本。

4. 编译内核与模块,产出一套能启动的组合

4.1 make 命令怎么敲才省心

配置确认无误后,开始编译。完整的命令是:

make -j$(nproc) Image dtbs modules

逐段解释:

  • -j$(nproc):并行编译,核数越多越快。但如果你主机的内存偏小(不到 8GB),建议手动限制并发数,比如-j4,否则内存被吃满,编译进程卡死甚至触发 OOM。
  • Image:生成内核镜像。在 loongarch 架构里,最终产物是arch/loongarch/boot/Image,这是一个未经压缩的 ELF 镜像;有些场景还会生成Image.gz,需要压缩时用。
  • dtbs:编译全部设备树二进制。
  • modules:把配置里标记为<M>的驱动编译成内核模块。

先编这三个目标,一次搞定。第一次编译时间会比较长,我这边在普通笔记本上全速编译大概用了 10 到 15 分钟,中途没有任何报错输出其实是很正常的,别以为卡死了。

如果中间报错,不要盲目重试,先看报错信息。大部分问题集中在:

  • 工具链版本老,不支持当前内核用到的某些语法。
  • 缺少头文件或依赖库。
  • 源码目录不干净,之前编过其他架构的产物混在里面。

针对最后一种情况,执行一次彻底清理再重新来:

make mrproper make my_jingjiupai_defconfig make -j$(nproc) Image dtbs modules

make mrproper会把之前所有编译产物和 .config 一起清掉,是“从零再来”的标准动作。

4.2 编译产物到底在哪,各有什么用途

编译完成后,有一个动作很多新手会忽略:先别急着部署,花几分钟梳理一下产物。内核编译的产物分散在不同的目录,打包时搞混很容易启动失败。

主要产物及位置如下表:

文件路径作用
内核镜像arch/loongarch/boot/Image 或 Image.gz真正被 Bootloader 引导的内核
设备树文件arch/loongarch/boot/dts/ 下各厂商目录描述板级硬件信息,外设初始化靠它
vmlinux源码根目录带符号的未压缩内核,主要用于调试
内核模块drivers/ 和 sound/ 等目录下的 .ko 文件按需加载的外设驱动等
Module.symvers源码根目录模块符号表,外部模块编译时需要引用

这里特别说一下vmlinux。如果你只打算跑板子,vmlinux没用;但如果你想用 kgdb 调试内核或者看 panic 栈的符号,这个文件就是唯一宝贝。建议每次编译后把它保留到单独的目录,别清理掉。

4.3 编译 KBuild 外部模块,驱动开发的前置练习

编译完内核主体后,再准备一个外部模块的编译环境。平时写设备驱动基本都是外部模块的方式,不需要每次改一行驱动就重编一遍整个内核。

先在内核源码目录下确认Module.symvers存在,它是内核导出符号的列表。然后随便建一个测试模块目录:

// hello.c #include <linux/module.h> #include <linux/kernel.h> static int __init hello_init(void) { printk(KERN_INFO "hello from jingjiupai\n"); return 0; } static void __exit hello_exit(void) { printk(KERN_INFO "goodbye\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");

配套的 Makefile:

obj-m := hello.o KERNEL_DIR ?= /path/to/your/kernel/source PWD := $(shell pwd) all: $(MAKE) ARCH=loongarch CROSS_COMPILE=loongarch64-linux-gnu- -C $(KERNEL_DIR) M=$(PWD) modules clean: $(MAKE) ARCH=loongarch CROSS_COMPILE=loongarch64-linux-gnu- -C $(KERNEL_DIR) M=$(PWD) clean

然后执行:

make

如果配置正确,目录下会多出hello.ko。把这个文件拷到板子上,insmod hello.ko,再用dmesg | tail就能看到打印。这一整套流程跑通,说明你的交叉编译环境已经具备了驱动开发的基本能力。

4.4 编译内核模块后,怎么处理模块的部署问题

内核镜像编好只是第一步,如果.config里有大量模块,那根文件系统里必须对应有/lib/modules/<内核版本>/目录,否则那些动态加载的驱动都不会生效。

最标准的做法是:

# 先安装到一个临时目录 make ARCH=loongarch CROSS_COMPILE=loongarch64-linux-gnu- \ INSTALL_MOD_PATH=/tmp/rootfs modules_install

INSTALL_MOD_PATH指定根文件系统的挂载点或目录。执行后会在/tmp/rootfs/lib/modules/下生成与内核版本同名的目录,里面是modules.depmodules.alias等文件以及所有.ko。然后把整个lib/modules/内核版本目录拷贝到板子的根文件系统对应位置:

scp -r /tmp/rootfs/lib/modules/<内核版本> root@<板子IP>:/lib/modules/

拷贝完成后,在板子上执行:

depmod -a

这一步会重新生成模块依赖关系。很多新手跳过了 depmod,直接 insmod 某些模块,明明文件还在却报 module not found,就是因为依赖关系文件没更新。

5. 设备树修改,把内核和真实硬件对齐

5.1 设备树文件去哪找,改哪个

内核编译完之后,如果板子上的某个外设没工作,大概率不是内核代码的问题,而是设备树节点没配好。设备树描述的是“硬件长什么样、分布在哪些地址、用哪个中断、引脚怎么复用”,本质上是一份硬件配置清单。

先找到板子对应的 dts 文件。常见路径是arch/loongarch/boot/dts/loongson/或类似目录。如果你不确定,用搜索命令:

find arch/loongarch/boot/dts -name "*.dts" | xargs grep -l "model ="

板级 dts 通常会 include 一个 SoC 通用的 dtsi 文件,dtsi 里定义的是芯片内部外设,dts 里才定义“这块板子上到底启用了哪些外设、接到了哪个引脚”。

修改的原则是:能改板级 dts 的就不动 SoC 的 dtsi。因为 dtsi 是公用的,动了它会影响同一芯片下的所有板子。

5.2 新增一个 GPIO 控制节点的实例

举个例子。如果久久派上有一颗 LED 灯接在某个 GPIO 上,你想在设备树里给驱动留一个节点,可以在板级 dts 中追加类似内容:

/ { leds { compatible = "gpio-leds"; status = "okay"; user_led { label = "user-led"; gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>; linux,default-trigger = "heartbeat"; }; }; };

这个片段在根节点下新建了一个gpio-leds节点,告诉内核:第一个 GPIO 控制器的第 12 号引脚上接了 LED,默认触发方式是心跳闪烁。

但要注意:gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>里的 12 号引脚不能乱写。必须先查板子的原理图或者芯片手册,确认这个 LED 到底挂在哪个控制器、哪个引脚上。我曾经踩过一个坑:以为写 12 号引脚就行,结果这个引脚在硬件上直接被复用成了其他功能,驱动加载后不仅灯没亮,还影响了另一个外设的工作。

5.3 引脚复用配置,最容易被忽视的细节

外设要正常工作,光有设备树节点还不够,对应的引脚必须先被配置成正确的复用功能。

大多数 SoC 都有 pinmux(引脚复用)机制。一个物理引脚可能有七八种功能,比如既能做 GPIO,也能做 UART 的 TX,还能做 PWM 输出。用哪个功能,需要在设备树的 pinctrl 节点里指定。

比如启用某个板载串口时,dtsi 里可能会有类似这样的片段:

&uart1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart1_pins>; };

这里pinctrl-0指向一个预设好的引脚配置组,配置组里包含了引脚号、功能编号、上下拉等电气属性。如果你新接的外设恰好和某个引脚冲突,就可能会导致两个设备节点争抢同一个引脚,编译不会报错,但运行时表现非常诡异:一会儿能用,一会儿不能用。

所以每次改完设备树之后,强烈建议用下面命令反编译生成的 dtb,检查实际生效的引脚配置:

dtc -I dtb -O dts -o decompiled.dts arch/loongarch/boot/dts/xxx.dtb

decompiled.dts和源码里的 dts 对比一下,确认改动真实落到了产物里,而不是因为 include 顺序问题被覆盖了。

5.4 设备树改完怎么验证,先别急着上板

设备树是在内核启动早期就要解析的东西,如果设备树写错了,轻则某个外设不工作,重则内核在初始化阶段就 panic,而且日志可能只闪一两行,你根本来不及看。

我的习惯是先用 qemu 或至少用dtbs_check这类检查工具做一次静态验证。如果环境里没有 qemu,那就退而求其次,在 dtc 反编译后仔细核对语法和节点路径。

一个非常实用的技巧是:编译完 dtb 后,在板子的 U-Boot 里用命令把设备树加载到内存并打印:

load mmc 0:1 0x90000000 /boot/xxx.dtb fdt addr 0x90000000 fdt print /soc/gpio@xxx

fdt print可以确认 Bootloader 实际读入的设备树内容和你预期一致。这一步能排查掉 80% 的“改了没生效”问题。

6. 内核升级的边界操作,如何做到失败也不变砖

6.1 备份旧内核,给系统留一条退路

内核升级最怕的不是编译失败,而是新内核启动不了。我之前有一块板子,升级完新内核后系统在启动早期 panic,旧内核又没有备份,最后只能拆下存储卡重新烧镜像,整个环境全部推倒重来。从此之后,我给自己立了一条规矩:不管多自信,升级前必须备份当前能用的一套内核镜像、dtb 和设备树源码。

具体操作可以这样:

ssh root@<板子IP> mkdir -p /boot/backup-$(date +%Y%m%d) cp /boot/Image* /boot/backup-$(date +%Y%m%d)/ cp -r /lib/modules/$(uname -r) /lib/modules/backup-$(uname -r)

备份完之后,再看你板子的启动方式。如果 Bootloader 通过固定名称加载文件,比如从/boot/Image启动,那用新内核替换前,最好保留一个旧内核的副本并修改 U-Boot 环境变量,指向旧文件:

# U-Boot 环境变量示例 setenv bootcmd 'fatload mmc 0:1 0x90000000 /boot/Image.new; booti 0x90000000 - 0x90020000' setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rw' saveenv

不同板子 Bootloader 变量千差万别,但核心思想是一致的:不破坏旧文件,只新增新文件,通过修改启动参数指向新文件。这样万一新内核起不来,还能在 Bootloader 里改回旧内核镜像名,救回系统。

6.2 安装新内核和模块的正确顺序

部署顺序有个讲究:先装内核模块,再换内核镜像。

为什么?因为新内核和旧内核版本号不同时,模块目录是按版本号区分的。如果你先把新内核镜像写进启动分区,但根文件系统里还没有对应的/lib/modules/新版本,那么启动后所有模块化驱动都会加载失败。反过来,如果先把模块装好,旧内核还在运行,即使新内核镜像有问题,你当前系统也还能正常使用,可以从容排查。

具体操作可以这样:

  1. 在主机上编译完,把新内核模块安装到临时目录。
  2. 把整个模块目录 scp 到板子根文件系统/lib/modules/下。
  3. 在板子上执行depmod -a更新依赖。
  4. 确认模块就绪后,再复制新内核镜像和设备树到启动分区。
  5. 修改 Bootloader 启动变量,指向新内核。
  6. 重启,观察启动日志。

6.3 新内核启动失败后的急救三板斧

如果重启后起不来,先不要慌,按顺序排查:

第一板斧:看串口日志。如果串口有输出,认真读最后几行。最常见的两种:

  • 输出停在内核解压之前,说明 Bootloader 加载内核这一步有问题,镜像格式不匹配或加载地址不对。
  • 输出到一半 panic,说明内核本身或设备树有问题,看 panic 前最后几行会有线索。

第二板斧:如果串口什么输出都没有,检查 Bootloader 环境变量是否被意外修改,尤其是bootcmdbootargs。在 U-Boot 里执行pri打印当前环境变量,对照之前记录的有效配置逐项检查。

第三板斧:在 Bootloader 停住,手动加载备份的旧内核:

fatload mmc 0:1 0x90000000 /boot/backup-xxx/Image booti 0x90000000 - 0x90020000

如果这条命令能起来,说明硬件和存储都没问题,纯粹是新内核的事。之后就可以回到主机上调试内核配置或设备树,不用动板子。

6.4 什么时候需要重新烧录整个系统

内核升级如果做到了上面这几步,基本不会变砖。但有一个例外:如果你的板子 Bootloader 也被你一并升级或者搞坏了,那就不是内核层面的问题了。

所以这里有一条非常重要的经验:内核升级过程中,绝对不要顺手去烧 Bootloader。Bootloader 是最后一道防线,它只要正常,无论内核怎么折腾,都能用上面的方法救回来;Bootloader 一旦挂了,很多板子只能通过烧录器或者特殊的强制升级模式救砖,门槛直接高一个数量级。

7. 常见问题速查与踩坑记录

7.1 编译阶段典型报错

现象排查思路解决方法
flex: command not found主机缺词法分析器apt install flex
*** No rule to make target “Image”架构没配对,或源码不支持该架构确认ARCH=loongarch,检查arch/loongarch目录是否存在
ld: unknown architecture of input file用了 x86 本地 gcc 编译确认CROSS_COMPILE设置正确,且 PATH 能找到交叉编译器
Kconfig: not found源码目录不干净make mrproper后重新配置
openssl/xxx.h: No such file or directory缺少依赖apt install libssl-dev

这些报错我基本都遇到过,前几次可能还会慌,后来发现绝大多数都是环境问题,和内核代码本身无关。把所有环境依赖提前装好,能少走很多弯路。

7.2 启动阶段常见故障

现象排查思路常见原因与处理方法
串口无输出Bootloader 没找到内核镜像,或串口参数不对检查波特率是否 115200、硬件流控是否关闭、bootcmd 指向的文件是否存在
内核引导后 panic,提示无法挂载根文件系统root 参数错误或对应驱动没编进内核检查 bootargs 中的root=/dev/mmcblk0p2PARTUUID,确认存储控制器驱动为 built-in
启动后外设节点找不到设备树没生效或 pinmux 配置错误fdt print查看 Bootloader 加载的 dtb,对比 dts 源码;检查 pinctrl 节点
insmod报 invalid module format模块版本与当前内核不匹配modinfo hello.ko查看 vermagic,对比uname -r
根文件系统下/lib/modules/为空模块没安装检查INSTALL_MOD_PATH是否正确,重跑modules_install并拷贝

7.3 我踩过的两个最典型的坑

第一个坑是交叉编译命令里忘了带ARCH=loongarch,结果 make 默认按 x86 来编。编译过程中没有任何明显报错,但是生成的 Image 文件是 x86 格式,拷到板子上 U-Boot 加载后一点反应都没有。当时排查了很久,后来用file Image一看才发现格式不对。所以编译完一定要养成习惯,用file命令检查产物架构:

file arch/loongarch/boot/Image

正常输出应该包含ELF 64-bit LSB executable, *loongarch*字样。如果显示的是 x86-64,不用怀疑,肯定没交叉编译成功。

第二个坑是设备树里的复用引脚和另一个外设冲突。当时我在设备树里加了一个 GPIO 按键节点,编译部署一切正常,但板子启动后系统日志疯狂刷引脚已经被占用的警告,之前一直正常的串口也开始出现乱码。后来对照原理图才发现,我用的 GPIO 引脚恰好和调试串口的 RX 引脚是同一个物理引脚,硬件上根本不支持这种接法。从此之后,我在改设备树前一定会先把原理图打开,把引脚占用情况列一张表,再动手改文件。

7.4 一些提高效率的建议

  • 每次编译改动前,先git diff看清楚自己改了哪些源码,避免改乱。
  • 做好记录的习惯。包括编译命令、内核版本、设备树改动点、遇到的问题和解决方案。这个笔记以后就是你的“个人 BSP 维护手册”。
  • 在板子上跑 Linux 时,尽量用 rootfs 的 NFS 挂载方式调试内核,可以在主机上直接改 rootfs 内容,省去反复烧写存储卡的时间。

我个人后来养成了一个习惯:每次发布一个能用的内核版本,都会把.configdts和编译脚本打成一个 tar 包,标记好日期存起来。别嫌麻烦,等你在新内核上把设备驱动调通之后回头看,这份记录能帮你省下大量回退和复盘的时间。

内核升级这件事本身不难,难的是把整个流程控制得“可回退、可追溯”。如果你也是刚上手久久派,建议先照着这个流程完整走一遍,哪怕出厂内核用着没毛病,也值得专门找个晚上编译一次,不为别的,就为把工具链、源码目录、部署路径这些关键节点都摸清楚。后面无论是做驱动开发、外设适配,还是接 ROS 2 这类重量级框架,都会因为你提前趟过这一遍流程而轻松很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询