第三章:Bootloader与内核:U-Boot移植与配置、Linux内核裁剪与编译、设备树(DTS)基础
各位同学,今天咱们聊聊嵌入式Linux在POS机上电后,最先发生的那些事。说白了,就是Bootloader怎么把内核请进内存,内核又怎么认识你的硬件。这部分我踩过的坑最多,咱们一个一个来拆解。
3.1 U-Boot移植与配置:让板子先跑起来
U-Boot,全称是Universal Bootloader。你可以把它理解成PC上的BIOS,但比BIOS灵活得多。POS机一上电,CPU首先执行的就是U-Boot。它的任务很简单:初始化硬件,加载内核。
我刚开始做POS机项目时,拿到一块新板子,第一件事就是移植U-Boot。嗯,这里要注意,移植不是从零写代码,而是基于现有板级配置修改。
3.1.1 获取与解压U-Boot源码
从官方仓库或芯片厂商的Github拉取代码。我个人习惯用denx.de的官方主线版本,因为社区支持好。解压后,你会看到这样的目录结构:
u-boot/
├── arch/ # CPU架构相关代码
├── board/ # 板级支持包
├── configs/ # 默认配置文件
├── drivers/ # 驱动
├── dts/ # 设备树源文件
└── include/ # 头文件
关键在 board/ 和 configs/ 目录。POS机常用的主控,比如全志、瑞芯微、NXP i.MX系列,都有现成的参考板。
3.1.2 配置U-Boot
配置U-Boot就像给汽车选配置。你想想看,你的POS机用的是什么CPU?多大的DDR?有没有NAND Flash?这些都要告诉U-Boot。
以我手头一个基于NXP i.MX6ULL的POS机项目为例:
make mx6ull_pos_defconfig # 假设我们已经创建了默认配置
make menuconfig # 图形化配置界面
在menuconfig里,我通常会做这几件事:
- 设置串口波特率:POS机调试串口一般用115200,别搞错了,否则打印乱码。
- 配置DDR参数:这个最坑。DDR时序参数错了,板子直接死给你看。我曾经因为一个时序参数抄错了,折腾了两天。
- 使能网络支持:POS机需要网络升级固件,所以网卡驱动必须开。
- 关闭不需要的功能:比如USB HOST、复杂的文件系统支持,能省则省,减少启动时间。
3.1.3 编译与烧录
配置好了,编译就简单了:
make -j4
# 生成 u-boot.imx 或 u-boot.bin,取决于芯片
烧录方式因芯片而异。i.MX系列可以用SD卡启动,也可以用USB下载工具。我记得第一次烧录时,忘了设置启动模式跳线,板子死活没反应。后来发现是拨码开关没拨对。
3.2 Linux内核裁剪与编译:给POS机瘦身
POS机不是服务器,不需要跑全套桌面Linux。内核裁剪,说白了就是去掉你用不到的驱动和功能。一个完整的Linux内核有上万个驱动,你的POS机可能只用到了几十个。
3.2.1 获取内核源码
同样,从芯片厂商的官方仓库拉取。比如全志的BSP内核,或者主线内核。我建议用厂商提供的长期支持版本,因为驱动适配好。
git clone https://github.com/xxx/linux-pos.git
cd linux-pos
make ARCH=arm pos_defconfig # 加载POS机默认配置
3.2.2 裁剪步骤
进入配置界面:
make ARCH=arm menuconfig
这里我一般按这个顺序裁剪:
- 关闭不用的架构支持:只保留ARM,其他全关。
- 关闭不用的驱动:POS机不需要显卡驱动、声卡驱动、蓝牙驱动。把这些都去掉,内核能小一半。
- 精简文件系统支持:只保留ext4、FAT、NFS(调试用)。其他像Btrfs、XFS,用不到就关。
- 关闭调试选项:发布版本一定要关掉
Kernel debugging,否则内核又大又慢。
关键点:裁剪后,内核镜像大小最好控制在3MB以内。POS机的Flash空间很宝贵,你想想看,一个内核占10MB,那应用放哪?
3.2.3 编译与验证
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4
# 生成 zImage 或 uImage
编译完成后,用 size 命令看看内核大小:
arm-linux-gnueabihf-size vmlinux
# 输出类似:text data bss dec hex
# 2845672 123456 78901 3048029 2e85dd
如果text段超过4MB,说明你裁剪得还不够狠。我曾经把一个内核从8MB裁到2.8MB,启动时间从12秒降到了4秒。效果立竿见影。
3.3 设备树(DTS)基础:让内核认识你的硬件
设备树,英文叫Device Tree,缩写DTS。它的作用就是告诉内核:你的POS机上有哪些硬件,它们怎么连接的。
以前的内核,硬件信息都硬编码在C代码里。换一个板子就要改代码重新编译。有了设备树,硬件描述和内核代码分开了。你改硬件,只需要改DTS文件,不用动内核。
3.3.1 DTS文件结构
一个典型的POS机DTS文件长这样:
/dts-v1/;
#include "imx6ull.dtsi" // 包含CPU核心定义
/ {
model = "POS Terminal v1.0";
compatible = "mycompany,pos-v1", "fsl,imx6ull";
chosen {
stdout-path = &uart1;
};
memory {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB DDR
};
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
};
&i2c1 {
clock-frequency = <100000>;
status = "okay";
touch: touch@38 {
compatible = "goodix,gt911";
reg = <0x38>;
interrupt-parent = <&gpio1>;
interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
};
};
};
你看,这里定义了内存大小、串口、I2C总线、触摸屏。每个硬件节点都有 compatible 属性,内核通过这个字符串找到对应的驱动。
3.3.2 编译DTS
DTS文件不能直接给内核用,要编译成二进制DTB文件:
dtc -I dts -O dtb -o pos.dtb pos.dts
或者在内核编译时一起生成:
make ARCH=arm dtbs
生成的 pos.dtb 文件,U-Boot会把它加载到内存,然后传递给内核。
3.3.3 调试设备树
设备树写错了,内核可能启动到一半就挂了。我常用的调试方法:
- 查看内核启动日志:用
dmesg | grep -i "dtb\|of\|device tree"看有没有错误。 - 检查设备树节点:在板子上执行
ls /proc/device-tree/,看看节点有没有生成。 - 反编译DTB:用
dtc -I dtb -O dts -o dump.dts pos.dtb看看实际加载的设备树对不对。
3.4 启动流程串联
最后,咱们把整个流程串起来。POS机启动时,大致经历这几个阶段:
| 阶段 | 执行内容 | 时间 |
|---|---|---|
| 1. ROM Code | 芯片内部固件,初始化时钟、DDR | ~100ms |
| 2. U-Boot SPL | 加载U-Boot主程序 | ~200ms |
| 3. U-Boot主程序 | 初始化外设,加载内核和设备树 | ~500ms |
| 4. Linux内核 | 解压、初始化、挂载根文件系统 | ~2s |
| 5. 应用程序 | 启动POS机主程序 | ~1s |
整个启动过程,控制在3-4秒内是比较理想的。如果超过5秒,用户就会觉得卡。我优化过一个项目,把U-Boot的启动延时从2秒降到0.5秒,就是去掉了不必要的驱动初始化和打印信息。
好了,这一章的内容就这些。U-Boot移植、内核裁剪、设备树,这三样是嵌入式Linux开发的看家本领。你把这些搞透了,POS机项目就成功了一半。下一章咱们聊聊根文件系统的构建,那是应用软件的家。