3、嵌入式Linux基础:内核裁剪与编译、根文件系统制作(Busybox)、设备树(DTS)基本概念、GPIO与UART驱动加载
好,咱们进入第三章。这一章是硬骨头,但也是TTU开发中最核心的环节。说白了,你手里的Linux系统不是Windows,不能拿来就用。你得自己动手,给它“减肥”、“装系统”、“配驱动”。我当年第一次做这个,烧坏过三块开发板……嗯,咱们一步步来。
3.1 内核裁剪与编译:给Linux“瘦身”
为什么需要裁剪?你想想看,一个标准Linux内核有上万个驱动,而你的TTU只需要网络、串口、GPIO、I2C等几个模块。全编译进去,不仅镜像大,启动还慢。我习惯的做法是:先跑一遍默认配置,再一点点关掉不需要的。
操作流程:
- 获取内核源码:从芯片厂商(比如NXP、TI)官网下载对应BSP包。
- 配置内核:执行
make menuconfig,进入图形化界面。 - 裁剪要点:
- 关掉不用的文件系统(如XFS、Btrfs)
- 关掉不用的网卡驱动(只留你板子上的)
- 关掉调试选项(如KGDB、DEBUG_FS)——产品发布前一定要关
- 编译:
make -j4 zImage,生成内核镜像。
核心命令示例:
# 清理旧配置
make distclean
# 加载默认配置(以imx6为例)
make imx_v7_defconfig
# 进入菜单配置
make menuconfig
# 编译内核
make -j4 zImage
# 编译设备树
make -j4 dtbs
我的经验: 裁剪时别贪心。我曾经为了省空间,把USB驱动全关了,结果现场调试时没法插U盘拷日志……后来我学乖了,至少保留一个USB Mass Storage支持。
3.2 根文件系统制作(Busybox):从零搭建“小Linux”
内核跑起来后,还需要一个根文件系统。Busybox就是干这个的——它把常用的Linux命令(ls、cp、sh等)打包成一个可执行文件,体积只有几百KB。我刚开始用的时候,觉得这东西太神奇了。
制作步骤:
- 下载Busybox源码:
git clone git://busybox.net/busybox.git - 配置:
make menuconfig,选择“Build Busybox as a static binary”(静态编译,避免动态库依赖)。 - 编译安装:
make && make install,生成_install目录。 - 手动补充目录:在
_install下创建dev/、proc/、sys/、etc/等目录。 - 添加init脚本:在
etc/inittab和etc/init.d/rcS中配置启动流程。
最小化根文件系统结构:
rootfs/
├── bin/ # Busybox命令
├── sbin/ # 系统管理命令
├── dev/ # 设备节点(需手动创建或使用devtmpfs)
├── etc/
│ ├── inittab # 初始化配置
│ └── init.d/
│ └── rcS # 启动脚本
├── lib/ # 动态库(如果Busybox不是静态编译)
├── proc/ # 挂载点
├── sys/ # 挂载点
└── tmp/ # 临时文件
避坑指南: 我曾经在 etc/inittab 里忘了加 ::sysinit:/etc/init.d/rcS,结果系统启动后直接卡在“Please press Enter to activate this console”……折腾了两小时才发现。记住:inittab的语法很严格,每行格式不能错。
3.3 设备树(DTS)基本概念:硬件描述语言
设备树是什么?说白了,就是告诉内核“你的板子上有哪些硬件,它们怎么连接的”。以前老内核用板级文件(board-xxx.c),每次换硬件都要改源码。现在用DTS,改一个文本文件就行。我个人觉得,这是Linux嵌入式开发最优雅的设计之一。
DTS语法核心:
- 节点(node):每个硬件设备是一个节点,用
{}包裹。 - 属性(property):描述节点特性,如
compatible = "vendor,device"。 - reg:定义地址和大小,如
reg = <0x0209C000 0x4000>。 - 中断:
interrupts = <0 67 4>(中断号、触发方式)。
一个简单的GPIO节点示例:
/ {
gpio-leds {
compatible = "gpio-leds";
led-red {
label = "red";
gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
};
};
我的习惯: 写DTS时,先用 dtc -I dts -O dtb xxx.dts 编译成dtb,再用 dtc -I dtb -O dts xxx.dtb 反编译检查。这样能快速发现语法错误。嗯,这招是当年被坑出来的。
3.4 GPIO与UART驱动加载:让硬件“动”起来
驱动加载有两种方式:编译进内核(built-in)和编译成模块(module)。我建议:核心驱动(如UART、GPIO控制器)编译进内核,非核心的(如LED、按键)用模块。为什么?因为模块可以在系统运行时动态加载/卸载,调试方便。
GPIO驱动加载:
- 确保内核配置了
CONFIG_GPIOLIB=y。 - 在DTS中定义GPIO控制器节点(芯片厂商通常已提供)。
- 用户空间通过
/sys/class/gpio操作:echo 18 > export、echo out > direction、echo 1 > value。
UART驱动加载:
- 内核配置
CONFIG_SERIAL_IMX=y(以i.MX6为例)。 - DTS中使能UART节点:
status = "okay";,并配置引脚复用(pinctrl)。 - 系统启动后,
/dev/ttymxc0等设备节点自动出现。
UART的DTS配置片段:
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
status = "okay";
};
&iomuxc {
pinctrl_uart1: uart1grp {
fsl,pins = <
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
>;
};
};
重要提醒: 加载驱动前,一定要检查引脚复用冲突。我曾经在同一个项目里,把UART2的TX引脚同时配成了GPIO,结果串口发数据时电平乱跳,调试了整整一天。记住:一个引脚一次只能干一件事。
好了,这一章的内容就这些。内核裁剪、根文件系统、设备树、驱动加载——这四个环节环环相扣。你只要动手做一遍,就会发现其实没那么难。下一章咱们聊聊TTU的通信协议栈,那才是真正考验功底的地方。