Liunx驱动 IO 模型

4 种 IO 模型

  • 阻塞 IO
  • 非阻塞 IO
  • poll /epoll 支持(多路复用)
  • 异步通知(信号驱动 IO,AIO 简化版)

1. 阻塞 IO(默认模式)

流程

应用调用 read() → 进入内核 → 内核没数据 → 把当前进程放进等待队列 → 进程休眠、让出 CPU→ 硬件 / 驱动有数据了 唤醒进程 → 拷贝数据到用户态 → 返回

特点

  • Linux 设备文件默认就是阻塞 IO
  • cat /dev/xxx 就是典型阻塞 IO
  • 没数据就卡死等待,不占用 CPU

驱动对应内核机制

等待队列 wait_queue_head_t 驱动里 wait_event_interruptible() 就是干这个的


2. 非阻塞 IO(O_NONBLOCK)

流程

应用打开设备加 O_NONBLOCK 标志调用 read() → 内核没数据 不休眠 直接立刻返回 -EAGAIN,告诉应用:现在没数据,你待会再来

特点

  • 不阻塞、不睡觉,马上返回
  • 应用要自己轮询 while 循环不停读
  • 浪费 CPU

驱动里怎么适配

file->f_flags & O_NONBLOCK 判断是否非阻塞,没数据直接返回 -EAGAIN


3. IO 多路复用(select /poll/epoll)

核心思想

一个线程监听多个文件描述符,哪个有数据就处理哪个,不用每个 fd 开一个线程阻塞等。

Linux 内核实现

  • 应用:epoll_create / epoll_ctl / epoll_wait
  • 驱动:必须实现 poll 函数
  • 内核维护等待队列,设备有数据时主动唤醒 epoll 监听进程

地位

Linux 高并发服务器底层全靠 epoll,是 Linux 性能最强的同步 IO 多路复用模型。


4. 异步 IO(AIO /io_uring)

流程

应用发起读请求,不用等 ,直接去干别的事内核自己后台把数据准备好,完事通知应用(信号 / 回调)

两个阶段

  1. 传统:libaio 老式异步 IO
  2. 现代 Linux 5.0+:io_uring 全新高性能异步 IO 框架,目前最强

特点

  • 全程不阻塞、不轮询
  • 内核主动帮你做完 IO 再通知
  • 高性能存储、数据库、网络框架都在用

一张表看懂四种 IO 模型区别

IO 模型 是否阻塞 是否轮询 内核关键机制 典型用法
阻塞 IO 阻塞等待 不轮询 等待队列、进程休眠 普通 read/cat
非阻塞 IO 不阻塞 应用轮询 判断 O_NONBLOCK 循环 while 读设备
多路复用 epoll 阻塞在 epoll_wait 内核监听 驱动 poll 函数 + 等待队列 高并发网络服务
异步 AIO/io_uring 完全不等 无需轮询 内核异步回调 高性能磁盘 / 网络

io_model_demo.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/wait.h>

#define MAJOR_NUM 250
#define DEV_NAME  "io_model_demo"

// 全局环形缓冲区(模拟硬件数据)
#define BUF_SIZE 100
static char rbuf[BUF_SIZE];
static int rlen = 0;

// 等待队列头:阻塞 IO 核心
static wait_queue_head_t r_wait;

// 异步 IO:异步通知结构体
static struct fasync_struct *r_fasync;

// 字符设备核心
static struct cdev my_cdev;
static struct class *my_class;
static struct device *my_dev;

/* =============================================
 * 1. 阻塞 IO + 非阻塞 IO:read 函数
 * ============================================= */
static ssize_t my_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    // 没有数据
    if (rlen == 0) {
        // 判断是否是非阻塞模式
        if (file->f_flags & O_NONBLOCK) {
            return -EAGAIN; // 非阻塞:直接返回,不睡觉
        }

        // 阻塞模式:进程休眠,等待数据
        wait_event_interruptible(r_wait, rlen != 0);
    }

    //os.system('echo polled | sudo tee /dev/io_model_demo >/dev/null')
    // 有数据:拷贝到用户空间
    if (size > rlen)
        size = rlen;

    if (copy_to_user(buf, rbuf, size))
        return -EFAULT;

    rlen = 0; // 读完清空
    return size;
}

/* =============================================
 * 2. IO 多路复用:poll 函数(epoll/select/poll)
 * ============================================= */
static __poll_t my_poll(struct file *file, poll_table *wait)
{
    __poll_t mask = 0;

    // 把当前进程挂到等待队列
    poll_wait(file, &r_wait, wait);

    // 数据可读 → 返回 POLLIN
    if (rlen > 0)
        mask |= POLLIN | POLLRDNORM;

    return mask;
}

/* =============================================
 * 3. 异步通知:信号驱动 IO(AIO 简化版)
 * ============================================= */
static int my_fasync(int fd, struct file *file, int on)
{
    return fasync_helper(fd, file, on, &r_fasync);
}

/* =============================================
 * 模拟写入数据(测试用)
 * ============================================= */
static void set_data(const char *str)
{
    strncpy(rbuf, str, BUF_SIZE - 1);
    rbuf[BUF_SIZE - 1] = '\0';
    rlen = strlen(rbuf);

    // 唤醒阻塞的进程
    wake_up_interruptible(&r_wait);

    // 异步通知:发信号给应用
    kill_fasync(&r_fasync, SIGIO, POLL_IN);
}

