第一章 开发环境搭建:交叉编译工具链配置、TFTP/NFS网络文件系统搭建、串口终端与调试工具

各位同学,大家好。我是老李,在车载Linux这行摸爬滚打了十多年。今天咱们正式开始《车载Linux内核移植与驱动开发实战》的第一课。

做嵌入式开发,尤其是车载Linux,第一步不是写代码,而是搭环境。这就像盖房子,地基没打好,后面全是坑。我见过太多新手,甚至有些老手,因为环境问题浪费好几天时间。所以,这一章咱们把基础打牢。

1.1 交叉编译工具链配置

先说说交叉编译。你想想看,你的电脑是x86架构,但车机芯片通常是ARM架构。你不能直接在电脑上编译出ARM能跑的程序,对吧?这就需要交叉编译工具链。

说白了,交叉编译就是在你的PC上,用一套特殊的编译器,生成目标板(比如ARM)能执行的二进制文件。

1.1.1 工具链的选择

我个人习惯用Linaro提供的工具链,稳定且社区支持好。当然,芯片厂商也会提供自己的工具链,比如NXP、TI、瑞萨。我的建议是:

  • 前期学习:用Linaro的gcc-arm-linux-gnueabihf,通用性强。
  • 项目实战:用芯片厂商官方提供的工具链,针对性更强,性能优化更好。

重要提醒:工具链的版本一定要和你的内核版本匹配。版本差太多,编译会报各种莫名其妙的错误。

1.1.2 安装与配置

安装其实很简单,解压就行。但配置环境变量这一步,很多人会栽跟头。

# 下载工具链(以Linaro 7.5为例)
wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz

# 解压到指定目录
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/

# 配置环境变量(写入~/.bashrc)
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm

# 使配置生效
source ~/.bashrc

# 验证安装
arm-linux-gnueabihf-gcc --version

我的小技巧:我习惯在项目根目录下创建一个 setenv.sh 脚本,把环境变量写进去。每次打开新终端,直接 source setenv.sh,干净利落。这样不同项目之间不会互相干扰。

1.1.3 避坑指南

我曾经在一个项目里,编译内核时报错说找不到 gnueabihf 相关的头文件。查了半天,发现是 CROSS_COMPILE 变量末尾忘了加短横线 -。嗯,这里要注意,这个短横线不能少。

还有一次,同事把32位和64位的工具链搞混了,编译出来的内核在板子上直接起不来。所以,下载前一定看清楚是 arm-linux-gnueabihf(32位)还是 aarch64-linux-gnu(64位)。

1.2 TFTP/NFS网络文件系统搭建

开发阶段,你不可能每次修改都重新烧写整个系统。太慢了。所以我们需要网络文件系统。TFTP用来传输内核和设备树,NFS用来挂载根文件系统。

为什么这么做?因为这样你修改了驱动或者应用程序,只需要重新编译,然后重启板子,就能通过网络加载新的文件。开发效率提升不止一个档次。

1.2.1 TFTP服务器搭建

TFTP很简单,就是用来传文件的。我们用它来传内核镜像(zImage)和设备树(.dtb)。

# 安装TFTP服务器(Ubuntu)
sudo apt-get install tftpd-hpa

# 配置TFTP
sudo vim /etc/default/tftpd-hpa

# 内容如下:
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpboot"   # 你的tftp根目录
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-l -c -s"

# 创建目录并设置权限
sudo mkdir -p /tftpboot
sudo chmod 777 /tftpboot

# 重启服务
sudo systemctl restart tftpd-hpa

注意:防火墙要放行UDP 69端口。我遇到过好几次,服务器搭好了,板子就是连不上,最后发现是防火墙没关。

1.2.2 NFS服务器搭建

NFS用来共享根文件系统。板子启动时,通过网络挂载你PC上的文件系统目录。

# 安装NFS服务器
sudo apt-get install nfs-kernel-server

# 配置导出目录
sudo vim /etc/exports

# 添加一行(假设你的根文件系统在 /home/li/rootfs)
/home/li/rootfs *(rw,sync,no_subtree_check,no_root_squash)

# 重启NFS服务
sudo systemctl restart nfs-kernel-server

# 验证
showmount -e localhost

我的经验no_root_squash 这个选项很重要。不加的话,板子上的root用户会被映射成nobody,很多操作会权限不足。我曾经因为这个,调试一个驱动时,死活写不了设备节点,折腾了一下午。

1.2.3 U-Boot中的网络启动设置

板子启动时,需要在U-Boot里设置好网络参数。这里给个典型配置:

# 设置IP地址(板子IP)
setenv ipaddr 192.168.1.100
# 设置服务器IP(你的PC)
setenv serverip 192.168.1.10
# 设置网关
setenv gatewayip 192.168.1.1
# 设置网络掩码
setenv netmask 255.255.255.0

# 从TFTP下载内核和设备树
tftp 0x82000000 zImage
tftp 0x83000000 board.dtb

# 设置NFS根文件系统启动参数
setenv bootargs 'console=ttyS0,115200 root=/dev/nfs nfsroot=192.168.1.10:/home/li/rootfs rw ip=192.168.1.100:192.168.1.10:192.168.1.1:255.255.255.0::eth0:off'

# 启动内核
bootz 0x82000000 - 0x83000000

你想想看,有了这套流程,你改完代码,编译,然后重启板子,几秒钟就能看到效果。比起烧写Flash,快太多了。

1.3 串口终端与调试工具

串口是嵌入式开发者的眼睛。没有串口,你根本不知道板子启动到哪一步了,内核为什么崩溃。

1.3.1 串口终端配置

我推荐用 minicom 或者 screen。我个人更习惯用 screen,因为它轻量,而且可以轻松回滚。

# 安装screen
sudo apt-get install screen

# 连接串口(假设串口设备是 /dev/ttyUSB0,波特率115200)
screen /dev/ttyUSB0 115200

# 退出screen:Ctrl+A,然后按K,再按Y确认

关键参数:波特率115200,数据位8,停止位1,无校验。这是嵌入式Linux的标配。如果板子输出乱码,先检查波特率对不对。

1.3.2 调试工具

除了串口终端,还有几个工具是必备的:

工具 用途 我的评价
dmesg 查看内核日志 调试驱动时,这是第一手信息
strace 跟踪系统调用 应用程序出问题时,用它看卡在哪
gdb 调试应用程序 配合gdbserver做远程调试,很强大
perf 性能分析 找性能瓶颈时,用它准没错
devmem2 读写物理内存 调试寄存器时,我经常用它

举个例子,调试一个I2C驱动时,我会先用 devmem2 直接读写I2C控制器的寄存器,确认硬件没问题。然后再看驱动代码。这样能快速定位是硬件问题还是软件问题。

警告:用 devmem2 操作物理地址时,一定要小心。写错了寄存器,可能导致系统死机甚至硬件损坏。我在项目中就见过有人不小心把DDR配置寄存器改乱了,板子直接变砖。

1.4 本章小结

好了,这一章的内容就这些。我们搭建了交叉编译环境,配好了TFTP和NFS服务器,也搞定了串口终端。这些是后续所有实验的基础。

说实话,这些步骤看起来琐碎,但每一步都很关键。我刚开始做车载项目时,也是从这些基础环境一步步走过来的。环境搭好了,后面写驱动、移植内核,才能事半功倍。

下一章,我们会正式开始内核的编译和启动流程分析。到时候,这些环境就派上用场了。

课后练习:按照本章步骤,在你的PC上搭建好完整的开发环境。然后用串口连接你的开发板,确保能从U-Boot启动到NFS根文件系统。这是后续所有实验的入场券。