第一章:嵌入式Linux驱动开发概述

各位同学好,我是你们这门课的老朋友。今天咱们聊聊嵌入式Linux驱动开发的入门知识。说实话,我刚开始接触这块时也是一头雾水,后来踩了不少坑才慢慢摸清门道。这一章,我会把最核心的东西掰开揉碎了讲给你听。

1.1 嵌入式系统简介

嵌入式系统,说白了就是「专机专用」的计算机系统。跟咱们桌上那台能打游戏、能写文档的PC不一样,嵌入式系统通常只干一件事——比如你的路由器只管转发数据包,智能手表只管计步和显示时间。

我个人习惯把嵌入式系统拆成三块来看:

  • 硬件层:处理器(比如咱们用的i.MX8M)、内存、外设接口
  • 软件层:Bootloader、内核、根文件系统、应用程序
  • 中间层:驱动——就是连接硬件和软件的桥梁

你想想看,没有驱动,内核怎么知道怎么操作GPIO?怎么读写I2C设备?驱动就是那个「翻译官」,把硬件的电信号翻译成软件能理解的API调用。

核心观点:嵌入式Linux驱动开发,本质上就是写代码让内核能正确控制硬件外设。这件事做好了,整个系统就稳了。

我在项目中遇到过一件事:有个同事写了个SPI驱动,功能测试全过,结果一上产线就丢数据。查了两天才发现是时钟极性配置错了——嗯,这种细节问题,后面我会专门讲怎么避坑。

1.2 Linux内核架构

Linux内核是个庞然大物,但咱们搞驱动开发,不需要把整个内核都啃下来。你只需要关注几个关键模块:

模块 作用 驱动开发者关注点
进程调度 决定哪个进程用CPU 中断上下文、休眠机制
内存管理 分配和回收内存 DMA缓冲区、kmalloc/vmalloc
文件系统 管理存储设备 VFS接口、设备节点
设备驱动 控制硬件外设 这就是咱们的主战场

内核里驱动占了很大一部分代码量。我刚开始学的时候,总觉得内核源码像天书。后来发现,其实驱动框架是有套路的——字符设备、块设备、网络设备,每种都有固定的模板。

我的建议:别一开始就扎进源码细节。先理解驱动在内核中的位置,搞清楚它跟VFS、设备模型的关系。框架懂了,写代码就是填空。

为什么会这样?因为Linux内核设计者早就把通用逻辑抽象好了。你写一个I2C驱动,只需要实现probe、remove、读写函数,剩下的注册、匹配、电源管理,内核框架帮你搞定。

1.3 驱动开发环境搭建

环境搭建是新手最容易卡住的地方。我记得第一次配交叉编译工具链,折腾了整整一天。现在回头看,其实就三步:

1.3.1 交叉编译工具链

咱们的i.MX8M是ARM架构的,但开发机是x86的。所以需要用交叉编译器——在x86上编译出能在ARM上跑的程序。

# 下载Linaro工具链(以aarch64为例)
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz

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

# 设置环境变量
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

# 验证是否安装成功
aarch64-linux-gnu-gcc --version

注意:工具链版本必须跟内核版本匹配。我曾经因为用了太新的工具链,编译出来的内核模块加载就报错——符号版本不匹配,折腾了两天才发现是工具链的锅。

1.3.2 NFS网络文件系统

开发阶段,每次修改驱动都重新烧写根文件系统太慢了。NFS可以让你把开发机上的目录挂载到开发板上,改完代码直接运行。

# 开发机(Ubuntu)上配置NFS服务
sudo apt install nfs-kernel-server
sudo mkdir -p /srv/nfs/rootfs
sudo chown nobody:nogroup /srv/nfs/rootfs

# 编辑/etc/exports,添加:
/srv/nfs/rootfs *(rw,sync,no_subtree_check,no_root_squash)

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

# 开发板U-Boot启动参数设置:
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs ip=192.168.1.200:192.168.1.100::255.255.255.0::eth0:off'
saveenv
boot

我个人习惯把内核模块放在NFS目录下的一个独立文件夹里,这样insmod/rmmod测试特别方便。你想想看,改一行代码,重新编译,开发板上直接加载新模块——这效率比烧写快十倍。

1.3.3 TFTP网络启动

TFTP用来通过网络加载内核镜像。配合NFS,可以实现「无盘启动」——开发板上连存储都不需要。

# 开发机安装TFTP服务
sudo apt install tftpd-hpa

# 配置TFTP目录 /srv/tftp
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp

# 把内核镜像放到TFTP目录
cp arch/arm64/boot/Image /srv/tftp/

# 开发板U-Boot中设置:
setenv serverip 192.168.1.100
setenv ipaddr 192.168.1.200
tftp ${loadaddr} Image
booti ${loadaddr} - ${fdt_addr}

避坑指南:我曾经因为TFTP传输超时,查了半天发现是防火墙没关。还有一次是网线质量差,丢包导致内核解压失败。建议用交叉网线直连开发板和电脑,别经过交换机。

小结

这一章咱们把嵌入式Linux驱动开发的「地基」打好了。嵌入式系统是什么、内核怎么组织、开发环境怎么搭——这些是后面所有章节的基础。下一章我会带你写第一个字符设备驱动,到时候你就知道,驱动开发其实没那么神秘。

嗯,最后说一句:环境搭建遇到问题别硬扛。我当年为了省事跳过NFS配置,结果每次改驱动都要烧写,浪费的时间够配十次NFS了。该花的功夫,一步都省不了。