搞龙芯平台的内核编译,尤其是 Linux 4.19 这个版本,几乎是每个做国产化平台移植、工控开发、嵌入式落地的人都要过的一道坎。我见过不少项目现场,代码已经写好,板子也备好了,结果卡在内核编译上:要么是汇编报错,要么是链接溢出,要么干脆就是头文件路径不对,一行日志看得人头皮发麻。这篇文章就把我在龙芯平台上编译 Linux 4.19 时遇到过的各种报错、排错思路和处理方案整理出来。不管你是刚接触龙芯的新人,还是在信创、轨交、电力等行业做技术选型的老手,拿到一份 4.19 内核源码后照着这个思路走,能少走一大半弯路。
先说清楚一个方向性问题:文章里所有的“龙芯”,我都按目前市面上能看到的两条技术路线分开讲。因为很多编译报错的根源,其实在动手编译之前就已经埋下了——你是拿 MIPS 兼容工具链编译架构选错的源码,还是在 LoongArch 分支上用旧 GCC 硬编,这两类问题表现很像,但解法完全不一样。
1. 动手之前,先分清龙芯的两条技术路线
1.1 MIPS 兼容时代的龙芯:LS2K1000、3A3000、3A4000
早期龙芯产品线里,LS2K1000、3A3000、3A4000 这些芯片虽然在指令集上沿用了 MIPS64 体系,但内部扩展了很多龙芯自己的指令,比如loongson3a相关的访存、多媒体指令。这些芯片在 Linux 内核里的架构目录都是arch/mips,配置文件名通常是loongson3_defconfig、loongson2k_defconfig这类。编译这一类内核时,ARCH 变量要设成mips,交叉工具链则要用能识别loongson3a扩展的mips64el-linux-gnu-gcc系列。
有个常见的误区是:拿一套“通用 MIPS 工具链”去编 3A4000 的内核,结果在写汇编文件时报错“unrecognized opcode”。这不一定是你代码写错,而是工具链本身不认识目标 CPU 的扩展指令。所以在开始之前,先确认你手里的芯片型号、对应内核源码分支、目标工具链三者是不是同一批来源。很多网上东拼西凑的教程,最容易在这块翻车。
1.2 新指令集时代:LoongArch 与 3A5000、3A6000
从 3A5000 开始,龙芯切到了自主指令集 LoongArch。这个架构在 Linux 官方主线的合入时间点是 5.19,不是 4.19。也就是说,如果你拿到的是干净的官方 linux-4.19.y 源码,里面根本没有arch/loongarch这个目录。这时候还非要在 4.19 上做 LoongArch 平台,通常只有两条路可走:一是用龙芯开源社区维护的 loongnix 内核仓库里的 4.19 分支,二是把新内核里的 LoongArch 支持手动反向移植到 4.19,工作量不小,不太推荐。
所以,当有人告诉我“Linux 4.19 在龙芯上编译出错”,我第一反应不是去看报错日志,而是先问一句:你的板子是 MIPS 龙芯还是 LoongArch 龙芯?如果板子是 3A5000、3A6000,源码却来自官方 4.19 主干,那基本可以说是“拿大米做米饭但锅里没有米”——方向就从根上错了。正确做法是直接换用 loongnix 提供的 4.19 分支,或者把内核版本放宽到 5.19 以上的主线。这一条想通了,后面很多看似诡异的报错都能想通。
1.3 为什么 Linux 4.19 在龙芯上特别容易翻车
4.19 在主流内核里其实算是一个非常成熟的长期维护版本,很多国产平台都基于它做 BSP。但越成熟越说明一个问题:它发布时,龙芯 LoongArch 还没进社区视野,MIPS 龙芯的支持也经历了多个补丁版本迭代。不同厂商从不同基线拉出来的 4.19 源码树,在细节上差异很大。你从 A 网站下了源码,从 B 网站下了补丁,再用 C 网站的工具链编译,三者的版本彼此不认识,就会引发大量看起来莫名其妙的错误。
再加上不少教程里都要求“先 make menuconfig 手动改配置”,新手一不小心改错选项,比如把 CPU 型号选成 MIPS R6,编译器生成的指令集和内核汇编文件需要的指令集就分叉了,报错立刻跟上。这个阶段要解决的,不是某一个报错,而是先建立一套“统一的版本组合”。我的经验是:产品线用哪家方案,就严格用哪家提供的内核源码包和工具链压缩包,少做自作聪明的混搭。
2. 编译环境没配对,一半以上的报错都出在这里
2.1 工具链选型:官方交叉编译器与发行版自带编译器的差别
龙芯平台的内核编译,多数人是在 x86 主机上做交叉编译。这样效率高,也方便集成到 CI。问题是交叉工具链的来源很杂,有的来自龙芯官方发布,有的来自第三方社区,还有的是发行版软件源里现成的gcc-mips64el-linux-gnuabi64。这些工具链不仅版本不同,默认的 ABI、默认的-march参数也不一样。
我比较推荐的做法是:优先使用龙芯开源社区发布的工具链包,通常是带loongson版本标识的一整个压缩包,解压之后把 bin 目录加进 PATH。比如针对 MIPS 龙芯,一套 GCC 8.3 或相近版本、搭配 binutils 2.30+ 的组合,对我来说是最稳定的。用它编译 Linux 4.19 源码树,踩坑最少。这里有个判断工具链是否靠谱的小技巧:在配置好 PATH 后直接执行一下mips64el-linux-gnu-gcc -v,看看输出里的Target字段是不是mips64el-linux-gnu或类似值,再看看版本号。如果Target明显是 x86 或者 ARM,说明你 PATH 里的编译器不对,CROSS_COMPILE 写了也白写。
2.2 内核源码从哪来:stable、Loongnix 分支、自维护补丁树
内核源码的选择直接决定后面会不会踩到“补丁错位”的坑。如果你面向的是 3A4000 一类 MIPS 龙芯,直接下载官方 linux-4.19.y 主线,再核对有没有 loongson 社区维护的补丁集即可。但如果面向的是 2K1000,很多官方主线没有覆盖的板级配置、驱动文件,需要从 Loongnix 的 git 仓库拉取对应的 4.19 分支,这个分支本身已经合入了大量龙芯平台补丁,拿到就能编译。
我不太建议的做法是:在某个 ROM 链接或分享包里下载一个“已经打过补丁”的压缩包,因为这样的包往往来源不明,你后续想用git log查看历史、定位问题时非常被动。拿到源码后,第一时间用git branch、git log -1确认当前基线,最好把源码目录和厂商的仓库地址、提交号对应起来。后面一旦遇到编译问题,能快速反推这个补丁是不是出自官方。这个习惯能帮你省下很多瞎猜的时间。
2.3 最容易忽略的三个环境变量(ARCH、CROSS_COMPILE、PATH)
很多编译报错,追到根上其实就是环境变量没设对。以 MIPS 龙芯为例,开编之前我都会先确认下面几个变量:
export ARCH=mips export CROSS_COMPILE=mips64el-linux-gnu- export PATH=/opt/loongson-gcc-8.3/bin:$PATH make loongson3_defconfig make -j$(nproc) vmlinux这里容易踩的坑有这几个。第一,ARCH 和 CROSS_COMPILE 不区分大小写但语义不同,写成小写也不是不行,但如果在 Makefile 层面被覆盖就会产生混乱。第二,CROSS_COMPILE必须是工具链的前缀,到连字符为止,不能把整个gcc文件名写进去。第三,PATH 一定要把龙芯工具链目录放在前面,否则系统里如果同时存在一个名字相近的 MIPS 工具链,编译时会“张冠李戴”,报错极其隐蔽。第四,用O=out指定输出目录时,每次切换源码分支前,最好将这个输出目录清空一次,否则旧配置和新源码混在一起,Kconfig 会报各种依赖错误。
3. 四大类编译报错,逐类拆开看根因
3.1 配置类报错:defconfig 不存在、Kconfig 依赖缺失
这类报错主要特征是“还没开始编译就挂了”。比如执行make loongson3_defconfig时提示找不到对应的配置文件,多半是源码分支选错了,或者这个源码树根本不是面向龙芯平台的。另一种典型情况是scripts/kconfig/conf执行时报段错误、报“curses.h not found”之类,这通常是编译 menuconfig 需要的 host 依赖没装齐,比如libncurses-dev。解决方式很简单,按发行版对应装包即可。
配置阶段还有个容易被忽略的问题:make menuconfig时通过搜索/LOONGSON去改配置,改完之后直接退出保存,有时候改了不生效。这时候先执行make olddefconfig把缺失的默认选项补齐,再重新编译。如果报错信息指向 Kconfig 文件里的某个依赖不满足,比如“config X depends on Y”之类的提示,多半是你在 menuconfig 里关掉了某些基础选项,比如关掉了CONFIG_64BIT。3A 平台的内核,很多选项是依赖64BIT的,这个选项被关闭后,后续 Kconfig 解析就会连环报错。
3.2 头文件缺失类:version.h / unistd.h / openssl 丢失的真正含义
编译到一半突然说某个头文件找不到,这类报错最干扰视线。根据我的经验,90% 的情况不是你代码缺头文件,而是构建流程没走完。最典型的是:
include/generated/uapi/linux/version.h: No such file or directoryversion.h这类文件是配置阶段自动生成的,并不是源码里自带。报这个错,说明你在执行编译目标时没有先跑make prepare,或者是用make clean而不是make mrproper清理源码树,导致生成目录状态不完整。处理方式是补一次准备步骤:
make mrproper make loongson3_defconfig make prepare另一个很常见的头文件缺失是openssl/opensslv.h。这个出现时,绝大多数情况是因为你开了内核模块签名、或者开启了CONFIG_SYSTEM_TRUSTED_KEYS,编译过程中需要读取证书。主机上如果没装 libssl-dev 就会直接报找不到。处理方法是安装主机依赖,或者临时关掉模块签名相关配置。对一次性的内核验证来说,关掉这个选项并不影响启动。顺便说一句,有时候报asm/unistd.h找不到,那就要怀疑你是不是把ARCH设错了,比如在 LoongArch 源码上用了ARCH=mips,生成的 uapi 目录整个都不对,自然找不到目标架构的头文件。
3.3 汇编与指令集报错:GCC 对不上 -march 的连锁反应
汇编层报错是最让人头疼的一类,因为错误信息往往很简短,直接指向某一行.s汇编代码。比如:
{standard input}: Assembler messages: Error: unrecognized opcode Error: invalid operands for input/output这类错误的原因通常是编译器生成了当前 binutils 不认识的指令,或者内联汇编写的是目标 CPU 不支持的指令。举个例子,3A4000 支持loongson3a扩展指令,如果你的工具链是早期 MIPS 通用版,assembler 不识别这些扩展,编译过程就会挂在汇编阶段。反过来,如果你的内核配置把 CPU 型号选成了 MIPS R6,但 3A4000 本身不是完整的 R6 实现,汇编器也会抱怨指令不合法。
做数据处理时,我通常会先用一条命令快速确认工具链对目标特性的支持情况:
echo | mips64el-linux-gnu-gcc -dM -E - | grep -i loongson如果输出里能看到相关宏,比如__loongson_*,说明这个编译器带有龙芯扩展支持;如果什么都没有,你就要考虑换工具链了。还有一个不起眼的坑:某个外部补丁为了 O3 优化或特定特性,会在汇编文件里写.set mips64r2这样的伪指令。如果补丁和源码基线不匹配,伪指令和实际目标 ISA 冲突,也会触发操作数非法一类报错。这时候不要死磕汇编代码,先回头查补丁基线对不对,你会轻松很多。
3.4 链接阶段报错:R_MIPS_26 / section overflow 到底在说什么
链接阶段的报错有很强的“压迫感”,因为往往文件编了一大半才爆出来,而且信息里全是地址、重定位,看起来像天书一样。最常见的两种是:
relocation truncated to fit: R_MIPS_26 against 'start_kernel' ld: section .text will not fit in region先说R_MIPS_26。MIPS 的跳转指令 J / JAL 在机器码里只放 26 位偏移,范围是 ±256MB。当内核代码段很长,或者某个模块内函数之间的跳转距离超过这个范围时,链接器就没办法把这个重定位“塞进”26 位里,于是报错说“relocation truncated”。内核里解决这个问题的常规手段是在编译时启用-mlong-calls,让编译器不再生成直接跳转,而是生成寄存器间接跳转。R_MIPS_26 报错出现时,先看命令行里有没有-mlong-calls。
比如用make V=1打出真正的编译命令,然后手动在对应目录的 Makefile 里追加:
CFLAGS_xxx.o += -mlong-calls这种方法能针对性解决某个目标文件的跳转范围问题。如果报错出现在 vmlinux 链接阶段而不是某个.o文件里,那就要检查.config中是不是开启了CONFIG_64BIT、CONFIG_BUILD_ELF64等选项。64 位入口的 vmlinux 布局通常更宽松,能减少多种重定位截断问题。另外,检查内核链接地址是否被改得过高也很重要。很多国产板卡的 BSP 会把内存起始地址映射得比较靠后,如果链接脚本里设置的地址和硬件布局不一致,普通跳转和绝对寻址都可能溢出。
还有一个经常和它一起出现的section will not fit,表示某个段超出链接脚本分配的空间。这往往不是代码写错,而是链接地址被占了、又或者某些节被硬塞到固定位置。处理时先把链接脚本里对应段的起始地址往内存高地址调一调,如果产品对物理地址有固定要求,再反过来裁剪代码段或把部分数据移到只读段。总之,链接类错误的核心是“距离”和“地址”,不是代码逻辑。
3.5 编译器版本引发的编译失败:新 GCC 编译老内核的兼容性处理
龙芯平台的工具链更新速度并不算快,但很多人手里难免有“新玩意”。用太新的 GCC 编译 Linux 4.19,会碰到一批由编译告警升级成错误导致的失败。比如 GCC 10 以后,对数组边界、字符串函数做了更严格的检查,老内核的某些写法在旧编译器下是“警告”,到新编译器下就成了“错误”,构建直接中断。
比较典型的报错包括:
cc1: error: unrecognized command-line option '-Wno-address-of-packed-member' error: array subscript ... is outside array bounds第一种是 GCC 版本太老,编译器本身不认识这个警告选项;第二种是版本太新,检查太严格,老代码被揪出来了。稳妥的方案是:查一下这套内核的开发周期,按当时的工具链版本去匹配。4.19 在龙芯平台上,GCC 8.x 是比较舒服的区间,GCC 12/13 就很容易翻车。如果非要继续用新 GCC,可以临时把“把警告当错误”这个开关关掉。做法是在 make 命令行里加:
make KCFLAGS="-Wno-error" ...或者对具体目录做定向豁免。注意不要全局把-Wno-error变成默认,否则有些真正的错误也会被放过去,后面启动时各种诡异问题都会回来找你。
4. 一次典型排错过程的全流程演示
4.1 现场现象:vmlinux 链接阶段反复失败
拿一个真实的场景来演示排错过程。假设我拿到了一个 3A4000 平台的 BSP,内核源码是 loongnix 的 4.19 分支,工具链是对应的 8.3。配置阶段很顺利,编译到一大半,在最后链接 vmlinux 时报了这样一段:
arch/mips/kernel/built-in.o: in function 'loongson3_smp_fixup': relocation truncated to fit: R_MIPS_26 against 'smp_send_reschedule'报错指向内核 SMP 相关代码,给人一种“是不是 SMP 配置有问题”的错觉。但如果直接去改 SMP 配置,方向就偏了。链接器给出的关键信息是R_MIPS_26,和 3.4 节说的同类问题:跳转距离超过了 26 位偏移的范围。这时候我第一件事不是改代码,而是确认整个构建的 CFLAGS 里有没有开-mlong-calls。
4.2 用 V=1 拿到第一手信息,逐层缩小范围
我习惯先用一条命令把冗长的编译过程全部打出来:
make V=1 -j$(nproc) vmlinux 2>&1 | tee build.log然后到日志里搜mlong-calls。如果一条都搜不到,基本可以断定当前内核配置没有启用这个选项。接着我去.config里确认 CPU 型号定义。对于 3A4000,应该在.config里看到CONFIG_CPU_LOONGSON3=y,同时CONFIG_64BIT=y。如果这些都没问题,而arch/mips/Makefile里只在特定条件下加-mlong-calls,那可以判断问题出在某几个目标文件上。
接下来我会定位到报错涉及的目标文件,也就是arch/mips/kernel/built-in.o里的某一段。为了精确解决,看一下这个目录的 Makefile,给对应源文件追加长调用编译选项。比如报错函数来自smp.c,就在arch/mips/kernel/Makefile里加一行:
CFLAGS_smp.o += -mlong-calls然后重新编译,观察是否还有新的R_MIPS_26报错出现。如果有,继续给相邻的目标文件加。实战中这样操作两三轮,链接错误就能解决。
4.3 一行 Kconfig 改动解决链接溢出的实际操作
还有一种情况是链接地址本身的布局问题。有时候改配置项比逐个加-mlong-calls更彻底。在看 3A3000/3A4000 平台时,我会去检查CONFIG_KVM_GUEST、CONFIG_64BIT等选项。尤其是某些 BSP 默认拿 32 位内核配置来改,导致 vmlinux 被链接到 32 位地址空间,代码段稍长就溢出。此时把 64 位构建选项打开,很多重定位问题会自然消失。
我做过一次比较有代表性的操作:某次section .text will not fit反复出现,我算了一下链接脚本给.text分配的地址区间,发现脚本默认起始地址比硬件实际的系统内存地址高了 1MB,结果代码段尾部挤到了不存在的地址上。处理方式是在相关平台 Kconfig 里改成正确的PHYSICAL_START值,重新make olddefconfig后问题直接消失。这一改动在编译时看不出任何区别,但加载阶段和链接阶段的地址计算就全都对上了。
5. 编译产物验证:能编出来不代表能启动
5.1 先用 QEMU 把内核跑起来,少烧坏几张板子
内核编译通过只是万里长征第一步。我强烈建议,在把 vmlinux 烧进板子前,先用 QEMU 跑一遍。QEMU 对龙芯的支持这些年已经比较成熟:MIPS 龙芯可以使用qemu-system-mips64el配合malta或loongson3-virt机器模型,LoongArch 则可以用qemu-system-loongarch64。先验证内核能不能启动到 userspace,再验证串口有没有输出,比直接在真机上反复实验要快得多。
用 QEMU 验证还有一个好处:可以隔离问题。如果 QEMU 里能正常启动,而真机起不来,那基本可以确定问题在 bootloader、设备树或内存布局这一层,不是内核编译的问题。反之,如果 QEMU 里也起不来,就继续回头查内核配置。
5.2 上板启动时最容易踩的三个坑
第一个坑是串口没输出。很多人编完内核烧进去,屏幕毫无反应,第一反应是内核死机。结果查了一圈,发现是 bootloader 传的console参数不对。龙芯板载串口通常是ttyS0,波特率 115200,启动参数里要写清楚:
console=ttyS0,115200如果 PMON 或 UEFI 只传了console=ttyS0没传波特率,内核可能还是能输出,但如果固件初始化不同于默认值,输出就是乱码或空白。这个坑很小,排查却要花不少时间。
第二个坑是内存布局不匹配。很多龙芯开发板的物理内存并不从 0 地址开始,而是从0x10000000或0x20000000开始。如果内核配置里的CONFIG_MEMORY_START和实际硬件不一致,内核启动后访问内存会越过有效范围,导致随机 panic。这类问题在 QEMU 里通常不好复现,因为 QEMU 的默认内存布局比较“标准”,所以上板前一定要把板卡用户手册里的内存映射表对照一遍。
第三个坑是 initramfs 架构不对。编译内核是一回事,制作 initramfs 又是另一回事。如果你在 x86 主机上用构建工具生成了 initramfs,但构建工具链的 ARCH 配错了,导致里面打包的/init是 x86 可执行文件,内核一解压就会报:
Kernel panic - not syncing: No working init found.这在内核本身编译没问题的情况下非常容易误导人。排查时先用file init看一下可执行文件格式,确认是ELF 64-bit MSB/LSB MIPS开头,再考虑其他原因。
6. 常见报错速查表
| 报错片段 | 根因方向 | 解决动作 |
|---|---|---|
| Can't find default configuration "loongson3_defconfig" | 源码分支不对或路径缺失 | 替换为 loongnix 4.19 分支或检查代码目录完整性 |
| include/generated/uapi/linux/version.h: No such file | 构建流程未执行 prepare | 执行 make prepare 或 make mrproper 后重来 |
| fatal error: openssl/opensslv.h: No such file | 主机缺少 libssl-dev 或开启了模块签名 | 安装 libssl-dev,或临时关闭 CONFIG_MODULE_SIG |
| unrecognized opcode / invalid operands | 工具链不支持目标 ISA 扩展 | 换用龙芯官方工具链,避免通用 MIPS 工具链 |
| relocation truncated to fit: R_MIPS_26 | 跳转范围超出 26 位限制 | 给目标文件追加 -mlong-calls,或检查链接地址 |
| section .text will not fit | 链接脚本段地址分配问题 | 调整 PHYSICAL_START 或链接脚本段布局 |
| unrecognized command-line option '-Wno-address-of-packed-member' | 编译器太老/太新,选项不兼容 | 换成 4.19 时代匹配的 GCC 版本,或删掉对应 Wno 选项 |
| undefined reference to 'xxx' | 对应 CONFIG 依赖未开启 | 搜索该符号关联的 Kconfig,打开依赖配置 |
| No working init found | initramfs 架构错误或为空 | 用交叉工具链重新构建 initramfs,并检查 ARCH |
| Kernel panic - not syncing: Attempted to kill init | 用户态与内核头文件版本不匹配 | 统一交叉编译环境和内核头文件来源 |
我在龙芯平台上反复折腾多次之后,逐渐养成了一个习惯:拿到任何一套 4.19 源码和工具链,先花两分钟把三个东西记录下来——源码仓库的地址和提交号、交叉编译器版本号、目标板卡型号。这三者只要来自同一个版本周期,绝大多数编译问题都不会出现。所谓编译出错,十有八九是混搭出来的,先查环境、再查配置,最后才怀疑代码,这个顺序在龙芯上特别管用。如果查了半天还找不到方向,去源码树的Documentation/目录里翻一翻平台相关文档,或者用git log -- arch/mips看看这个文件最近的修改记录,往往比满网搜索更能说明问题。