Linux platform驱动开发与设备树资源适配 - 第1节

1.1 Linux内核驱动概述

Linux内核驱动是操作系统与硬件设备之间的桥梁。它负责管理硬件设备的初始化、数据收发、中断处理以及电源管理等核心功能。在嵌入式系统中,驱动程序的稳定性和效率直接决定了产品的性能与可靠性。

核心认知:驱动开发不仅仅是“写代码控制硬件”,更是理解内核框架、总线模型、内存管理与并发同步的综合工程。掌握Platform总线与设备树,是进入现代嵌入式Linux驱动开发的必经之路。

1.1.1 内核模块基础

Linux内核采用模块化设计,允许在运行时动态加载和卸载功能代码,这些可动态加载的代码片段称为内核模块。模块机制使得内核可以保持精简,同时支持丰富的硬件驱动。

一个最简单的内核模块包含两个核心函数:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, Kernel Module Loaded!\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, Kernel Module Unloaded!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A Simple Hello World Module");
元素 说明
module_init() 指定模块加载时的入口函数
module_exit() 指定模块卸载时的清理函数
__init 标记初始化函数,加载后释放内存
__exit 标记退出函数,模块内建时忽略
MODULE_LICENSE 声明模块许可证,影响内核符号导出
编译与测试:使用 make -C /lib/modules/$(uname -r)/build M=$PWD modules 编译模块,通过 insmod 加载,rmmod 卸载,lsmod 查看已加载模块列表。

1.1.2 驱动分类

Linux内核将设备驱动划分为三大类,每一类对应不同的抽象模型和用户空间接口:

  • 字符设备:以字节流方式访问,顺序读写,如串口、GPIO、I2C、SPI设备。通过 file_operations 结构体实现 open/read/write/ioctl 等操作。
  • 块设备:以数据块为单位随机访问,支持缓冲区与调度,如eMMC、NAND Flash、SD卡。通过 block_device_operations 和请求队列管理I/O。
  • 网络设备:负责数据包的收发,遵循网络协议栈接口,如以太网控制器、WiFi模块。通过 net_device_ops 实现 ndo_open/ndo_start_xmit 等回调。
常见误区:初学者容易混淆“字符设备”与“块设备”的界限。判断标准不是硬件类型,而是访问模式:是否支持随机访问、是否有缓冲区。例如,一个简单的LED灯驱动一定是字符设备,而U盘驱动则是块设备。

下表从多个维度对比三类驱动的核心差异:

维度 字符设备 块设备 网络设备
数据单位 字节流 数据块(512B/4KB) 数据包(MTU)
访问方式 顺序/随机 随机(支持寻址) 异步收发
用户接口 /dev/xxx + open/read/write /dev/xxx + 文件系统挂载 socket / netlink
内核接口 file_operations block_device_operations + 请求队列 net_device_ops + NAPI
典型硬件 UART、GPIO、I2C、音频 eMMC、SATA、NVMe 以太网、WiFi、CAN
Linux 内核驱动分类架构图 用户空间 (User Space) 系统调用接口 (System Call Interface) 字符设备驱动 file_operations 块设备驱动 block_device_ops 网络设备驱动 net_device_ops 硬件设备 (Hardware)

1.2 驱动开发环境搭建

搭建一套完整的驱动开发环境是入门的第一步。嵌入式驱动开发通常采用交叉编译方式:在性能强大的宿主机(x86)上编写和编译代码,生成目标平台(ARM/RISC-V)的可执行文件,再部署到目标板运行。

1.2.1 交叉编译链

交叉编译链(Cross Toolchain)包含编译器、链接器、调试器等工具,针对特定目标架构生成二进制代码。常用工具链包括:

  • ARM GCC:arm-linux-gnueabihf-gcc,适用于ARM Cortex-A系列
  • AArch64 GCC:aarch64-linux-gnu-gcc,适用于ARMv8 64位
  • RISC-V GCC:riscv64-linux-gnu-gcc,适用于RISC-V平台
环境验证:安装完成后,执行 arm-linux-gnueabihf-gcc --version 查看版本信息。若输出正常,说明工具链已就绪。

1.2.2 内核源码树

驱动模块的编译依赖内核源码树(Kernel Source Tree),它提供了内核头文件、Makefile规则以及模块编译框架。获取内核源码树的两种方式:

  1. 下载官方内核:从 kernel.org 下载对应版本源码,解压后配置编译。
  2. 使用板级SDK:厂商提供的BSP包中通常包含预配置的内核源码树,可直接用于模块编译。

编译内核模块的标准Makefile模板:

# Makefile for external kernel module
obj-m := hello.o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
变量 含义
obj-m 指定要编译为模块的目标文件
KERNELDIR 内核源码树路径,指向已编译的内核
M=$(PWD) 告诉内核构建系统当前模块源码所在目录
避坑指南:内核源码树必须预先编译完成,且版本与目标运行内核一致。使用 uname -r 确认版本号,避免因版本不匹配导致模块加载失败(Invalid module format 错误)。
驱动开发环境搭建流程 步骤1 安装交叉编译链 步骤2 获取内核源码树 步骤3 配置并编译内核 步骤4 编写驱动模块代码 步骤5 交叉编译模块 步骤6 部署到目标板测试

1.2.3 开发环境清单

下表汇总了搭建驱动开发环境所需的软件组件及其用途:

组件 示例 用途
交叉编译器 arm-linux-gnueabihf-gcc 将C源码编译为目标架构的二进制
内核源码树 linux-5.10 提供内核头文件与模块编译框架
设备树编译器 dtc 将DTS编译为DTB二进制
根文件系统 Buildroot / Yocto 提供目标板运行时的用户空间环境
调试工具 gdb-multiarch / kgdb 内核模块源码级调试
学习建议:初学者可以先在x86宿主机上使用 make modules_prepare 准备内核头文件,直接编译并加载模块到本地内核进行验证,待熟悉流程后再切换到交叉编译环境。