ARTICLE DETAIL

资讯详情

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

龙芯平台Linux 4.19内核编译全攻略:从架构识别到错误解决

龙芯平台Linux 4.19内核编译全攻略:从架构识别到错误解决 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_defconfigLinux 4.19源码里其实已经提供了针对龙芯3系列的默认配置路径是arch/mips/configs/loongson3_defconfig。这个配置文件里包含了龙芯平台需要的基本选项比如CPU类型、串口、中断控制器、PCIe等。如果你直接用make defconfig大概率生成的是mips通用配置CPU架构可能还是默认的CONFIG_CPU_MIPS32_R1之类的编出来的内核根本无法在龙芯3A4000上跑而且在编译前期就可能因为CPU类型不匹配出现一些奇怪的编译约束问题。正确的配置流程是这样export ARCHmips export CROSS_COMPILEmips64el-linux-gnuabi64- make loongson3_defconfig这里有两处注意点。第一ARCH必须为mips因为4.19还没有LoongArch分支。第二CROSS_COMPILE要匹配实际使用的交叉编译器前缀如果系统里是gcc-mips64el-linux-gnuabi64那么前缀就是mips64el-linux-gnuabi64-。如果你是在龙芯本机上直接编译本机内核而非交叉编译CROSS_COMPILE可以留空但ARCHmips必须要设置否则源码不会走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 ...造成这个问题的直接原因是内核在编译时通过-marchloongson3a等参数指定了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那一行把-marchloongson3a改成-marchmips64r2。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: (.text0x...): relocation truncated to fit: R_MIPS_26 against startup_64解决这类问题最常用的开关是开启CONFIG_MIPS_UNCACHED不对这里是和内核布局相关的选项。在MIPS龙芯平台上通常可以开启CONFIG_PHYSICAL_START相关参数调整内核加载地址但更有效的办法是开启CONFIG_MIPS_ALIGNMENT实际上针对R_MIPS_26Linux内核在arch/mips/Kconfig中提供了一些布局选项。比较实用的方案是开启CONFIG_EXPERT后调整CONFIG_PAGE_OFFSET让内核线性映射区域更宽裕同时在编译时加入-mlong-calls选项。具体做法是在内核源码顶层Makefile中找到KBUILD_CFLAGS追加一个标志KBUILD_CFLAGS -mlong-calls或者在make命令行里指定make ARCHmips CROSS_COMPILEmips64el-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: cant open file ...这个错误一般不是交叉编译器的问题而是因为编译过程中某些中间文件没有生全或者当前目录权限不对。常见于用root用户之外的普通用户编译内核但源码目录的属主是root或者之前你已经用root编译了一半生成了一些root拥有的文件现在换成普通用户继续编译就会提示无法写入或无法打开。优先检查一下ls -l .config如果发现.config属于root直接改属主sudo chown -R $USER:$USER .另外modpost阶段可能需要读取Module.symvers如果这个文件是空的或缺失也会报类似错误。解决办法是先执行make ARCHmips CROSS_COMPILEmips64el-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 8GCC 9也基本可用GCC 10以上需要打一些额外的兼容补丁比如移除-Wno-address-of-packed-member或者处理-Werror相关问题。遇到这类情况我的建议是先看看错误信息中是不是带有-Werror相关的字样如果是可以在Makefile里把KCFLAGS加上-Wno-error禁用把警告当错误make ARCHmips CROSS_COMPILEmips64el-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上使用系统自带的mkinitrdmkinitrd /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 ARCHmips export CROSS_COMPILEmips64el-linux-gnuabi64-生成默认配置make loongson3_defconfig一般到这一步config就出来了。但为了确认CPU核数参数我们可以再调整一下全局选项make menuconfig在Kernel Features里确认SMP设置为你的核数比如龙芯3A4000四核就选4。退出保存。然后开始编译内核。因为交叉编译较慢建议用-j4或-j8make -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 consolettyS0,115200n8 initrd /boot/initrd.img-4.19.247 }consolettyS0是根据你的调试串口设备调整如果使用VGA可以不加或使用consoletty0。6. 常见编译错误速查表方便你快速定位问题我把前面提到的错误汇总成表格并额外补充几个我见过的其他错误。这个表可以收藏备用。错误现象主要原因推荐解法cc1: error: unrecognized command line option -mno-branch-likelyGCC版本过高移除旧选项换GCC 8/7或去掉该编译选项Error: opcode not supportedbinutils太老或不支持龙芯扩展指令升级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 olddefconfigarch/mips/boot/uImage Error 1缺少mkimage工具安装u-boot-toolsmodpost: cant open file Module.symvers模块准备不完整执行make modules_prepareVFS: Unable to mount root fsinitramfs缺失或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 directoryGCC版本过新编译头文件路径不对安装对应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多分钟等待时间完全可以接受。我在多次踩坑后已经很少把交叉编译作为首选了除非是批量构建场景。希望这些经验能让你少走弯路早日拿到自己定制的龙芯内核。
返回列表