三种常见的 linux 设备的驱动介绍及框架
- 按照读写存储数据方式,我们可以把设备分为以下几种:字符设备、块设备和网络设备。而Linux三大驱动就是指对这些设备的驱动,即字符设备、块设备驱动和网络设备驱动。
1. 字符设备 (Character Devices)
- 字符设备是一种按字节流(character stream)进行访问的设备,不可寻址,没有缓冲。你请求 5 个字节,它就给你 5 个字节(如果设备里有的话)。它不支持随机访问,数据只能顺序读写。
原理
- 核心:
file_operations
结构体。里面定义了当用户空间程序对设备文件调用open()
,read()
,write()
,ioctl()
等系统调用时,内核应该执行的对应驱动函数。 - VFS (虚拟文件系统): 当
open("/dev/mydevice", ...)
时,VFS 会根据路径找到对应的 inode(索引节点),inode 中包含了设备号(主设备号和次设备号)。 - 驱动注册: 驱动在加载时,会通过
register_chrdev()
或alloc_chrdev_region()
+cdev_add()
来告诉内核能处理主设备号为 X 的设备,操作函数菜单是file_operations
结构体。 - 连接: VFS 通过主设备号找到驱动和
file_operations
,然后调用实现的my_open()
,my_read()
等函数,从而将用户空间的操作连接到了驱动代码上。
- 核心:
典型例子:
- 串口 (
/dev/ttyS*
)、控制台 (/dev/console
)、鼠标 (/dev/input/mouse0
)、键盘(/dev/input/event*)
。 - I2C/SPI 设备: 虽然挂在特定总线上,但最终给用户提供的接口往往是字符设备,如一个 I2C 接口的温湿度传感器,可能会表现为
/dev/i2c-1
或通过sysfs
访问。 - 裸设备驱动: 各种自定义的、简单的控制类设备
- 串口 (
代码示例
2. 块设备 (Block Devices)
- 块设备是按“块”(Block)为单位进行数据访问的设备,块是固定大小的(如 512 字节、4KB)。与字符设备最大的不同是: 支持随机寻址它可以随机访问(直接读写第 N 个块),并且 有内核I/O缓冲区。
原理
- 核心:
block_device_operations
结构体和 请求队列 (Request Queue)。 - I/O 调度器: 当用户程序请求读写数据时,请求不会立即发送给硬件。而是被分解成一个个对“块”的操作请求(
struct request
),放入一个请求队列中。内核的 I/O 调度器 会对队列里的请求进行合并、排序,以提高磁盘寻道效率(比如把对相邻块的请求放在一起处理)。 - 缓冲/缓存 (Buffer Cache): 内核会把频繁访问的块设备数据缓存在内存中(Page Cache/Buffer Cache)。当用户请求读取数据时,如果缓存里有,就直接从内存返回,速度极快,根本不需要访问物理设备。写操作也可能先写入缓存,稍后再“刷”到磁盘上。
- 驱动的角色: 块设备驱动的主要工作不是直接处理
read/write
,而是从请求队列中取出已经由 I/O 调度器优化好的request
,然后根据request
里的信息(起始块号、块数量、方向),操作硬件来完成真正的数据传输。 - 使用: 通常不直接用
read/write
对/dev/sda
这样的裸设备进行操作(虽然也可以)。但更常见的用法是:在块设备上创建文件系统(mkfs.ext4 /dev/sda1
),然后mount
到一个目录上。之后,用户和程序就通过文件系统来访问,享受到了文件系统和块设备层共同带来的高效和便利。
- 核心:
典型例子: 硬盘 (HDD/SSD)(
/dev/sda
)、U盘 (/dev/sdb
)、SD卡 (/dev/mmcblk0
)、RAM disk(内存模拟的块设备)、Flash 存储 (通过 MTD): NAND/NOR Flash 在 MTD 层之上也可以表现为块设备。代码示例
3. 网络设备 (Network Devices)
- 网络设备是用于收发数据包(Packet)的设备。它和其他两类设备有本质区别,它不对应
/dev
目录下的文件节点。而是通过单独的网络接口来代表。
原理
- 核心:
net_device_ops
结构体和sk_buff
(Socket Buffer)。- 接口而非文件: 网络设备在内核中被抽象成一个接口(Interface),如
eth0
,wlan0
。用户空间程序通过 Socket API(socket()
,bind()
,sendto()
,recvfrom()
)等内核协议栈来与内核的 TCP/IP 协议栈交互,而不是操作设备文件。 - 数据流:
- 发送: 用户数据通过 Socket API 进入内核协议栈,被层层打包(加上 TCP/UDP 头、IP 头等),最终形成一个
sk_buff
结构体。这个sk_buff
被交给网络设备驱动。驱动的ndo_start_xmit
函数(定义在net_device_ops
中)负责将sk_buff
里的数据包通过物理网卡发送出去。 - 接收: 网卡收到一个数据包,产生硬件中断。驱动的中断处理程序把数据从硬件接收到内存,封装成一个新的
sk_buff
,然后把它交给内核网络协议栈。协议栈逐层解包,最后通过 Socket 将数据送达正确的应用程序。
- 发送: 用户数据通过 Socket API 进入内核协议栈,被层层打包(加上 TCP/UDP 头、IP 头等),最终形成一个
- 驱动的角色: 网络设备驱动是硬件和内核协议栈之间的“搬运工”,主要负责:初始化网卡、启动/停止数据收发、在
sk_buff
和硬件之间传递数据包。
- 接口而非文件: 网络设备在内核中被抽象成一个接口(Interface),如
- 核心:
典型例子: 有线网卡 (
eth0
,enp3s0
)、无线网卡 (wlan0
)虚拟网络接口、 CAN总线设备、 USB网络适配器。代码示例
对比
特性 | 字符设备 (Char) | 块设备 (Block) | 网络设备 (Net) | 平台驱动 (Platform) |
---|---|---|---|---|
数据单位 | 字节流 (Stream) | 数据块 (Block) | 数据包 (Packet) | 不直接处理数据流 |
访问方式 | 顺序访问 | 随机访问 | Socket API | N/A |
I/O 缓冲 | 无 (或很简单) | 有内核缓冲/缓存和I/O调度 | 有 Socket 缓冲 | N/A |
用户接口 | /dev 文件节点 |
/dev 文件节点, 文件系统 |
Socket 接口, ifconfig |
通常是为其他驱动提供服务 |
核心结构体 | file_operations |
block_device_operations |
net_device_ops |
platform_driver |
核心机制 | VFS 文件操作映射 | 请求队列和I/O调度 | 协议栈和sk_buff |
设备与驱动的分离、匹配、探测 |
主要用途 | 简单、串行 I/O 设备 | 存储设备 | 网络通信 | SoC 内部集成外设的管理框架 |
字符设备代码示例
1 |
|
块设备代码示例
1 |
|
网络设备代码示例
1 |
|
三种常见的 linux 设备的驱动介绍及框架
https://goko-son626.github.io/post/the-Three-Basic-Linux-Driver-Models.html