1. 编译前先搞定两件事:架构识别和工具链选择
很多人第一次拿到龙芯开发板或老式龙芯台式机,第一反应就是直接下个Linux 4.19内核源码包,敲上make menuconfig然后make -j4,以为跟x86机器上一样顺滑。结果往往是一堆莫名其妙的报错,有的甚至刚开始就失败了。如果你也是这种情况,先别急着搜错误码,最值得花时间的其实是前面两步:搞清你手里的龙芯到底是什么架构,以及你正在用的工具链到底能不能干这个活。
1.1 你的龙芯是MIPS64还是LoongArch
龙芯从2001年起步,经历过几个大阶段。早期龙芯2E、2F是基于MIPS III兼容的指令集,后来龙芯3A1000到3A4000都是MIPS64兼容的处理器,指令集都是基于MIPS64r2再做了扩展。而2021年之后发布的龙芯3A5000、3A6000,包括很多新的龙芯开发板,使用的是龙芯自研的LoongArch指令集,和MIPS已经有本质区别。
Linux 4.19这个版本比较特殊,它对龙芯的支持主要停留在MIPS架构层面,也就是针对3A3000、3A4000这类处理器。内核源码里arch/mips目录下有针对龙芯的系列文件,比如loongson3平台代码。如果你的机器是3A5000或者更新的LoongArch处理器,那Linux 4.19几乎无法直接编译通过,需要打龙芯官方提供的补丁,或者直接切换到5.19、6.1等高版本内核。对于LoongArch平台的适配,上游社区直到Linux 5.19才正式合入LoongArch架构支持代码,4.19实在太老了。
所以第一步就是确认CPU型号。可以通过/proc/cpuinfo查看,或者在终端执行lscpu,如果里面有Loongson-3A5000,但架构显示LoongArch,可以直接放弃4.19。如果显示Loongson-3A4000、MIPS 64之类的字眼,那就可以继续往下走。
1.2 工具链版本决定了你会不会被GCC坑到
绝大多数"编译出错"并不是内核代码本身的问题,而是编译器版本不匹配。很多人在龙芯上用的是发行版自带的交叉编译器,比如gcc-mips64el-linux-gnuabi64,这套工具链的GCC版本通常比较新,可能是GCC 9、10、11,但Linux 4.19时代的代码写得很早,它假设GCC不会做某些激进优化。当使用GCC 9以上版本编译时,经常会出现类似下面这样的错误:
arch/mips/kernel/topology.c: 警告: 未使用的变量 cc1: error: unrecognized command line option "-mno-branch-likely"报错的原因很简单:新GCC移除或改变了某些旧的编译器选项,比如-mno-branch-likely在GCC 10以后就不再支持了。解决思路有两个,一是降低GCC版本到GCC 7.x或8.x,二是手动修改内核的Makefile,把不支持的选项从KBUILD_CFLAGS中移除。不过我更推荐第一种,因为内核编译过程中还可能出现其他因为新编译器行为变化导致的奇怪问题,不能只见树木不见森林。
在中国龙芯生态里,很多交叉编译工具链实际上是从https://mirrors.tuna.tsinghua.edu.cn/loongson/等镜像站下载的,最好找厂商提供的、与内核版本时间匹配的交叉工具链。比如针对Linux 4.19开发周期,推荐使用GCC 7.3或GCC 8.2的交叉编译器。如果已经装好了系统,直接用本机的gcc编译也可以,但前提是你的整个系统是基于MIPS版本的Linux发行版,并且gcc版本不能太激进。我在龙芯3A4000的Loongnix系统上编译4.19时,系统自带gcc是8.2.1,一次通过。换到Debian 11移植版上,gcc变10.2,立刻出现一堆问题。
2. 内核配置阶段最容易踩的坑
很多人配置内核喜欢偷懒,直接拿x86的config去改,或者用make defconfig生成一个默认配置。但实际上龙芯平台的内核编译出错,很多在配置阶段就已经埋下伏笔了。比如缺少某些必选选项、.config文件交叉污染、顺便开启了和架构冲突的功能等。
2.1 别用make defconfig,用loongson3_defconfig
Linux 4.19源码里其实已经提供了针对龙芯3系列的默认配置,路径是arch/mips/configs/loongson3_defconfig。这个配置文件里包含了龙芯平台需要的基本选项,比如CPU类型、串口、中断控制器、PCIe等。如果你直接用make defconfig,大概率生成的是mips通用配置,CPU架构可能还是默认的CONFIG_CPU_MIPS32_R1之类的,编出来的内核根本无法在龙芯3A4000上跑,而且在编译前期就可能因为CPU类型不匹配,出现一些奇怪的编译约束问题。
正确的配置流程是这样:
export ARCH=mips export CROSS_COMPILE=mips64el-linux-gnuabi64- make loongson3_defconfig这里有两处注意点。第一,ARCH必须为mips,因为4.19还没有LoongArch分支。第二,CROSS_COMPILE要匹配实际使用的交叉编译器前缀,如果系统里是gcc-mips64el-linux-gnuabi64,那么前缀就是mips64el-linux-gnuabi64-。如果你是在龙芯本机上直接编译本机内核(而非交叉编译),CROSS_COMPILE可以留空,但ARCH=mips必须要设置,否则源码不会走arch/mips里面的编译规则。
生成好loongson3_defconfig之后,我建议再用make menuconfig加两个关键选项,一个是对应你的CPU型号,一个是开启适合龙芯3A4000的优化参数。多数情况下默认就行,但为了保险可以确认一下CONFIG_CPU_LOONGSON3是否被选中。可以执行:
grep CONFIG_CPU_LOONGSON3 .config如果输出是# CONFIG_CPU_LOONGSON3 is not set,那说明配置有误,要进去重新选,否则编译出来的内核甚至无法启动。
2.2.config文件污染问题
还有一种配置阶段常见的坑:你之前在x86或者其他架构上编译过内核,源码目录里残留了一个旧的.config文件,然后直接在新架构上跑make menuconfig,系统会提示你基于现有配置加载。如果没有清理干净,某些x86选项会留下,编译时就会出现无法解析的语法错误,或者干脆生成一个架构混杂的配置,出现"Kconfig:error: recursive dependency detected"这类问题。
我的习惯是,拿到一份新源码之后先清理一次:
make mrproper这个命令会删除所有之前的构建产物和配置文件。然后再执行make loongson3_defconfig。另外,修改过.config之后如果需要更新依赖,建议用make olddefconfig而不是make oldconfig,前者会采用默认值自动填充新增选项,不容易因为手动按错导致配置不一致。如果你改完了配置之后,编译时报出类似 "No rule to make target 'include/config/auto.conf' " 的错误,说明配置依赖没有生成完整,再执行一次make olddefconfig基本能解决。
3. 编译中高频报错:从汇编到链接逐个击破
配置完成之后,真正的编译阶段才是重头戏。Linux 4.19在龙芯平台上的编译错误五花八门,但如果把常见问题归归类,其实主要集中在这几个方向:汇编器不认识某些指令、链接阶段的重定位溢出、内核构建脚本中小工具的编译错误,以及各种头文件包含错误。这些坑我都踩过,每一项都有比较成熟的解决办法。
3.1 汇编错误:"Error: opcode not supported" 或 "unknown pseudo-op"
这类错误通常来自内联汇编文件,或者某个CPU相关的.S文件。比如在编译arch/mips/kernel/head.S或arch/mips/lib/*.S时,会出现类似:
arch/mips/kernel/head.S: Assembler messages: arch/mips/kernel/head.S:165: Error: opcode not supported: lsa or ...造成这个问题的直接原因是,内核在编译时通过-march=loongson3a等参数指定了CPU架构,但当前使用的汇编器(binutils)版本太低,不认识loongson3a这个架构以及一些龙芯扩展指令。解决办法是升级binutils到2.28以上,或者开启兼容模式。也可以先确认一下当前交叉工具链支持的架构选项:
mips64el-linux-gnuabi64-as --help | grep "march="如果列表中没有loongson3a,那就得换工具链。如果你用的工具链是从龙芯厂商发布的软件包中安装的,但依然报这个错,比较少见,更大可能是你用了一个纯社区的MIPS工具链,它没有龙芯的扩展指令支持。此时最简单的方案是改成用mips64r2通用架构编译内核,代价是性能轻微下降,但兼容性好。修改方式是在Makefile或config里调整CONFIG_CPU_LOONGSON3相关的编译参数,或者在arch/mips/Makefile中找到cflags-y那一行,把-march=loongson3a改成-march=mips64r2。
3.2 链接错误:"relocation truncated to fit: R_MIPS_26 against ..."
这种错误在做内核链接时非常常见,尤其当代码段过大,或者某些函数分支跳转距离超过32MB的限制时。MIPS的跳转指令有一个26位立即数,跳转范围只有256MB,而R_MIPS_26类型重定位往往要求目标地址在同一256MB区域内。当内核的代码段膨胀后,可能就会报:
arch/mips/kernel/head.o: In function `kernel_entry': (.text+0x...): relocation truncated to fit: R_MIPS_26 against `startup_64'解决这类问题,最常用的开关是开启CONFIG_MIPS_UNCACHED?不对,这里是和内核布局相关的选项。在MIPS龙芯平台上,通常可以开启CONFIG_PHYSICAL_START相关参数调整内核加载地址,但更有效的办法是开启CONFIG_MIPS_ALIGNMENT?实际上,针对R_MIPS_26,Linux内核在arch/mips/Kconfig中提供了一些布局选项。比较实用的方案是开启CONFIG_EXPERT后,调整CONFIG_PAGE_OFFSET,让内核线性映射区域更宽裕,同时在编译时加入-mlong-calls选项。
具体做法是,在内核源码顶层Makefile中找到KBUILD_CFLAGS,追加一个标志:
KBUILD_CFLAGS += -mlong-calls或者在make命令行里指定:
make ARCH=mips CROSS_COMPILE=mips64el-linux-gnuabi64- KBUILD_CFLAGS="-mlong-calls" -j4-mlong-calls会让编译器对跨段跳转使用更长的调用序列,避免R_MIPS_26重定位超范围。代价是内核体积稍大、性能有极微小损失,但至少能编译通过。如果你的问题不在函数调用,而是数据段的$gp相对寻址溢出,则可能需要调整CONFIG_MIPS_AUTO_PFN_OFFSET或使用-G 0选项来禁用全局指针优化。总之这类错误需要具体看报错位置再定,优先尝试追加KBUILD_CFLAGS比较快。
3.3 编译工具错误:"scripts/mod/modpost: error: can't open file ..."
这个错误一般不是交叉编译器的问题,而是因为编译过程中某些中间文件没有生全,或者当前目录权限不对。常见于用root用户之外的普通用户编译内核,但源码目录的属主是root,或者之前你已经用root编译了一半,生成了一些root拥有的文件,现在换成普通用户继续编译,就会提示无法写入或无法打开。优先检查一下:
ls -l .config如果发现.config属于root,直接改属主:
sudo chown -R $USER:$USER .另外,modpost阶段可能需要读取Module.symvers,如果这个文件是空的或缺失,也会报类似错误。解决办法是先执行:
make ARCH=mips CROSS_COMPILE=mips64el-linux-gnuabi64- modules_prepare重新生成模块相关的准备文件,再继续make zImage。如果是编译外部模块时遇到modpost错误,可能还需要make modules。
3.4 头文件或宏定义的兼容性问题
4.19的内核源码在新GCC环境下还会出现一些比较隐晦的编译错误。比如我见过include/linux/compiler-gcc.h报错,说__attribute__((error(""))无法识别,这多半是GCC版本过旧,不支持某个attribute。但如果在龙芯平台上是新GCC报错,则通常是GCC 12移除了某些老的内核代码依赖的行为。Linux 4.19时代的内核官方只支持到GCC 8,GCC 9也基本可用,GCC 10以上需要打一些额外的兼容补丁,比如移除-Wno-address-of-packed-member或者处理-Werror相关问题。
遇到这类情况,我的建议是先看看错误信息中是不是带有-Werror相关的字样,如果是,可以在Makefile里把KCFLAGS加上-Wno-error禁用把警告当错误:
make ARCH=mips CROSS_COMPILE=mips64el-linux-gnuabi64- KCFLAGS="-Wno-error" -j4这是一个通用解法,能解决很多因为"警告升级为错误"而中断的编译过程。但这属于治标不治本,如果能换编译器就换编译器。我个人在编译龙芯4.19内核时,尝过GCC 12的苦头,后来就是老老实实装了个GCC 8的交叉编译器,整个过程顺滑很多。
4. 编译通过后内核无法启动的排查思路
有些人觉得编译只要过了,万事大吉,结果拷到开发板上U盘启动,一到内核解压阶段就卡死,或者串口输出乱码,或者干脆黑屏。这类问题虽然不属于"编译出错",但实际上也是配置或生成过程中埋下的雷。我在这里一并说一下,因为它们非常容易和编译问题混淆,你以为还要改代码,其实只是少了某个文件。
4.1 内核镜像格式和引导方式要匹配
在龙芯MIPS平台上,编译产物可能是vmlinux、vmlinuz或uImage。不同引导方式对镜像格式要求不同。如果使用PMON或U-Boot引导,通常需要vmlinuz或uImage格式。执行make uImage需要有mkimage工具,且需要在配置中开启CONFIG_SYS_SUPPORTS_BIG_ENDIAN之类?其实主要在于生成uImage时,要在顶层Makefile中能调用到mkimage。如果系统提示:
make[1]: *** [arch/mips/boot/uImage] Error 1大多是因为缺少uboot-mkimage工具,安装一下即可:
sudo apt install u-boot-tools如果你用的是grub引导,则直接使用vmlinuz就行。在Loongnix这类系统上,一般/boot下放的是带vmlinuz压缩内核,配合grub.cfg启动。如果你编出来的内核无法启动,先检查一下引导配置里的root=参数是否正确。
4.2 设备树或ACPI问题
Linux 4.19对龙芯3系列支持方式比较特殊。早期龙芯3A3000使用内置的loongson3平台代码,自动探测硬件;而3A4000则开始支持ACPI启动。4.19内核中,loongson3平台代码默认使用device tree吗?其实不是,很多龙芯平台并不需要设备树,而是通过BIOS传递信息。但如果你的主板是特殊的评估板,可能需要提供dtb。编译时如果没有生成相应的dtb,启动时可能会在早期串口初始化阶段卡住。
排查方法:在编译完成后检查arch/mips/boot/dts/目录下是否生成了对应的dtb文件。通常龙芯平台文件的路径是arch/mips/boot/dts/loongson/。如果有loongson3-4core.dts之类的文件,你需要用make dtbs单独编译。如果没有dts目录,那说明你的平台是采用ACPI方式,不需要关注dtb。
4.3 initramfs没有打包进去
这个真的很容易被忽略。很多人在本机编译x86内核时,系统要么用make modules_install、要么用update-initramfs自动生成initramfs,但交叉编译龙芯内核时,因为没有执行make modules_install和生成initramfs,导致启动时内核panic,提示VFS: Unable to mount root fs。有的人误以为这是编译问题,反复重新编译。正确的做法是,在内核配置中开启CONFIG_BLK_DEV_INITRD,然后用工具把根文件系统的initrd打包进内核,或者单独放一个initrd.img,在grub中指定。
例如在Loongnix上,使用系统自带的mkinitrd:
mkinitrd /boot/initrd.img-4.19.0-new $(uname -r)不过此时$(uname -r)是原系统版本,建议直接用你自己的内核版本号。这个过程比较繁琐,但只有做一次,启动问题就能解决。
5. 一次完整的交叉编译实录:从源码下载到生成vmlinuz
前面讲了那么多理论和坑,这里我用自己的实际过程给你完整走一遍。这是基于龙芯3A4000平台,在x86主机上用交叉编译方式编译Linux 4.19.247的过程。你需要准备一台x86_64的Linux机器,装好交叉编译工具链。如果你手头直接有龙芯本机,也可以在本机编译,指令略有不同,我会在后面标注。
5.1 安装交叉工具链
在Ubuntu 20.04系统上,先安装基础依赖:
sudo apt install build-essential bc bison flex libssl-dev libncurses-dev u-boot-tools然后安装MIPS64交叉编译器:
sudo apt install gcc-mips64el-linux-gnuabi64 binutils-mips64el-linux-gnuabi64注意,Ubuntu 20.04默认的GCC是9.3,这个版本在编译4.19时偶尔有小问题,但基本可用。我自己更推荐安装GCC 8版本的工具链,如果你的发行版仓库里没有,可以从龙芯开源社区下载。
5.2 下载并解压内核源码
从kernel.org获取Linux 4.19.247版本:
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.19.247.tar.xz tar -xJf linux-4.19.247.tar.xz cd linux-4.19.247然后清理之前可能的残留:
make mrproper5.3 配置并编译
设置环境变量:
export ARCH=mips export CROSS_COMPILE=mips64el-linux-gnuabi64-生成默认配置:
make loongson3_defconfig一般到这一步,config就出来了。但为了确认CPU核数参数,我们可以再调整一下全局选项:
make menuconfig在Kernel Features里确认SMP设置为你的核数,比如龙芯3A4000四核就选4。退出保存。
然后开始编译内核。因为交叉编译较慢,建议用-j4或-j8:
make -j4 vmlinuz这里用vmlinuz目标,它会生成压缩后的内核镜像。如果你的工具链齐全,产物在arch/mips/boot/vmlinuz。如果想生成uImage,可以执行:
make -j4 uImage如果过程中报了之前提到的-mno-branch-likely错误,说明你的gcc太新。一个临时办法是在Makefile中把KBUILD_CFLAGS中的-mno-branch-likely去掉,或者用以下命令编译:
make -j4 vmlinuz KCFLAGS="-Wno-error -mno-branch-likely"但这样不保证完全解决,最好的办法是安装GCC 8。我用GCC 8编译时,整个流程大概只花了25分钟。如果用GCC 9,可能会在个别文件上报错,需要手动处理。
5.4 编译和安装模块
内核镜像编译好后,如果后续要加载模块,需要编译模块并安装到目标根文件系统的/lib/modules目录下:
make -j4 modules make modules_install INSTALL_MOD_PATH=/path/to/your/rootfs注意这里不是安装到当前系统的/lib/modules,而是指定你的目标根目录。如果你是在龙芯本机上编译,直接执行:
make -j4 modules sudo make modules_install那么模块就会安装到本机的/lib/modules/4.19.247下了。
5.5 生成initramfs并拷贝
如果你的根文件系统是独立分区,需要初始化ramdisk来挂载根分区,就参考前面的mkinitrd方式。这里有两种做法:一是把initrd放在/boot,二是直接编进内核。我习惯把initrd单独放,方便调试。操作流程:
cp arch/mips/boot/vmlinuz /path/to/boot/vmlinuz-4.19.247 cp System.map /path/to/boot/System.map-4.19.247 # 在目标rootfs中生成initrd mount --bind /dev /path/to/rootfs/dev chroot /path/to/rootfs /bin/bash mkinitrd -o /boot/initrd.img-4.19.247 4.19.247 exit当然实际过程要根据你的根文件系统里的工具进行调整,我这只是示意。如果你没有rootfs环境,也可以在不使用initrd的情况下,保证内核配置中CONFIG_CMDLINE里指定了正确的root=/dev/sdaX,并且开启了对应根文件系统的驱动(如ext4),也能直接启动。
5.6 grub配置
在目标龙芯机器上,修改/boot/grub/grub.cfg,添加一个启动项:
menuentry 'Linux 4.19.247 Loongson' { linux /boot/vmlinuz-4.19.247 root=/dev/sda2 console=ttyS0,115200n8 initrd /boot/initrd.img-4.19.247 }console=ttyS0是根据你的调试串口设备调整,如果使用VGA,可以不加或使用console=tty0。
6. 常见编译错误速查表
方便你快速定位问题,我把前面提到的错误汇总成表格,并额外补充几个我见过的其他错误。这个表可以收藏备用。
| 错误现象 | 主要原因 | 推荐解法 |
|---|---|---|
cc1: error: unrecognized command line option "-mno-branch-likely" | GCC版本过高,移除旧选项 | 换GCC 8/7,或去掉该编译选项 |
Error: opcode not supported | binutils太老或不支持龙芯扩展指令 | 升级binutils到2.28+,或改用mips64r2架构编译 |
relocation truncated to fit: R_MIPS_26 against ... | 跳转距离超出MIPS范围 | 追加KBUILD_CFLAGS += -mlong-calls,或开启CONFIG_EXPERT调整布局 |
No rule to make target 'include/config/auto.conf' | 依赖配置未生成 | 执行make olddefconfig |
arch/mips/boot/uImage Error 1 | 缺少mkimage工具 | 安装u-boot-tools |
modpost: can't open file 'Module.symvers' | 模块准备不完整 | 执行make modules_prepare |
VFS: Unable to mount root fs | initramfs缺失或root参数不对 | 生成initrd,检查启动参数 |
arch/mips/kernel/topology.c: undefined reference tocpu_topology'` | 配置选项冲突或内核代码适配问题 | 检查CONFIG_SMP和CONFIG_GENERIC_ARCH_TOPOLOGY的搭配 |
fatal error: linux/compiler-gcc7.h: No such file or directory | GCC版本过新,编译头文件路径不对 | 安装对应GCC版本,或创建空的compiler-gcc10.h等文件(要谨慎) |
warning: deprecated-mno-branch-likely'导致-Werror`中断 | GCC高版本+Werror策略 | 追加KCFLAGS=-Wno-error |
这张表基本覆盖了大多数"Linux 4.19在龙芯平台上编译"遇到的问题。
7. 我的几个额外心得
编译龙芯内核和编译x86内核最大的感觉是,环境必须克制。不要为了追新而盲目升级交叉编译器,内核源码的成熟度比你想象中更依赖工具链的"同期匹配"。如果你是在生产环境里给龙芯设备做定制内核,我建议把整个交叉编译工具链固定下来,甚至用Docker封装一个带有GCC 8、旧版binutils的编译环境,这样团队任何人拿到同一份环境,都能稳定复现结果。
还有一个容易忽略的问题是磁盘空间。龙芯交叉编译内核,源码加中间产物可能占用超过10GB,尤其开启了make modules后,产生的大量.o文件会迅速膨胀。如果编译中途因为磁盘写满而报错,你会看到很多莫名其妙的"No space left on device",有人会误判为代码问题。编译前先执行df -h确认磁盘余量。
最后,如果条件允许,尽量在龙芯本机上编译,而不是交叉编译。本机编译至少能省掉CROSS_COMPILE相关的环境变量困扰,而且一旦遇到汇编器错误,可以直接用本机的as --version排查。实测龙芯3A4000四核编译4.19大概需要40多分钟,等待时间完全可以接受。我在多次踩坑后,已经很少把交叉编译作为首选了,除非是批量构建场景。希望这些经验能让你少走弯路,早日拿到自己定制的龙芯内核。