嵌入式 Linux 驱动概述:分类、内核模块、开发环境

嵌入式 Linux 驱动概述:分类、内核模块、开发环境

嵌入式 Linux 驱动是连接硬件和应用的桥梁。这篇从零讲起:驱动是什么、分哪几类、内核模块机制、开发环境怎么搭,带你入门 Linux 驱动开发。

大家好,我是黒漂技术佬。

做嵌入式的,早晚要碰驱动。应用层调用 open/read/write 就能操作硬件,背后都是驱动在干活。驱动直接跟内核、跟硬件打交道,门槛比应用层高,但也更底层、更有意思。

这个系列 12 篇,从内核模块到字符设备、中断、定时器、并发同步、设备树、I2C/SPI,最后来个传感器驱动实战。

第一篇先讲基础:驱动是什么、分类、内核模块机制、开发环境搭建。


一、Linux 驱动是什么?

作用

驱动程序是硬件和应用之间的桥梁

复制代码
应用程序 → 系统调用 → 驱动程序 → 硬件
  • 应用层:用 open、read、write、ioctl 这些标准接口
  • 驱动层:把这些操作翻译成硬件能懂的读写寄存器、中断处理等
  • 硬件:实际干活的外设(GPIO、I2C、SPI、显示屏、传感器......)

为什么需要驱动?

  1. 统一接口:不管什么硬件,应用层都用 open/read/write,不用管硬件差异
  2. 权限隔离:硬件操作在内核态,应用在用户态,安全稳定
  3. 复用性:一套驱动可以给多个应用用

二、Linux 驱动的分类

Linux 驱动主要分三大类:

1. 字符设备驱动(Character Device)

  • 字节流顺序读写的设备
  • 最常见、最简单的一类
  • 例子:串口、GPIO、LED、按键、I2C 设备、SPI 设备
  • 设备文件在 /dev/xxx,主设备号+次设备号标识

2. 块设备驱动(Block Device)

  • (一般 512 字节或 4KB)随机读写的设备
  • 主要是存储类设备
  • 例子:硬盘、SD 卡、eMMC、U盘
  • 有缓存机制,比字符设备复杂

3. 网络设备驱动(Network Device)

  • 负责网络数据包收发
  • 不走 /dev 文件系统,用 socket 接口
  • 例子:以太网、WiFi
  • 最复杂的一类驱动

其他子系统

除了三大类,还有很多专门的子系统:

  • input 子系统:按键、触摸屏、鼠标
  • framebuffer / DRM:显示驱动
  • V4L2:摄像头驱动
  • ALSA:音频驱动
  • USB 驱动:USB 设备
  • PCIe 驱动:PCIe 设备
  • pinctrl / GPIO 子系统:引脚控制

这些底层还是基于字符设备/平台设备,但内核提供了专门的框架。

本系列重点

入门系列以字符设备驱动为主,这是最基础、最常用的。学会了字符设备,其他类别的驱动思路是通的。


三、内核模块(Kernel Module)

是什么

Linux 驱动一般以内核模块的形式存在,可以动态加载和卸载,不用重新编译内核。

bash 复制代码
# 加载模块
insmod xxx.ko
modprobe xxx

# 卸载模块
rmmod xxx

# 查看已加载模块
lsmod

.ko 文件就是内核模块文件。

为什么用模块?

  1. 灵活:需要的时候加载,不用就卸载,节省内存
  2. 开发方便:改了驱动重新加载就行,不用重启内核
  3. 可裁剪:嵌入式系统按需加载,减小内核体积

最简单的内核模块

c 复制代码
#include <linux/init.h>
#include <linux/module.h>

// 模块加载时调用
static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, Linux Kernel!\n");
    return 0;
}

// 模块卸载时调用
static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, Kernel!\n");
}

module_init(hello_init);  // 注册入口函数
module_exit(hello_exit);  // 注册出口函数

MODULE_LICENSE("GPL");    // 许可证,必须有
MODULE_AUTHOR("Heipiao");
MODULE_DESCRIPTION("Hello World Module");

关键宏说明

