【Note】《深入理解Linux内核》Chapter 13 :深入理解 Linux 内核中的 I/O 架构与设备驱动

《深入理解Linux内核》Chapter 13 :深入理解 Linux 内核中的 I/O 架构与设备驱动

关键词:I/O 子系统、字符设备、块设备、VFS、设备驱动、file_operations、设备号、cdev、gendisk、驱动模型、模块加载、udev


一、Linux I/O 架构总览

1.1 为什么需要抽象化的 I/O 架构?

  • 设备多样性(硬盘、串口、键盘、GPU);
  • 设备访问方式差异极大;
  • 用户空间程序期望统一的访问接口。

因此,Linux 内核定义了统一的 设备驱动模型,将设备与内核、用户空间解耦。

1.2 I/O 子系统的三层结构

+------------------------+
| 用户空间(应用程序)    |
| open(), read(), ioctl()|
+------------------------+
| 虚拟文件系统(VFS)     |
| 统一的文件接口           |
+------------------------+
| 设备驱动                |
| 字符/块/网络设备驱动     |
+------------------------+
| 硬件设备                |
+------------------------+
  • 应用调用系统调用接口;
  • VFS 负责路径查找与权限管理;
  • 设备驱动通过 file_operations 提供操作实现;
  • 实际访问硬件由驱动完成。

二、字符设备与块设备的区别

2.1 字符设备(Character Device)

  • 字节流方式进行访问;
  • 不支持随机访问;
  • 如串口、终端、输入设备、声卡。

2.2 块设备(Block Device)

  • 固定大小块(如 512B、4KB)访问;
  • 支持随机访问;
  • 如硬盘、SSD、RAM 磁盘。

2.3 网络设备属于独立类别,不通过 VFS 接口,而由 socket 层处理。


三、设备号与设备节点

每个设备由一个唯一的 设备号(device number) 标识,包含:

  • 主设备号(major number):标识驱动程序;
  • 次设备号(minor number):标识驱动内的具体设备。
# 查看设备号
ls -l /dev/ttyS0
crw-rw---- 1 root dialout 4, 64 /dev/ttyS0
  • 4 是主设备号,64 是次设备号。

四、字符设备驱动的注册与操作流程

4.1 注册字符设备

字符设备在内核中用 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);

4.2 实现 file_operations 接口

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() 访问时,最终会调用上述函数。

4.3 创建设备节点

#include 

struct class *my_class = class_create(THIS_MODULE, "myclass");
device_create(my_class, NULL, devno, NULL, "mydev");

将会在 /dev/mydev 生成一个设备节点。


五、块设备驱动的结构

5.1 块设备驱动框架

  • 每个块设备用 gendisk 表示;
  • 调度器将请求封装成 bio;
  • 驱动通过 request_queue 接收请求并完成;

5.2 注册块设备

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);

5.3 IO 请求处理

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() 创建请求队列;
  • 用于支持调度器、合并请求、延迟执行等。

六、模块化驱动设计与热插拔

6.1 模块编写框架

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");

6.2 加载与卸载

insmod mymodule.ko
rmmod mymodule

内核模块支持动态加载,热插拔设备(如 USB)可以自动加载对应驱动。


七、udev 与设备自动管理

Linux 中用户空间的 udev 子系统负责:

  • 动态创建 /dev 下的设备节点;
  • 加载模块、设定权限;
  • 响应内核发送的 uevent

例如插入 U 盘时,内核会发出 uevent,udev 捕捉后:

  • 创建 /dev/sdb
  • 执行 mount 或提示用户挂载。

八、I/O 缓存与内核缓冲区

设备读写通常是慢速操作,内核通过缓冲机制提高性能:

  • 页缓存(page cache):用于块设备(磁盘文件);
  • 环形缓冲区(circular buffer):用于字符设备(串口);
  • request queue:支持 IO 调度与合并优化;
  • bio 结构:表示底层块设备操作单元。

九、中断处理与驱动

驱动可以注册中断处理函数:

request_irq(irq_number, handler, IRQF_SHARED, "mydev", dev_id);
  • 中断触发时执行 handler;
  • 可与工作队列(workqueue)、tasklet 协作完成下半部分处理。

十、sysfs 与驱动模型统一接口

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:内核调试用虚拟文件系统;

十三、设备驱动开发建议

  1. 从简单的字符设备开始学习;
  2. 使用 qemu + 虚拟设备测试驱动加载与卸载;
  3. 理解中断上下文与同步机制(spinlock vs mutex);
  4. 使用日志打印配合 dmesg 调试驱动;
  5. 学习设备模型、设备树(platform driver)、内核事件处理。

十四、源码文件索引

文件 作用描述
fs/char_dev.c 字符设备注册与管理
fs/block_dev.c 块设备操作实现
drivers/char/ 多种字符设备驱动源码
drivers/block/ 块设备驱动(如 loop、ramdisk)
include/linux/fs.h file_operations, inode 等结构定义
drivers/base/ 统一驱动模型代码

你可能感兴趣的:(读书笔记,linux,linux,架构,运维)