第四章 Linux内核移植:从源码到跑起来

好,咱们进入正题。这一章讲的是Linux内核移植,说白了就是让Linux内核在你的海思芯片上跑起来。我刚开始做BSP时,觉得这步特别神秘,后来发现其实就是几个关键步骤:拿源码、改配置、写设备树、裁剪、编译、烧录。嗯,一步步来。

4.1 内核源码获取与配置

先说说源码从哪来。我个人习惯直接从kernel.org拉主线代码,但海思平台嘛,官方SDK里通常会带一份打过补丁的内核。我建议你用SDK里的那份,省得自己打一堆补丁,踩坑概率小很多。

拿到源码后,第一件事就是配置。配置有两种方式:

  • make menuconfig:图形化界面,适合新手,我早期全靠它
  • make defconfig:用默认配置,然后手动改

我个人更推荐第二种。为什么?因为menuconfig里选项太多,你很容易漏掉关键项。我有个血的教训——有一次忘了开串口驱动,内核跑起来后控制台一片黑,查了两天才发现。

我的习惯:先make ARCH=arm64 hisilicon_xxx_defconfig生成基础配置,再用menuconfig微调。这样既快又稳。

4.2 设备树(DTS)概念与编写

设备树,英文叫Device Tree,缩写DTS。这玩意儿说白了就是描述硬件信息的配置文件。你想想看,内核要驱动一个外设,总得知道它挂在哪条总线上、中断号是多少、寄存器地址在哪吧?这些信息就写在DTS里。

DTS的基本结构是这样的:

/dts-v1/;

#include "hi3559av100.dtsi"

/ {
    model = "HiSilicon HI3559AV100 DEMO Board";
    compatible = "hisilicon,hi3559av100";

    chosen {
        stdout-path = &uart0;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x0 0x80000000 0x0 0x80000000>;
    };

    &uart0 {
        status = "okay";
        clocks = <&clk_uart0>;
    };
};

这里有几个关键点:

  • compatible:告诉内核这个板子是什么型号,内核靠它匹配驱动
  • memory:内存布局,地址和大小必须和硬件一致
  • chosen:启动参数,比如控制台用哪个串口

我在项目中遇到过最坑的事,就是DTS里内存地址写错了。板子有2GB内存,我写成了1GB,结果系统只认一半内存,跑起来各种OOM。查了两天才发现是DTS的问题。

注意:DTS的缩进和格式非常严格。少一个分号、多一个空格,编译直接报错。我建议你用dtc工具做语法检查:dtc -I dts -O dtb -o test.dtb test.dts

4.3 海思平台内核裁剪

内核裁剪,说白了就是去掉你用不到的功能。为什么要做?因为海思芯片的Flash空间有限,一个完整的内核动辄几十MB,裁剪后能压到几MB。

裁剪的核心思路:

  1. 关掉不用的驱动:比如你的板子没有WiFi,就把WiFi驱动全关了
  2. 去掉调试选项:生产环境不需要printk调试,关掉能省不少空间
  3. 模块化:把不常用的驱动编译成模块,按需加载

我曾经给一个客户做项目,他们要求内核必须小于4MB。我花了三天时间,把能关的全关了,最后压到3.8MB。嗯,刚好达标。那几天我做梦都在看menuconfig的选项。

这里给个裁剪清单参考:

功能模块 裁剪建议 节省空间
网络协议栈 只保留IPv4,关掉IPv6 约200KB
文件系统 只保留ext4和squashfs 约500KB
调试选项 关掉所有DEBUG选项 约1MB
不用的驱动 按硬件实际需求裁剪 视情况而定
核心原则:裁剪不是越狠越好。我曾经为了省空间,把USB驱动也关了,结果客户后来要接USB摄像头,只能重新编译内核。所以,留点余量。

4.4 内核编译与烧录

编译这一步,其实没什么花头。就是执行几条命令:

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make menuconfig
make -j4 Image dtbs

这里要注意几个点:

  • 交叉编译器:必须用海思提供的工具链,版本不对会编译失败
  • -j参数:根据你的CPU核心数来设,我一般用-j4或-j8
  • Image vs zImage:海思平台一般用Image,因为bootloader支持

编译完成后,你会得到两个关键文件:

  • Image:内核镜像
  • xxx.dtb:设备树二进制文件

烧录方式取决于你的bootloader。如果是uboot,我一般用tftp下载:

tftp 0x80000000 Image
tftp 0x82000000 xxx.dtb
booti 0x80000000 - 0x82000000

嗯,这里有个坑。我记得有一次烧录后系统起不来,查了半天发现是dtb地址和内核地址重叠了。内核加载时会覆盖dtb的数据,导致设备树损坏。后来我把dtb放到0x82000000,问题解决。

我的调试技巧:第一次烧录时,在uboot里用printenv检查bootargs是否正确。特别是console参数,写错了就看不到任何输出。

最后说一句,内核移植不是一次就能成功的。我做过几十次移植,每次都会遇到新问题。但只要你掌握了这几个步骤——源码、配置、DTS、裁剪、编译、烧录——剩下的就是耐心调试了。嗯,慢慢来,总会跑起来的。