作用
module_init 模块加载时调用的函数
module_exit 模块卸载时调用的函数
__init 标记初始化函数,加载完后释放内存
__exit 标记退出函数,只在卸载时用
MODULE_LICENSE 模块许可证,GPL 才能用 GPL 的内核函数
MODULE_AUTHOR 作者
MODULE_DESCRIPTION 描述
MODULE_ALIAS 别名

printk

内核里打印用 printk,不是 printf。带日志级别:

复制代码
KERN_EMERG   紧急
KERN_ALERT   警报
KERN_CRIT    严重
KERN_ERR     错误
KERN_WARNING 警告
KERN_NOTICE  注意
KERN_INFO    信息
KERN_DEBUG   调试

dmesg 看内核日志。


四、驱动开发环境

1. 硬件开发板

入门推荐:

  • 树莓派:资料多,社区大,适合入门
  • 正点原子 / 野火 IMX6ULL:国产教程多,配套齐全
  • RK3588:性能强,AI 相关开发常用
  • QEMU 模拟:没有板子也能学,用虚拟机

2. 交叉编译工具链

在 x86 电脑上编译 ARM 平台的代码,需要交叉编译器:

bash 复制代码
# ARM 32位
arm-linux-gnueabihf-gcc

# ARM 64位
aarch64-linux-gnu-gcc

3. Linux 内核源码

编译驱动需要对应版本的内核源码(或内核头文件),版本要跟开发板上运行的内核一致。

复制代码
内核源码树/
├── arch/          架构相关
├── drivers/       驱动源码
├── include/       头文件
├── fs/            文件系统
├── kernel/        内核核心
├── mm/            内存管理
├── net/           网络
└── Makefile

4. Makefile

编译内核模块的 Makefile 很固定:

makefile 复制代码
# 内核源码路径,根据实际情况改
KERNELDIR := /path/to/linux-kernel
# 当前目录
PWD := $(shell pwd)
# 交叉编译器
CROSS_COMPILE := arm-linux-gnueabihf-
ARCH := arm

obj-m := hello.o  # 要编译的模块

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

clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

执行 make 就能生成 hello.ko

5. 加载测试

bash 复制代码
# 传到开发板上
scp hello.ko root@192.168.1.100:/tmp/

# 开发板上加载
insmod hello.ko
dmesg | tail    # 看打印

# 卸载
rmmod hello
dmesg | tail

五、内核态 vs 用户态

区别

用户态 内核态
权限 受限,不能直接操作硬件 最高权限,操作所有硬件
内存 独立的用户空间 共享内核空间
API C 库、系统调用 内核函数(printk, kmalloc...)
稳定性 崩溃了只是进程挂了 崩溃了整个系统挂
调试 gdb 方便 调试麻烦,kgdb / printk

驱动运行在内核态

驱动代码运行在内核态,所以:

  • 不能用 C 库函数(printf、malloc 不行)
  • 要用内核提供的函数(printk、kmalloc、copy_to_user 等)
  • 出 bug 可能导致内核 panic,系统死机
  • 内存分配要小心,不能泄漏

六、驱动的基本结构

一个典型的字符设备驱动包含:

1. 模块入口出口

c 复制代码
module_init(xxx_init);
module_exit(xxx_exit);

2. 设备号申请

c 复制代码
alloc_chrdev_region(&devid, 0, 1, "xxx");  // 动态分配
register_chrdev_region(devid, 1, "xxx");   // 静态指定

3. cdev 注册

c 复制代码
cdev_init(&cdev, &fops);    // 绑定操作函数
cdev_add(&cdev, devid, 1);  // 加入内核

4. 文件操作集合(file_operations)

c 复制代码
static const struct file_operations xxx_fops = {
    .owner = THIS_MODULE,
    .open = xxx_open,
    .release = xxx_release,
    .read = xxx_read,
    .write = xxx_write,
    .unlocked_ioctl = xxx_ioctl,
};

应用层的 open/read/write 对应到驱动里的这些函数。

5. 硬件操作

读写寄存器、配置 GPIO、处理中断......跟具体硬件相关。

6. 自动创建设备节点

c 复制代码
class_create();
device_create();

加载模块自动在 /dev 下创建设备文件,不用手动 mknod。


七、学习路线建议

