3. SDK架构解析:Processor SDK结构、文件系统布局、内核源码树、U-Boot源码树、设备树(Device Tree)概念
好,咱们进入第三章。这一章我打算把TI的Processor SDK掰开揉碎了讲清楚。说实话,我刚接触TI平台那会儿,第一次解压完SDK,看着满屏的文件夹,心里是有点懵的。但后来做项目多了,发现这套结构其实设计得挺讲究。你只要摸清了它的骨架,后面开发就会顺风顺水。
3.1 Processor SDK整体结构
TI的Processor SDK,说白了就是一个超级大礼包。它把Linux内核、U-Boot、文件系统、工具链、还有各种例程,全给你打包好了。我个人习惯把它理解成「三件套」:
- 引导加载程序 —— 就是U-Boot,负责把硬件初始化好,然后跳进内核。
- 操作系统内核 —— Linux内核,负责管理所有资源。
- 根文件系统 —— 提供用户空间的程序和库。
你下载下来的SDK,解压后大概长这样:
ti-processor-sdk-linux-am335x-evm-XX.XX.XX.XX/
├── bin/ # 一些辅助脚本和工具
├── board-support/ # 板级支持包,U-Boot和内核源码都在这里
├── example-applications/ # 例程,新手必看
├── filesystem/ # 文件系统相关
├── linux-devkit/ # 交叉编译工具链
├── Makefile # 顶层构建脚本
├── Rules.make # 公共编译规则
└── setup.sh # 环境配置脚本
嗯,这里要注意:board-support/ 这个目录是核心。你后面要改内核、改U-Boot,基本都在这里折腾。
核心要点:SDK的设计哲学是「开箱即用」。你执行 setup.sh 配置好环境,然后 make 一下,就能生成完整的系统镜像。但真正做产品时,我建议你还是得把每个组件单独拎出来理解。
3.2 文件系统布局
TI的SDK提供了两种文件系统:一种是基于BusyBox的最小系统,另一种是完整的Tiny Yocto文件系统。我在项目中遇到过,很多新手上来就选大的,结果发现很多用不到的包占空间,还拖慢启动速度。
咱们先看看文件系统里都有啥:
filesystem/
├── arago-base-tisdk-image-am335x-evm.tar.xz # 基础文件系统
├── tisdk-rootfs-image-am335x-evm.tar.xz # 完整文件系统
└── tisdk-rootfs-image-am335x-evm.ubi # UBI镜像,用于NAND Flash
解压后,根文件系统的典型布局是这样的:
| 目录 | 用途 | 我的经验 |
|---|---|---|
/bin |
用户命令(如ls, cp) | BusyBox的软链接都在这里 |
/sbin |
系统管理命令 | ifconfig、reboot这些 |
/etc |
配置文件 | 网络、启动脚本都在这里改 |
/lib |
共享库和内核模块 | 驱动ko文件放这里 |
/usr |
用户程序和数据 | 你写的应用建议放/usr/bin |
/var |
可变数据(日志等) | 调试时经常看/var/log |
小技巧:调试阶段,我习惯用NFS挂载根文件系统。这样在PC上改完代码,目标板重启就能生效,省去了反复烧写的麻烦。具体做法是在U-Boot里设置 root=/dev/nfs nfsroot=...。
3.3 内核源码树
内核源码在 board-support/linux-XX.XX.XX+gitXXXX 目录下。你想想看,Linux内核有几万甚至几十万个文件,但咱们做嵌入式开发,其实只需要关注几个关键目录:
linux-XX.XX.XX/
├── arch/ # 体系结构相关代码
│ └── arm/ # ARM架构
│ ├── boot/dts/ # 设备树文件(重点!)
│ ├── mach-omap2/ # TI OMAP/AM平台相关
│ └── plat-omap/ # OMAP平台通用代码
├── drivers/ # 设备驱动
│ ├── net/ # 网络驱动
│ ├── mmc/ # MMC/SD卡驱动
│ ├── spi/ # SPI驱动
│ └── i2c/ # I2C驱动
├── include/ # 头文件
├── kernel/ # 内核核心代码
└── Documentation/ # 文档,没事多翻翻
我个人习惯,拿到一个新板子,第一件事就是去 arch/arm/boot/dts/ 目录下看看。因为设备树文件直接决定了你的硬件能不能正常工作。
避坑指南:我曾经在某个项目里,因为内核版本和SDK自带的工具链不匹配,编译出来的内核死活起不来。后来发现,TI的SDK对内核版本和工具链版本有严格的对应关系。所以,不要轻易升级内核版本,除非你清楚自己在做什么。
3.4 U-Boot源码树
U-Boot在 board-support/u-boot-XXXX.XX+gitXXXX 目录下。U-Boot的源码结构跟内核有点像,但更轻量:
u-boot-XXXX.XX/
├── arch/ # 架构相关
│ └── arm/ # ARM相关
│ ├── cpu/ # CPU初始化代码
│ └── dts/ # U-Boot自己的设备树
├── board/ # 板级代码
│ └── ti/ # TI系列板子
│ ├── am335x/ # AM335x EVM
│ └── am437x/ # AM437x EVM
├── common/ # 通用代码
├── drivers/ # 驱动
├── include/ # 头文件
│ └── configs/ # 板级配置头文件
└── cmd/ # 命令行命令实现
U-Boot的配置方式跟内核不一样。它用的是 include/configs/ 下的头文件来配置。比如 am335x_evm.h 这个文件,里面定义了内存大小、环境变量位置、启动参数等等。
核心要点:U-Boot的启动流程大致是:SPL(第一级引导)→ U-Boot完整版 → 加载内核。SPL负责初始化DDR和时钟,然后跳转到U-Boot。如果SPL出了问题,板子连灯都不亮。我调试时,习惯在SPL里加串口打印,确认硬件初始化是否通过。
3.5 设备树(Device Tree)概念
设备树,英文叫Device Tree,简称DT。这东西刚出来的时候,我其实挺抵触的。因为以前写内核驱动,直接在C代码里写死硬件地址,多直接啊。但后来发现,设备树确实是个好东西。
说白了,设备树就是用来描述硬件信息的。它告诉内核:你的CPU是什么型号、内存多大、I2C控制器在哪个地址、GPIO怎么用……等等。这样内核就不用把硬件信息写死在代码里了,换一块板子,换个设备树就行。
设备树的文件后缀是 .dts(源文件)和 .dtsi(包含文件)。编译后生成 .dtb(二进制文件)。
咱们看一个简单的例子:
/dts-v1/;
#include "am33xx.dtsi"
/ {
model = "TI AM335x EVM";
compatible = "ti,am335x-evm", "ti,am33xx";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB内存
};
&i2c0 {
status = "okay";
clock-frequency = <400000>;
eeprom@50 {
compatible = "atmel,24c256";
reg = <0x50>;
};
};
};
你看,这个文件描述了:
- 板子型号是AM335x EVM
- 内存起始地址0x80000000,大小256MB
- I2C0控制器使能,频率400KHz
- I2C总线上挂了一个EEPROM,地址0x50
调试技巧:内核启动时,你可以通过 cat /proc/device-tree/ 查看当前使用的设备树。如果某个外设没工作,先检查设备树里有没有对应的节点。我曾经花了一整天查一个SPI驱动的问题,最后发现是设备树里忘了加 status = "okay"。
3.6 设备树的核心语法
设备树的语法其实不复杂,记住几个关键点就行:
| 语法元素 | 说明 | 示例 |
|---|---|---|
/ |
根节点 | / { ... }; |
node-name |
节点名称 | cpu@0 { ... }; |
property |
属性,键值对 | reg = <0x1000 0x100>; |
&label |
引用标签 | &i2c0 { ... }; |
#include |
包含其他dtsi文件 | #include "am33xx.dtsi" |
嗯,这里要特别说一下 compatible 属性。这个属性是用来匹配驱动的。内核在启动时,会遍历设备树里的每个节点,然后根据 compatible 的值去找对应的驱动。比如 compatible = "ti,am335x-uart",内核就会去驱动列表里找名字匹配的驱动来初始化这个串口。
避坑指南:我曾经在移植一个老驱动时,发现设备树里配了 compatible,但驱动就是加载不了。后来查了半天,发现是驱动里的 of_match_table 写错了,跟设备树里的字符串不完全一致。记住,字符串必须完全匹配,大小写、连字符都不能错。
3.7 小结
这一章我们走了一遍SDK的骨架。从整体结构到文件系统,再到内核和U-Boot的源码树,最后重点讲了设备树。你想想看,这些组件是怎么串起来的?
U-Boot启动后,从Flash里读出设备树文件(.dtb)和内核镜像(zImage),然后把设备树的地址传给内核。内核解析设备树,知道有哪些硬件,然后加载对应的驱动。驱动初始化完成后,挂载根文件系统,最后启动用户空间的init进程。
这个流程,说白了就是嵌入式Linux的「三板斧」。后面几章,我们会逐一深入每个环节。下一章,咱们聊聊如何搭建开发环境,以及怎么用SDK编译出第一个能跑的系统。
课后思考:如果你拿到一块新的TI板子,但SDK里没有对应的设备树文件,你会怎么创建?从哪里入手?