第四章:嵌入式Linux基础
说实话,很多刚接触DPU开发的朋友,一上来就被嵌入式Linux给劝退了。我当年也是这样,看着那一堆编译选项、设备树文件、根文件系统,头都大了。但你别怕,这一章咱们就把这些基础打扎实。
嵌入式Linux说白了,就是给专用硬件定制的Linux系统。它不像你电脑上的Ubuntu那么"通用",而是精简到只保留你需要的东西。对于SmartNIC开发来说,理解这些基础,就像盖房子要先打地基一样重要。
4.1 Linux内核编译与设备树
内核编译,嗯,这是每个嵌入式开发者都绕不开的坎。我记得第一次编译内核时,花了整整一个下午,结果还失败了。后来才发现,其实就是少装了一个依赖包。
4.1.1 内核编译流程
先说说基本流程。你拿到一个内核源码包,比如Linux 5.10 LTS,然后要做这几步:
- 配置内核:用
make menuconfig或者make defconfig - 编译内核:
make -j4(-j后面的数字看你CPU核心数) - 编译模块:
make modules - 安装模块:
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,因为它简单直接。你只需要:
make menuconfig选择目标架构和软件包make一键编译- 在
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的学习没有捷径,多动手、多踩坑,经验就是这么积累起来的。下次遇到问题,别急着问别人,先自己查查/proc和/sys,说不定答案就在那里。