驱动(二)Linux 系统移植、驱动开发框架
一、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_DRIVERtristate "Hello World Character Driver"helpThis 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
文件中。