一、Linux 驱动基础
1. 驱动分类及核心特点
| 驱动类型 | 数据处理方式 | 典型应用场景 | 关键特性 |
|---|---|---|---|
| 字符设备驱动 | 按单个字符顺序读写 | 串口、键盘、鼠标、LED | 支持open/read/write等基本文件操作,无缓存或简单缓存 |
| 块设备驱动 | 按固定大小 "块"(通常 512 字节)读写 | 硬盘、eMMC、SD 卡、U 盘 | 支持随机访问,需缓存管理,通过文件系统间接访问 |
| 网络设备驱动 | 按网络帧(数据包)传输 | 以太网、WiFi、CAN 总线 | 不依赖文件系统,需对接 TCP/IP、CAN 等协议栈,通过socket接口访问 |
2. 驱动编译的两种方式(静态 vs 动态)
驱动编译的核心差异在于是否将驱动集成到内核镜像,或以独立模块形式加载,具体流程和区别如下:
(1)静态编译(内置到内核)
核心逻辑 :将驱动代码直接编译进 Linux 内核镜像(如zImage),内核启动时自动加载驱动。操作步骤:
-
准备驱动代码 :在 Linux 内核源码的对应目录(如
drivers/char/)编写驱动文件(例:hello.c)。 -
修改 Kconfig :在驱动目录的
Kconfig文件中新增配置项,用于menuconfig图形化选择,示例:kconfig
config HELLO_DRIVER tristate "Hello World Character Driver" help This is a test character driver for Linux. -
图形化配置 :执行
make ARCH=arm CROSS_COMPILE=xxx- menuconfig,在对应菜单中选中HELLO_DRIVER(勾选为*,表示静态编译)。 -
更新配置文件 :选中后,内核会自动在
.config文件中添加CONFIG_HELLO_DRIVER=y(y表示静态内置)。 -
修改 Makefile :在驱动目录的
Makefile中添加编译规则,将驱动文件关联到配置项:makefile
obj-$(CONFIG_HELLO_DRIVER) += hello.o -
编译内核 :执行
make ARCH=arm CROSS_COMPILE=xxx- all -jN,驱动会随内核一起生成到zImage中。
(2)动态编译(模块编译)
核心逻辑 :驱动代码编译为独立的内核模块文件(.ko),不内置到内核,可在系统运行时动态加载 / 卸载。操作步骤:
- 准备驱动代码 :同静态编译,编写
hello.c(需包含模块加载 / 卸载函数,如module_init()/module_exit())。 - 修改 Kconfig :同静态编译,配置项类型为
tristate(支持静态 / 动态 / 不编译)。 - 图形化配置 :执行
make menuconfig,将HELLO_DRIVER设为M(表示动态模块),.config中会生成CONFIG_HELLO_DRIVER=m。 - 修改 Makefile :同静态编译,编译规则不变(
obj-$(CONFIG_XXX)自动适配m模式)。 - 编译模块 :执行
make ARCH=arm CROSS_COMPILE=xxx- modules,内核仅编译标记为M的驱动,生成hello.ko文件。 - 模块操作 :
- 加载:
insmod hello.ko(临时加载,重启失效) - 卸载:
rmmod hello(需确保驱动未被使用) - 查看:
lsmod(查看已加载的模块)
- 加载:
二、Linux 驱动工程搭建(目录划分规范)
为保证工程可维护性,建议按 "功能模块 + 通用文件" 划分目录,典型结构如下:
plaintext
hello_driver/ # 驱动工程根目录
├── doc/ # 文档目录:存放驱动说明、接口文档、测试报告
├── driver/ # 驱动核心代码目录
│ ├── hello/ # 具体驱动模块(如hello驱动)
│ │ ├── hello.c # 驱动核心逻辑(初始化、文件操作、中断处理等)
│ │ ├── hello.h # 驱动头文件(宏定义、结构体、函数声明)
│ │ ├── Kconfig # 该模块的配置文件
│ │ └── Makefile # 该模块的编译规则
│ └── common/ # 通用驱动组件(如GPIO封装、中断封装)
├── include/ # 全局头文件目录:存放工程通用头文件(如平台定义、工具函数声明)
├── test/ # 测试程序目录:存放驱动测试代码(如`test_hello.c`,用于验证驱动功能)
├── scripts/ # 脚本目录:存放编译脚本(如`build.sh`)、烧录脚本(如`load_driver.sh`)
├── Makefile # 工程顶层Makefile:调用内核Makefile,指定交叉编译工具链、架构等
└── README.md # 工程说明:编译步骤、测试方法、注意事项
三、Linux 系统移植与驱动核心问题(重点)
1. Linux 系统移植需要哪些文件?各文件作用?
Linux 系统移植的核心是构建 "Bootloader + 内核 + 根文件系统" 三部分,所需关键文件及作用如下:
| 组件 | 关键文件 / 目录 | 作用说明 |
|---|---|---|
| Bootloader(以 UBoot 为例) | U-Boot 源码目录、u-boot.bin |
1. 硬件初始化(如 CPU、内存、GPIO、时钟);2. 加载内核到内存;3. 传递启动参数(bootargs)给内核;4. 提供命令行交互(如烧录、分区管理) |
| Linux 内核 | zImage/uImage、.config、设备树(*.dtb) |
1. zImage:压缩后的内核镜像,是系统运行的核心;2. .config:内核配置文件,决定内核功能和驱动;3. *.dtb:设备树文件,描述硬件信息(如外设地址、中断号),替代传统的 "板级代码" |
| 根文件系统 | rootfs目录(包含bin/sbin/etc等) |
1. 提供系统运行所需的文件和目录结构;2. 包含初始化脚本(如init)、系统命令(如ls/cd)、库文件(lib);3. 存储用户数据和应用程序 |
| 交叉编译工具链 | arm-linux-gnueabihf-gcc等 |
为 ARM 架构编译 UBoot、内核、根文件系统和驱动的工具集 |
2. Linux 系统的启动流程(从上电到应用运行)
完整启动流程分为 4 个阶段,按顺序如下:
-
**硬件上电初始化(固化代码)**上电后,CPU 先执行芯片内部固化的 "启动代码"(如 i.MX 的 ROM Code),完成最基础的硬件检查(如内存是否存在),然后从指定启动介质(如 eMMC、SD 卡)加载 Bootloader。
-
Bootloader(UBoot)阶段
- 初始化硬件:配置 CPU 时钟、内存控制器、GPIO、串口等(让硬件处于可工作状态)。
- 加载内核和设备树:将存储介质中的
zImage(内核)和*.dtb(设备树)加载到内存指定地址(如内核加载到0x80800000)。 - 传递启动参数:通过
bootargs将根文件系统路径、串口配置等参数传递给内核,然后跳转到内核入口地址,启动内核。
-
Linux 内核阶段
- 内核初始化:解压内核、初始化进程管理、内存管理、文件系统(如
ext4)、驱动框架。 - 设备树解析:解析
*.dtb文件,识别硬件设备(如 UART、GPIO),并加载对应的驱动。 - 启动 init 进程:内核初始化完成后,启动第一个用户空间进程
init(PID=1),init进程负责后续的系统初始化。
- 内核初始化:解压内核、初始化进程管理、内存管理、文件系统(如
-
根文件系统与用户空间阶段
init进程执行初始化脚本(如/etc/init.d/rcS):挂载其他分区(如/tmp)、启动网络服务、加载驱动模块、启动应用程序。- 进入用户交互:若配置了登录功能,会启动终端(如串口终端),等待用户登录,最终进入 Shell 命令行或自动启动应用。
3. UBoot 中bootcmd和bootargs的含义?
(1)bootcmd:UBoot 的 "自动启动命令"
-
作用 :UBoot 启动后,若未手动打断(如按键盘按键),会自动执行
bootcmd中定义的命令,用于加载内核和设备树。 -
典型示例 (针对 eMMC 启动):
bash
setenv bootcmd 'mmc dev 0; mmc read 0x80800000 0x1000 0x8000; mmc read 0x83000000 0x9000 0x1000; bootz 0x80800000 - 0x83000000'命令拆解:
mmc dev 0:选择第 0 个 eMMC 设备;mmc read 0x80800000 0x1000 0x8000:将 eMMC 中地址0x1000开始、长度0x8000的zImage读入内存0x80800000;mmc read 0x83000000 0x9000 0x1000:将 eMMC 中地址0x9000开始、长度0x1000的设备树(*.dtb)读入内存0x83000000;bootz 0x80800000 - 0x83000000:启动内核(bootz是压缩内核启动命令,-表示无 ramdisk,最后是设备树地址)。
(2)bootargs:UBoot 传递给 Linux 内核的 "启动参数"
-
作用 :内核启动时会解析
bootargs,获取根文件系统路径、串口配置、硬件参数等,决定系统的运行方式。 -
典型示例 :
bash
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rw init=/linuxrc'参数拆解:
console=ttymxc0,115200:指定系统控制台为ttymxc0(第 1 个串口),波特率 115200;root=/dev/mmcblk0p2:指定根文件系统位于mmcblk0p2(第 0 个 eMMC 的第 2 个分区);rootfstype=ext4:根文件系统类型为ext4;rw:根文件系统以 "可读可写" 模式挂载;init=/linuxrc:指定用户空间的第一个init进程路径为/linuxrc(通常是busybox的链接)。
4. UBoot 如何编译?UBoot 的功能?
(1)UBoot 编译步骤(以 ARM 架构为例)
-
准备交叉编译工具链 :确保
arm-linux-gnueabihf-gcc已安装并添加到PATH(可通过echo $PATH验证)。 -
获取 UBoot 源码 :从官方(https://www.denx.de/wiki/U-Boot)或开发板厂商(如正点原子)获取适配的 UBoot 源码。
-
加载默认配置 :执行厂商提供的板级配置文件(位于
configs/目录),示例(正点原子 IMX6ULL 开发板):bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_alientek_emmc_defconfig -
图形化配置(可选) :若需修改 UBoot 功能(如添加命令、支持外设),执行:
bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig -
编译 UBoot :指定并行任务数(
-jN,N 为 CPU 核心数的 1-2 倍),加速编译:bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16 -
获取编译产物 :编译完成后,在源码根目录生成
u-boot.bin(UBoot 镜像,需烧录到启动介质)、u-boot.elf(调试用)等文件。
(2)UBoot 的核心功能
UBoot 的定位是 "硬件初始化与内核加载工具",核心功能分为 5 类:
- 硬件初始化:上电后初始化 CPU、内存(DDR)、时钟、GPIO、串口、存储设备(eMMC/SD)等,为内核运行准备硬件环境。
- 内核加载与启动 :从存储介质(eMMC、SD、U 盘、网络)加载内核(
zImage)和设备树(*.dtb)到内存,传递bootargs后启动内核。 - 命令行交互 :提供丰富的命令集,用于调试和管理,如:
- 存储操作:
mmc(eMMC/SD)、usb(U 盘)、nand(NAND Flash); - 内存操作:
md(查看内存)、mw(修改内存)、cp(内存拷贝); - 网络操作:
ping(网络测试)、tftp(从 TFTP 服务器下载文件); - 环境变量操作:
setenv(设置环境变量)、saveenv(保存环境变量到存储介质)。
- 存储操作:
- 系统烧录 :支持将 UBoot、内核、根文件系统烧录到 eMMC、NAND Flash 等存储设备(如
mmc write命令)。 - 故障排查:提供串口打印、内存查看、硬件状态检测等功能,帮助定位硬件或系统启动问题。
5. 内核如何编译?如何裁剪?
(1)Linux 内核编译步骤(以 ARM 架构为例)
-
准备环境 :安装交叉编译工具链(如
arm-linux-gnueabihf-gcc)、内核源码(从https://www.kernel.org/或厂商获取)。 -
清理编译环境 :若之前编译过,执行
distclean彻底清理残留文件:bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean -
加载默认配置 :执行板级默认配置文件(位于
arch/arm/configs/目录),示例:bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_alientek_emmc_defconfig -
图形化裁剪(可选) :执行
menuconfig调整内核配置(裁剪功能 / 驱动):bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig -
编译内核 :执行
all编译内核镜像、设备树和模块(-jN加速):bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16 -
获取产物 :
- 内核镜像:
arch/arm/boot/zImage(压缩内核,用于启动); - 设备树:
arch/arm/boot/dts/xxx.dtb(xxx 为开发板对应的设备树文件名); - 驱动模块:若有动态编译的驱动,生成
*.ko文件(位于对应驱动目录)。
- 内核镜像:
(2)内核裁剪方法(通过menuconfig)
内核裁剪的核心是 "保留必需功能,删除无用功能",以减小内核体积、降低资源占用,步骤如下:
- 进入裁剪界面 :执行
make ARCH=arm CROSS_COMPILE=xxx- menuconfig,进入图形化配置界面。 - 裁剪原则 :
- 移除无用硬件驱动 :如无老旧硬件(软驱、并口)、特定网卡或声卡驱动,检查
Device Drivers子菜单,禁用无关选项。 - 精简文件系统支持 :保留必需的文件系统(如
ext4、squashfs),移除Btrfs、ReiserFS等。 - 禁用调试和性能分析 :关闭
Kernel hacking→Debugging相关选项,禁用Profiling和Tracers。 - 优化网络功能 :移除不用的网络协议(如
Amateur Radio、IrDA),精简防火墙(Netfilter)规则,仅保留必需模块。 - 模块化非核心功能 :将不常用功能编译为模块(
M),而非内置(Y),如某些文件系统或驱动。
- 移除无用硬件驱动 :如无老旧硬件(软驱、并口)、特定网卡或声卡驱动,检查
- 使用搜索功能 :在
menuconfig界面中,可以通过按 "/" 键来进行全局搜索,快速定位到需要裁剪或配置的选项。 - 保存配置 :完成裁剪后,按 "Esc" 键退出配置界面,选择保存配置,配置结果将保存到
.config文件中。