第四章:嵌入式Linux基础

说实话,很多刚接触DPU开发的朋友,一上来就被嵌入式Linux给劝退了。我当年也是这样,看着那一堆编译选项、设备树文件、根文件系统,头都大了。但你别怕,这一章咱们就把这些基础打扎实。

嵌入式Linux说白了,就是给专用硬件定制的Linux系统。它不像你电脑上的Ubuntu那么"通用",而是精简到只保留你需要的东西。对于SmartNIC开发来说,理解这些基础,就像盖房子要先打地基一样重要。

4.1 Linux内核编译与设备树

内核编译,嗯,这是每个嵌入式开发者都绕不开的坎。我记得第一次编译内核时,花了整整一个下午,结果还失败了。后来才发现,其实就是少装了一个依赖包。

4.1.1 内核编译流程

先说说基本流程。你拿到一个内核源码包,比如Linux 5.10 LTS,然后要做这几步:

  1. 配置内核:用make menuconfig或者make defconfig
  2. 编译内核make -j4(-j后面的数字看你CPU核心数)
  3. 编译模块make modules
  4. 安装模块make modules_install

这里我特别想提醒你一点:配置内核时,别贪多。我在项目中见过有人把能选的驱动全选上,结果编译出来的内核镜像有50多MB,根本放不进Flash里。你想想看,SmartNIC的存储空间多宝贵啊,能省则省。

核心要点:内核配置的原则是"够用就好"。只编译你硬件需要的驱动,其他的统统去掉。

4.1.2 设备树(Device Tree)

设备树这东西,说白了就是描述硬件信息的"说明书"。以前老内核用ACPI,但嵌入式设备太灵活了,ACPI那套太重。于是设备树就诞生了。

一个典型的设备树文件长这样:

/dts-v1/;

/ {
    model = "MySmartNIC Board";
    compatible = "vendor,myboard-v1";

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a72";
            reg = <0x0>;
        };
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;  /* 1GB RAM */
    };

    uart0: serial@1c090000 {
        compatible = "ns16550a";
        reg = <0x1c090000 0x1000>;
        interrupts = <0 33 4>;
        clock-frequency = <24000000>;
    };
};

看到没?每个硬件设备都有一个compatible属性,内核就是靠这个字符串来匹配驱动的。我曾经踩过一个坑:设备树里把UART的中断号写错了,结果串口死活不工作。查了两天才发现,原来是设备树和硬件手册对不上。

避坑指南:设备树里的寄存器地址、中断号一定要和硬件手册一一对应。我曾经因为一个地址偏移量写错,导致整个DMA控制器无法工作,排查了整整三天。

4.2 根文件系统制作

根文件系统,就是Linux启动后挂载的第一个文件系统。它包含了init程序、库文件、配置文件等。对于嵌入式设备,我们通常用Buildroot或Yocto来制作。

4.2.1 Buildroot:轻量级选择

我个人比较喜欢Buildroot,因为它简单直接。你只需要:

  1. make menuconfig选择目标架构和软件包
  2. make一键编译
  3. output/images/目录下拿到镜像文件

Buildroot适合那种"我要一个能跑起来的最小系统"的场景。比如SmartNIC的板卡管理控制器,用Buildroot就非常合适。

4.2.2 Yocto:企业级方案

Yocto就复杂多了。它用BitBake构建系统,有层(Layer)的概念。说实话,Yocto的学习曲线比较陡,但一旦掌握了,它的灵活性和可定制性是无与伦比的。

一个简单的Yocto配方(recipe)示例:

SUMMARY = "Simple helloworld application"
LICENSE = "MIT"
SRC_URI = "file://helloworld.c"

S = "${WORKDIR}"

do_compile() {
    ${CC} helloworld.c -o helloworld
}

do_install() {
    install -d ${D}${bindir}
    install -m 0755 helloworld ${D}${bindir}
}

Yocto的好处是,你可以精确控制每个软件包的版本、补丁、编译选项。对于需要长期维护的产品,Yocto是首选。

我的建议:刚开始学,先用Buildroot快速上手。等你对嵌入式Linux有感觉了,再挑战Yocto。别一上来就搞Yocto,容易劝退。

4.3 SSH与串口调试

调试嵌入式设备,就靠这两样:SSH和串口。SSH用于网络调试,串口用于底层调试。

4.3.1 串口调试

串口调试是最基础的。你只需要一根USB转串口线,然后用minicom或screen连接:

# 安装minicom
sudo apt-get install minicom

# 配置串口
minicom -s

# 或者直接用screen
screen /dev/ttyUSB0 115200

波特率通常用115200,数据位8位,无校验,1位停止位。嗯,这些参数记不住也没关系,大多数情况下默认就行。

我记得有一次,板子死活起不来,串口一点输出都没有。我以为是硬件坏了,结果发现是串口线接反了TX和RX。这种低级错误,犯过一次就再也不会犯了。

4.3.2 SSH调试

SSH就简单了。只要板子网络通了,直接:

ssh root@192.168.1.100

但要注意,第一次连接时,SSH会提示你确认主机密钥。这是安全机制,别跳过。

调试技巧:我习惯同时开两个终端,一个用串口看内核日志,一个用SSH执行命令。这样既能实时看到内核的打印信息,又能方便地操作文件系统。

4.4 常用系统命令与性能监控

这部分其实和普通Linux差不多,但嵌入式环境有些特殊。我挑几个最常用的说说。

4.4.1 系统信息查看

命令作用我的用法
uname -a查看内核版本先确认内核是不是我编译的那个
cat /proc/cpuinfo查看CPU信息检查CPU核心数和频率
free -h查看内存使用看内存够不够用
df -h查看磁盘使用看Flash空间还剩多少

4.4.2 性能监控

对于SmartNIC开发,性能监控特别重要。你想想看,网卡处理数据包,延迟和吞吐量是命根子。

# 查看CPU负载
top
htop    # 更友好的版本

# 查看网络流量
ifconfig eth0
ip -s link show eth0

# 查看中断分布
cat /proc/interrupts

# 查看内存映射
cat /proc/meminfo

我最常用的是top/proc/interrupts。为什么?因为SmartNIC的数据包处理通常依赖中断,如果中断分布不均匀,某个CPU核心负载过高,就会导致丢包。

性能调优小技巧:用taskset命令把中断处理绑定到特定CPU核心上,可以避免缓存抖动。我曾在项目中用这个技巧,把吞吐量提升了15%。

知识体系总览

为了让你更直观地理解这一章的知识结构,我画了一张图:

嵌入式Linux基础 - 知识体系 嵌入式Linux基础 内核编译与设备树 make menuconfig → make -j4 设备树:硬件说明书 compatible属性匹配驱动 根文件系统 Buildroot:轻量级 Yocto:企业级方案 Layer + BitBake构建 SSH与串口调试 串口:minicom/screen SSH:网络调试 双终端并行调试 性能监控 top / htop / free /proc/interrupts 中断分布

这张图把本章的四个核心模块串起来了。内核编译和设备树是基础,根文件系统是运行环境,调试方法是开发工具,性能监控是优化手段。四者缺一不可。

好了,这一章的内容就到这里。记住,嵌入式Linux的学习没有捷径,多动手、多踩坑,经验就是这么积累起来的。下次遇到问题,别急着问别人,先自己查查/proc/sys,说不定答案就在那里。


专注资料整理