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 驱动分类关系图

Linux 驱动分类架构 Linux 设备驱动 字符设备驱动 块设备驱动 网络设备驱动 GPIO / 串口 / I2C 键盘 / 鼠标 硬盘 / SD 卡 U 盘 / SSD 以太网卡 WiFi 模块 本课程聚焦字符设备驱动中的 GPIO 按键中断实现

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 环境搭建流程图

驱动开发环境搭建流程 ① 安装交叉编译链 ② 获取内核源码树 ③ 配置内核(make menuconfig) ④ 编译内核与模块 ✅ 环境就绪,开始驱动开发

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 按键)、块设备(存储)、网络设备(网卡)。
  • 环境搭建:交叉编译链 + 内核源码树 + 配置编译验证。