Linux GPIO按键驱动开发与输入子系统集成 - 第1节
1.1 Linux 内核模块基础
Linux 内核采用模块化设计,允许在运行时动态加载和卸载功能代码,而无需重新编译整个内核或重启系统。这种机制称为 内核模块(Kernel Module)。驱动开发的核心工作之一就是编写内核模块。
1.1.1 模块的 Hello World
一个最简单的内核模块包含两个关键函数:模块初始化和模块退出。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello, GPIO Driver World!\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, GPIO Driver World!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple GPIO example module");
关键点:
module_init() 和 module_exit() 是模块的入口和出口宏,printk() 是内核空间的打印函数,与用户空间的 printf() 不同。
1.1.2 模块的 Makefile 与编译
内核模块不能使用普通的 gcc 编译,必须依赖内核源码树提供的 Kbuild 构建系统。以下是一个典型的 Makefile:
obj-m := hello_gpio.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
常见错误: 如果
KDIR 指向的内核源码树与当前运行内核版本不一致,会导致模块加载失败,出现 Invalid module format 错误。
1.1.3 模块加载与卸载命令
| 命令 | 功能 | 示例 |
|---|---|---|
insmod |
加载模块(不自动解决依赖) | insmod hello_gpio.ko |
rmmod |
卸载模块 | rmmod hello_gpio |
modprobe |
加载模块(自动解决依赖) | modprobe hello_gpio |
lsmod |
列出当前已加载的模块 | lsmod | grep gpio |
modinfo |
查看模块信息 | modinfo hello_gpio.ko |
1.2 驱动分类:字符/块/网络
Linux 驱动根据设备类型和数据交换方式,主要分为三大类:字符设备驱动、块设备驱动和网络设备驱动。理解它们的区别是选择驱动框架的前提。
1.2.1 三类驱动对比
| 特性 | 字符设备 | 块设备 | 网络设备 |
|---|---|---|---|
| 数据访问方式 | 流式、顺序读写 | 随机块访问 | 数据包收发 |
| 设备文件 | /dev/xxx |
/dev/xxx |
无(通过 socket 访问) |
| 典型设备 | 键盘、鼠标、串口、GPIO | 硬盘、SD 卡、U 盘 | 以太网卡、WiFi 模块 |
| 内核接口 | file_operations |
block_device_operations |
net_device_ops |
| 数据缓冲 | 通常无缓冲 | 内核页缓存 | SKB 缓冲区 |
本课程重点: GPIO 按键驱动属于 字符设备驱动,通过
file_operations 结构体向用户空间提供 open、read、write、ioctl 等接口。
1.2.2 驱动分类关系图
1.3 驱动开发环境搭建
嵌入式 Linux 驱动开发通常采用 交叉编译 模式:在性能强大的宿主机(x86 PC)上编写代码,编译生成目标平台(ARM/RISC-V)的可执行文件,然后下载到目标板运行。
1.3.1 交叉编译链
交叉编译链是一套针对特定目标架构的工具集合,包含编译器、链接器、调试器等。以 ARM 平台为例,常用工具链包括:
- gcc-arm-linux-gnueabihf:针对 ARM Cortex-A 系列,支持硬件浮点。
- gcc-arm-none-eabi:针对裸机或 RTOS 环境,无操作系统。
- aarch64-linux-gnu-gcc:针对 64 位 ARMv8 架构。
安装示例(Ubuntu):
sudo apt-get install gcc-arm-linux-gnueabihf
1.3.2 内核源码树
编译内核模块必须依赖完整的内核源码树,包含头文件、Kconfig 和 Makefile 结构。获取内核源码树的两种方式:
| 方式 | 命令/步骤 | 适用场景 |
|---|---|---|
| 从内核官网下载 | wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.tar.xz |
通用开发,版本可控 |
| 从芯片厂商 BSP 获取 | 厂商提供的 SDK 中通常包含预配置的内核源码 | 板级开发,驱动适配 |
| 使用发行版内核头文件 | sudo apt-get install linux-headers-$(uname -r) |
快速测试,仅限本机模块 |
1.3.3 环境搭建流程图
1.3.4 验证环境是否就绪
完成上述步骤后,通过以下命令验证环境:
# 检查交叉编译器版本
arm-linux-gnueabihf-gcc --version
# 检查内核源码树是否存在
ls /path/to/kernel/include/linux/
# 编译一个空模块测试
cd /path/to/kernel
make modules_prepare
避坑指南: 确保内核源码树已经 至少完成一次配置(
make defconfig 或 make menuconfig),否则 modules_prepare 会失败。另外,宿主机与目标板的 内核版本必须一致,否则模块加载时会报版本魔术错误。
1.4 本章小结
本章建立了 Linux 驱动开发的三个基础支柱:
- 内核模块机制:动态加载/卸载,
module_init/module_exit入口出口。 - 驱动分类:字符设备(GPIO 按键)、块设备(存储)、网络设备(网卡)。
- 环境搭建:交叉编译链 + 内核源码树 + 配置编译验证。