关键词:I/O 子系统、字符设备、块设备、VFS、设备驱动、file_operations、设备号、cdev、gendisk、驱动模型、模块加载、udev
因此,Linux 内核定义了统一的 设备驱动模型,将设备与内核、用户空间解耦。
+------------------------+
| 用户空间(应用程序) |
| open(), read(), ioctl()|
+------------------------+
| 虚拟文件系统(VFS) |
| 统一的文件接口 |
+------------------------+
| 设备驱动 |
| 字符/块/网络设备驱动 |
+------------------------+
| 硬件设备 |
+------------------------+
每个设备由一个唯一的 设备号(device number) 标识,包含:
# 查看设备号
ls -l /dev/ttyS0
crw-rw---- 1 root dialout 4, 64 /dev/ttyS0
字符设备在内核中用 cdev
表示:
#include
struct cdev *my_cdev;
dev_t devno;
alloc_chrdev_region(&devno, 0, 1, "mydev");
my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
cdev_add(my_cdev, devno, 1);
static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *offset);
static ssize_t my_write(struct file *f, const char __user *buf, size_t len, loff_t *offset);
static int my_open(struct inode *i, struct file *f);
static int my_release(struct inode *i, struct file *f);
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.read = my_read,
.write = my_write,
.open = my_open,
.release = my_release,
};
read()
、write()
访问时,最终会调用上述函数。#include
struct class *my_class = class_create(THIS_MODULE, "myclass");
device_create(my_class, NULL, devno, NULL, "mydev");
将会在 /dev/mydev
生成一个设备节点。
gendisk
表示;request_queue
接收请求并完成;struct gendisk *gd = alloc_disk(1);
gd->major = major;
gd->first_minor = 0;
gd->fops = &my_block_fops;
gd->queue = my_request_queue;
add_disk(gd);
static void my_request_fn(struct request_queue *q)
{
struct request *req;
while ((req = blk_fetch_request(q)) != NULL) {
// 读取 req->sector, req->buffer
// 执行数据复制或硬件访问
__blk_end_request_all(req, 0);
}
}
blk_init_queue()
或 blk_alloc_queue()
创建请求队列;static int __init my_init(void) {
// 注册设备
return 0;
}
static void __exit my_exit(void) {
// 注销设备
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
insmod mymodule.ko
rmmod mymodule
内核模块支持动态加载,热插拔设备(如 USB)可以自动加载对应驱动。
Linux 中用户空间的 udev 子系统负责:
/dev
下的设备节点;uevent
。例如插入 U 盘时,内核会发出 uevent,udev 捕捉后:
/dev/sdb
;mount
或提示用户挂载。设备读写通常是慢速操作,内核通过缓冲机制提高性能:
驱动可以注册中断处理函数:
request_irq(irq_number, handler, IRQF_SHARED, "mydev", dev_id);
Linux 通过 sysfs
(通常挂载在 /sys
)暴露设备与驱动信息:
ls /sys/class/
ls /sys/block/
ls /sys/bus/
驱动注册时,创建设备类和属性,udev 可利用这些信息设置规则。
特性 | 字符设备 | 块设备 |
---|---|---|
访问粒度 | 字节流 | 块(通常为 512 字节或 4KB) |
支持缓存 | 无内核页缓存 | 使用 page cache 和 writeback |
文件系统挂载 | 不可挂载 | 支持挂载(如 /dev/sda1 ) |
接口函数 | file_operations | file_operations + 请求队列 |
示例设备 | 串口、终端、键盘 | 硬盘、SSD、光盘 |
dmesg
:查看模块加载、驱动信息;lsmod
:列出已加载模块;cat /proc/devices
:列出主设备号;udevadm monitor
:查看设备事件;strace
:跟踪系统调用,包括 open()
等;debugfs
:内核调试用虚拟文件系统;dmesg
调试驱动;文件 | 作用描述 |
---|---|
fs/char_dev.c |
字符设备注册与管理 |
fs/block_dev.c |
块设备操作实现 |
drivers/char/ |
多种字符设备驱动源码 |
drivers/block/ |
块设备驱动(如 loop、ramdisk) |
include/linux/fs.h |
file_operations , inode 等结构定义 |
drivers/base/ |
统一驱动模型代码 |