static ssize_t my_write(struct file *file, const char __user *buf,
			size_t size, loff_t *ppos)
{
    char kbuf[BUF_SIZE];

    if (size == 0)
        return 0;
    if (size >= BUF_SIZE)
        size = BUF_SIZE - 1;
    if (copy_from_user(kbuf, buf, size))
        return -EFAULT;
    kbuf[size] = '\0';
    set_data(kbuf);
    return size;
}

/* =============================================
 * 文件操作集合
 * ============================================= */
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read  = my_read,
    .write = my_write,
    .poll  = my_poll,      // 多路复用
    .fasync = my_fasync,   // 异步通知
};

static int __init my_init(void)
{
    dev_t devno = MKDEV(MAJOR_NUM, 0);

    // 初始化等待队列
    init_waitqueue_head(&r_wait);

    // 注册字符设备
    register_chrdev_region(devno, 1, DEV_NAME);
    cdev_init(&my_cdev, &fops);
    cdev_add(&my_cdev, devno, 1);

    // 自动创建设备节点
    my_class = class_create("io_model");
    my_dev = device_create(my_class, NULL, devno, NULL, DEV_NAME);

    printk("IO model driver init ok\n");
    return 0;
}

static void __exit my_exit(void)
{
    dev_t devno = MKDEV(MAJOR_NUM, 0);
    device_destroy(my_class, devno);
    class_destroy(my_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(devno, 1);
    printk("driver exit\n");
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");

1. 编译加载

cpp 复制代码
cd ~/project

make

sudo insmod io_model_demo.ko

sudo dmesg | tail -5

insmod 报 Device or resource busy,多半是固定主设备号 250 已被占用(例如 dax)。那时要先换动态设备号改驱动,或使用未被占用的主设备号------这里暂且假设能成功加载。

设备节点一般由 device_create 生成:/dev/io_model_demo。若权限不够,可先:

cpp 复制代码
sudo chmod 666 /dev/io_model_demo

2. 测「阻塞 read」

开两个终端。

终端 A(会卡住直到有数据):

cpp 复制代码
sudo cat /dev/io_model_demo

终端 B(写入触发 wake_up,cat 会读到并退出):

cpp 复制代码
echo "hello" | sudo tee /dev/io_model_demo

终端 A 应打印 hello(可能没有换行,取决于内核里缓冲区内容)。


3. 测「非阻塞 read」(应立刻返回,Resource temporarily unavailable

Bash 不好设 O_NONBLOCK,用 Python 最省事:

python 复制代码
sudo python3 - <<'PY'

import os

fd = os.open("/dev/io_model_demo", os.O_RDONLY | os.O_NONBLOCK)

try:

print(os.read(fd, 4096))

except BlockingIOError as e:

print("EAGAIN/nonblocking ok:", e.errno)

os.close(fd)

PY

无数据时应出现 BlockingIOError / errno 11 (EAGAIN);先 echo ... | sudo tee,再跑一次非阻塞读,应能读到数据。


4. 测「poll / select」(有数据才可读)

python 复制代码
sudo python3 - <<'PY'

import select, os

fd = os.open("/dev/io_model_demo", os.O_RDONLY | os.O_NONBLOCK)

r, _, _ = select.select([fd], [], [], 0.5)

print("before write:", r) # 多半 []

os.close(fd)

# 先写入

os.system('echo polled | sudo tee /dev/io_model_demo >/dev/null')

fd = os.open("/dev/io_model_demo", os.O_RDONLY | os.O_NONBLOCK)

r, _, _ = select.select([fd], [], [], 0.5)

print("after write:", r) # 应有 fd

print(os.read(fd, 4096))

os.close(fd)

PY

5. 测「异步信号 SIGIO」(需要先打开设备并注册)

需要自己写小段 C:对 fd 调用 fcntl 打开 O_ASYNCF_SETOWNF_SETSIG(可选);否则内核不会把你的进程登记进 r_fasync。这是进阶项,书里一般会配示例程序。


6. 卸载

python 复制代码
sudo rmmod io_model_demo

sudo dmesg | tail -3
相关推荐
计算机安禾4 小时前
【Linux从入门到精通】第39篇:版本控制Git服务器搭建——Gitea/GitLab私有化部署
linux·服务器·git
可视化运维管理爱好者4 小时前
pi mono操作开发指南
运维·网络·ai
浪客灿心4 小时前
Linux网络HTTP协议
linux
橙子也要努力变强4 小时前
volatile与信号
linux·服务器·c++
蜡笔小新拯救世界4 小时前
部分安全笔记总结
linux·网络·web安全
醇氧5 小时前
WSL 安装 Ubuntu 完整步骤(Windows 10/11 通用,极简无脑版)
linux·windows·ubuntu
中微子5 小时前
养虾小妙招:如何用 OpenClaw 把 Claude Code 调教成你的专属打工仔
linux·人工智能
TeDi TIVE5 小时前
Linux下MySQL的简单使用
linux·mysql·adb
Lucky_Turtle5 小时前
【Linux】debain13开启bbr
服务器·azure