第二章 开发环境搭建:Linux内核驱动开发环境配置、交叉编译工具链安装、目标板与主机通信设置
好,咱们直接进入正题。这一章要解决的是「工欲善其事,必先利其器」的问题。很多新手朋友一上来就急着写代码,结果编译都过不了,或者烧进去跑不起来——说白了,环境没搭对。我见过太多人卡在这一步,一卡就是好几天。
我个人习惯,先把环境理清楚再动手。这样后面调试的时候,你知道问题出在代码上,还是出在环境上。嗯,咱们一步步来。
2.1 Linux内核驱动开发环境配置
先说说宿主机环境。你总得有个Linux系统吧?我建议用Ubuntu 20.04 LTS或者22.04 LTS。为什么?因为社区支持好,遇到问题搜一下就有答案。你非要用Arch Linux或者Gentoo,我也拦不住你,但别到时候编译内核报错来找我诉苦。
核心依赖包清单:
sudo apt-get update
sudo apt-get install build-essential kernel-package
sudo apt-get install libncurses-dev libssl-dev
sudo apt-get install bison flex libelf-dev
sudo apt-get install bc rsync kmod
这里有个坑。我记得有一次帮一个团队搭环境,他们装的是Ubuntu 18.04,结果libelf-dev这个包死活装不上。后来发现是源的问题。你想想看,一个包装不上,整个内核编译就卡住了。所以,我建议你装完系统后第一时间换国内源,比如清华源或者阿里源。
小技巧: 如果你不确定哪些包已经装了,可以用 dpkg -l | grep <包名> 检查。别一股脑全装,有些包版本冲突会让你头疼。
2.2 交叉编译工具链安装
接下来是重头戏——交叉编译工具链。你想想看,你的开发机是x86架构,目标板可能是ARM、RISC-V或者MIPS。你不能直接在x86上编译一个x86的程序扔给ARM跑,对吧?这就需要交叉编译。
我常用的方式有两种:
- 从芯片厂商获取预编译工具链——比如ARM官方的gcc-arm-none-eabi,或者NXP、TI提供的SDK里自带的工具链。
- 自己用crosstool-NG编译——这个适合有特殊需求的老手,新手不建议折腾。
以ARM Cortex-A系列为例,我一般这样装:
# 下载ARM官方工具链
wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf.tar.xz
# 解压到指定目录
tar -xf gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf.tar.xz -C /opt/
# 配置环境变量
export PATH=$PATH:/opt/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin
注意: 环境变量最好写到 ~/.bashrc 或者 /etc/profile 里,不然每次开终端都要重新export。我曾经因为这个浪费了半小时,还以为工具链坏了。
验证工具链是否装好,很简单:
arm-none-linux-gnueabihf-gcc --version
如果能看到版本号,说明成了。如果报错说找不到命令,嗯,检查一下路径写对了没。
2.3 目标板与主机通信设置
环境搭好了,工具链也装上了,接下来得让主机和目标板能「说话」。常见的通信方式有几种:
| 通信方式 | 适用场景 | 速度 | 我个人的推荐 |
|---|---|---|---|
| 串口(UART) | 调试信息输出、控制台 | 慢(115200bps常见) | 必备,每个板子都要有 |
| 网络(SSH/NFS) | 文件传输、远程调试 | 快(100M/1Gbps) | 开发阶段首选 |
| JTAG/SWD | 硬件调试、单步执行 | 中等 | 调试内核崩溃时用 |
| USB(ADB/DFU) | 烧录镜像、文件传输 | 快 | 安卓板子常用 |
我个人最常用的是「串口 + 网络」的组合。串口用来打印内核启动日志,网络用来挂载根文件系统和传输驱动模块。
2.3.1 串口配置
串口工具我推荐 minicom 或者 screen。minicom功能全,screen轻量。我习惯用screen:
# 查看串口设备
ls /dev/ttyUSB* # 或者 /dev/ttyACM*
# 连接串口(波特率通常115200)
sudo screen /dev/ttyUSB0 115200
避坑指南: 我曾经遇到过串口线接触不良,导致打印信息断断续续。后来换了一根带屏蔽的USB转串口线,问题就解决了。别小看一根线,它能让你怀疑人生。
2.3.2 网络配置(NFS挂载)
开发驱动时,每次修改都要重新烧录镜像,太慢了。我建议用NFS挂载根文件系统。这样你在主机上编译好的驱动模块,目标板直接就能用。
主机端配置:
# 安装NFS服务器
sudo apt-get install nfs-kernel-server
# 编辑配置文件
sudo vim /etc/exports
# 添加一行(假设共享目录为 /home/user/nfsroot)
/home/user/nfsroot *(rw,sync,no_subtree_check,no_root_squash)
# 重启服务
sudo systemctl restart nfs-kernel-server
目标板端(U-Boot中设置):
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.10
setenv nfsroot /home/user/nfsroot
setenv bootargs console=ttyS0,115200 root=/dev/nfs nfsroot=$serverip:$nfsroot ip=$ipaddr
boot
关键点: 主机和目标板的IP要在同一个网段。别问我为什么知道——我见过有人把主机设成192.168.1.x,目标板设成10.0.0.x,然后折腾了一下午。
2.3.3 文件传输
有时候你只需要传一个.ko文件,不想挂载整个NFS。这时候可以用 scp 或者 tftp。
我习惯用scp:
# 从主机传到目标板
scp my_driver.ko root@192.168.1.100:/root/
如果目标板没有SSH服务,那就用tftp。U-Boot自带tftp客户端,很方便:
# 在U-Boot中
tftp 0x82000000 my_driver.ko
2.4 验证环境是否可用
所有东西都配好了,怎么知道能不能用?写一个最简单的Hello World驱动试试:
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello, World! This is my first driver.\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, World!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
编译命令:
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
如果用的是交叉编译,记得指定ARCH和CROSS_COMPILE:
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- -C /path/to/kernel/source M=$(pwd) modules
编译成功后,把生成的.ko文件传到目标板,然后:
insmod hello.ko
rmmod hello.ko
dmesg | tail
如果能看到打印信息,恭喜你,环境搭好了!
最后提醒一句: 内核版本一定要匹配。你主机上编译用的内核源码版本,必须和目标板上运行的内核版本一致。否则insmod会报错,说「Invalid module format」。我当年第一次搞驱动开发,就栽在这个坑里,折腾了两天才发现是版本没对上。
好了,环境搭建就讲到这里。下一章咱们开始写真正的驱动代码。记住,环境搭得越稳,后面写代码越顺。别急着赶进度,把基础打牢了再说。