入门阶段

  1. 内核模块 hello world
  2. 字符设备驱动基本框架
  3. open/read/write 实现
  4. GPIO 驱动(LED、按键)

进阶阶段

  1. 中断处理
  2. 定时器
  3. 并发与同步(自旋锁、信号量、互斥锁)
  4. 设备树

深入阶段

  1. I2C 子系统
  2. SPI 子系统
  3. platform 总线
  4. 输入子系统、Framebuffer 等

这个系列就按这个顺序来,从简单到复杂。


八、常见坑

坑 1:内核版本不匹配

编译驱动的内核源码版本必须跟开发板运行的内核版本一致,不然加载会失败(version magic 错误)。

坑 2:printk 不一定立刻打印

内核有缓冲,不一定立刻输出。用 dmesg 看,或者 printk 级别设高一点。

坑 3:在内核里用 C 库函数

不能用 printf、malloc、strcpy 这些用户态函数,要用内核对应的:printk、kmalloc、strlcpy......

坑 4:用户空间指针直接访问

应用传下来的指针(用户空间地址)不能直接在内核里读写,要用 copy_from_user / copy_to_user。

坑 5:忘了加 MODULE_LICENSE

没加 GPL 许可证,很多内核函数用不了,会报错 unknown symbol。

坑 6:驱动 bug 导致系统死机

内核态出错直接 panic,不像应用层只是段错误。调试要小心,多打日志。


九、本系列内容安排

  1. 嵌入式 Linux 驱动概述:分类、内核模块、开发环境
  2. 内核模块开发:module_init/module_exit 与模块机制
  3. 字符设备驱动:file_operations 与读写
  4. 设备号与设备节点:动态分配、udev 自动创建
  5. 中断处理:顶半部底半部、tasklet/workqueue
  6. 定时器与延时:内核定时器、hrtimer
  7. 并发与同步:自旋锁、信号量、互斥锁
  8. 平台设备驱动:platform bus 与设备树
  9. GPIO 子系统与 pinctrl
  10. I2C 设备驱动开发
  11. SPI 设备驱动开发
  12. 实战:传感器驱动从 0 到 1

十、本篇小结

  • Linux 驱动分三类:字符设备(字节流)、块设备(存储)、网络设备(网卡)
  • 驱动以内核模块形式存在,可动态加载卸载(.ko 文件)
  • 模块两个关键函数:module_init(加载)、module_exit(卸载)
  • 驱动运行在内核态,权限高但出错代价大,不能用 C 库
  • 开发环境:开发板、交叉编译器、内核源码树、Makefile
  • 字符设备驱动基本结构:设备号 + cdev + file_operations + 硬件操作
  • 学习路线:模块 → 字符设备 → 中断/定时器 → 并发 → 设备树 → I2C/SPI → 实战

下一篇讲内核模块开发:深入 module_init/module_exit、模块参数、模块依赖、导出符号。

我是黒漂技术佬。

相关推荐
2401_854151552 小时前
STM32 位带操作——像 51 一样操作 GPIO
单片机·嵌入式硬件
zlinear数据采集卡11 小时前
从0到1硬核拆解:工业级数据采集卡的隔离设计与Modbus通信实战
arm开发·单片机·嵌入式硬件·fpga开发·开源
Uncertainty!!12 小时前
Ubuntu 22.04 安装超好用中文输入法rime-ice(雾凇拼音)过程记录
linux·ubuntu·中文输入法
ShineWinsu13 小时前
对于Linux:网络基础的解析
linux·网络·面试·udp·笔试·ip·tcp
↘"LYong14 小时前
旧版 CentOS 安装 Docker 完整指南(附国内镜像加速)
linux·运维·docker
Zhang~Ling14 小时前
Vim 多模式详解:命令、插入、底行与插件配置
linux·编辑器·vim
lularible16 小时前
HSM技术精讲(3.8):证书对象——数字世界的“身份证明“
安全·开源·嵌入式·汽车电子·hsm
想你依然心痛16 小时前
Linux用户空间与内核空间通信:netlink、ioctl、mmap 零拷贝与性能对比
linux·运维·服务器
FreakStudio17 小时前
WIZnet 开源方案分享:W55MH32 开发板 + 小智 AI实现以太网版本的小智机器人
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机