嵌入式Linux驱动开发入门:从字符设备到设备树DTS - 第1节
1.1 什么是Linux驱动
在嵌入式Linux系统中,驱动(Device Driver)是操作系统内核与硬件设备之间的桥梁。它负责将上层应用的通用操作(如读、写、控制)转换为特定硬件设备能够理解的寄存器操作序列。没有驱动,操作系统就无法控制任何外设——从简单的LED灯到复杂的摄像头、Wi-Fi模块。
驱动开发在嵌入式Linux开发中占据核心地位。据统计,在嵌入式Linux项目中,驱动开发的工作量通常占整个系统开发的 40%~60%。理解驱动的本质,是入门嵌入式Linux开发的第一步。
1.2 驱动分类:三大类型
Linux内核将设备驱动划分为三大基本类型:字符设备、块设备和网络设备。每种类型对应不同的硬件特性和访问模式。
1.2.1 字符设备(Character Device)
字符设备以字节流的形式进行数据传输,数据按顺序逐个字节地读写,不支持随机访问。典型的字符设备包括:串口、GPIO、I2C、SPI、触摸屏、键盘、鼠标等。
- 访问方式: open / read / write / ioctl / close
- 设备文件: 位于 /dev 目录下,如 /dev/ttyS0、/dev/input/event0
- 主设备号: 标识驱动类型;次设备号: 标识同一驱动下的不同设备实例
- 特点: 简单、直接、适合低速设备
1.2.2 块设备(Block Device)
块设备以数据块为单位进行数据传输,支持随机访问(可寻址)。典型的块设备包括:eMMC、NAND Flash、SD卡、硬盘、U盘等。
- 访问方式: 通过文件系统挂载后,以文件形式读写
- 设备文件: 位于 /dev 目录下,如 /dev/mmcblk0、/dev/sda1
- 核心机制: 请求队列(request queue)和I/O调度
- 特点: 支持缓存、批量读写、适合大容量存储
1.2.3 网络设备(Network Device)
网络设备负责数据包的发送和接收,它不通过设备文件访问,而是通过内核网络协议栈(如TCP/IP)与用户空间交互。典型的网络设备包括:以太网控制器、Wi-Fi模块、蓝牙、CAN总线等。
- 访问方式: socket / netlink / 网络协议栈
- 设备名称: 如 eth0、wlan0、can0
- 核心机制: 中断驱动、DMA传输、NAPI轮询
- 特点: 异步、数据包导向、不依赖设备文件
1.2.4 三大驱动类型对比
| 对比维度 | 字符设备 | 块设备 | 网络设备 |
|---|---|---|---|
| 数据单位 | 字节流 | 数据块(512B/4KB等) | 数据包(MTU) |
| 访问模式 | 顺序读写 | 随机读写 | 异步收发 |
| 用户接口 | /dev/xxx 设备文件 | /dev/xxx + 文件系统 | socket / netlink |
| 典型设备 | 串口、GPIO、I2C | eMMC、SD卡、U盘 | 以太网、Wi-Fi、CAN |
| 驱动复杂度 | 低~中 | 中~高 | 高 |
| 入门推荐 | ✅ 首选 | 进阶 | 进阶 |
1.3 内核空间与用户空间
Linux系统将内存划分为两个特权级别不同的区域:内核空间(Kernel Space)和用户空间(User Space)。这是Linux安全性和稳定性的基石。
1.3.1 概念与区别
- 内核空间: 操作系统内核运行的地方,拥有最高权限(Ring 0),可以直接访问所有硬件资源和内存地址。
- 用户空间: 应用程序运行的地方,权限受限(Ring 3),不能直接访问硬件,必须通过系统调用请求内核服务。
这种隔离机制确保了:即使某个应用程序崩溃,也不会影响整个系统的稳定性。驱动运行在内核空间,因此驱动代码的错误(如空指针、内存越界)可能导致整个系统崩溃——这就是驱动开发需要格外谨慎的原因。
1.3.2 用户空间与内核空间的交互方式
用户空间程序与内核空间驱动之间的数据交换,主要通过以下机制实现:
| 交互方式 | 说明 | 典型API |
|---|---|---|
| 系统调用 | 用户程序通过软中断陷入内核 | open / read / write / ioctl |
| /dev 设备文件 | 通过文件操作接口访问驱动 | fopen / fread / fwrite |
| /sys 文件系统 | 通过sysfs属性文件读写驱动参数 | cat / echo |
| /proc 文件系统 | 读取内核运行时信息 | cat /proc/cpuinfo |
| netlink | 内核与用户空间的socket通信 | socket(AF_NETLINK) |
| mmap | 将内核内存映射到用户空间 | mmap() 系统调用 |
1.3.3 内核空间与用户空间数据传递示意图
1.3.4 数据传递的关键函数
由于内核空间和用户空间的内存地址映射不同,驱动不能直接访问用户空间传递的指针。必须使用内核提供的专用函数进行安全的数据拷贝:
// 从用户空间拷贝数据到内核空间
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
// 从内核空间拷贝数据到用户空间
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
// 字符串拷贝(用户空间 <-> 内核空间)
long strncpy_from_user(char *dst, const char __user *src, long count);
int *p = arg; *p = 0x1234; 这会导致内核崩溃。必须使用 copy_from_user / copy_to_user 系列函数。
1.4 本章小结
- Linux驱动 是内核与硬件之间的桥梁,运行在内核空间。
- 驱动分为 字符设备、块设备、网络设备 三大类,初学者应从字符设备入手。
- 内核空间 拥有最高权限,直接管理硬件;用户空间 权限受限,通过系统调用请求内核服务。
- 用户空间与内核空间的数据传递必须使用
copy_from_user/copy_to_user等安全函数。 - 理解这些基础概念,是后续编写第一个字符设备驱动的必要前提。