Linux阻塞与非阻塞I/O:从原理到实践详解
Linux阻塞与非阻塞I/O:从原理到实践详解
1. 阻塞与非阻塞I/O基础概念
1.1 阻塞与非阻塞简介
在Linux系统编程中,I/O操作可以分为两种基本模式:阻塞I/O和非阻塞I/O。这两种模式决定了当设备或资源不可用时,程序的行为方式。
阻塞I/O就像你在餐厅点餐后坐在座位上等待服务员上菜。在此期间你不能做其他事情,只能等待食物送到面前。在编程中,这意味着当程序执行I/O操作时,如果数据未准备好,进程会进入睡眠状态,直到条件满足才会继续执行。
非阻塞I/O则像是自助餐厅的就餐方式。你拿着餐盘去取食物,如果某个菜品暂时没有,你不会站在那里等待,而是先去拿其他食物,过一会儿再来查看。在编程中,这意味着当I/O操作无法立即完成时,操作会立即返回一个错误码(如EAGAIN),而不会阻塞进程。
代码示例对比:
// 阻塞方式读取串口
fd = open("/dev/ttyS1", O_RDWR);
read(fd, &buf, 1); // 串口有输入才返回// 非阻塞方式读取串口
fd = open("/dev/ttyS1", O_RDWR | O_NONBLOCK);
while(read(fd, &buf, 1) != 1) continue; // 循环尝试读取
1.2 两种模式的优缺点
阻塞I/O的优点:
- CPU利用率高,因为等待时不占用CPU资源
- 编程模型简单直接
- 适合顺序处理任务
阻塞I/O的缺点:
- 响应性差,无法同时处理多个I/O操作
- 可能导致进程长时间挂起
非阻塞I/O的优点:
- 响应性好,可以同时监控多个I/O操作
- 进程不会被长时间挂起
- 适合高并发场景
非阻塞I/O的缺点:
- CPU利用率高,因为需要不断轮询
- 编程复杂度较高
- 可能导致忙等待(busy waiting)
2. 等待队列机制
2.1 等待队列的概念
等待队列是Linux内核中实现阻塞I/O的核心机制。它允许进程在条件不满足时进入睡眠状态,当条件满足时再被唤醒。这就像医院候诊室的叫号系统,病人(进程)可以坐着休息(睡眠),当轮到他们时会被叫醒(唤醒)。
2.2 等待队列的操作接口
- 定义和初始化等待队列头:
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
// 或者使用宏一次性完成
DECLARE_WAIT_QUEUE_HEAD(my_queue);
- 定义等待队列项:
DECLARE_WAITQUEUE(name, tsk); // tsk一般为current表示当前进程
- 添加/移除等待队列:
add_wait_queue(&my_queue, &wait); // 添加
remove_wait_queue(&my_queue, &wait); // 移除
- 等待事件:
wait_event(wq, condition); // 无条件等待
wait_event_timeout(wq, condition, timeout); // 带超时等待
wait_event_interruptible(wq, condition); // 可被信号中断的等待
- 唤醒队列:
wake_up(&queue); // 唤醒所有等待的进程
wake_up_interruptible(&queue); // 只唤醒可中断的进程
2.3 等待队列的使用模板
一个典型的驱动中使用等待队列的模板如下:
static ssize_t device_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
{DECLARE_WAITQUEUE(wait, current);add_wait_queue(&dev->read_queue, &wait);// 等待数据可用while (data_not_ready()) {if (file->f_flags & O_NONBLOCK) { // 非阻塞模式检查remove_wait_queue(&dev->read_queue, &wait);return -EAGAIN;}__set_current_state(TASK_INTERRUPTIBLE);schedule(); // 让出CPUif (signal_pending(current)) { // 检查是否有信号remove_wait_queue(&dev->read_queue, &wait);return -ERESTARTSYS;}}// 数据已准备好,进行读取操作copy_to_user(buffer, dev->data, count);remove_wait_queue(&dev->read_queue, &wait);set_current_state(TASK_RUNNING);return count;
}
3. 轮询机制
3.1 轮询的概念
对于非阻塞I/O,当操作不能立即完成时,应用程序需要通过轮询的方式不断检查设备是否就绪。这就像你等待快递时不断查看物流信息,而不是坐在门口一直等待。
3.2 常见的轮询机制
- select系统调用:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select使用位图(fd_set)来表示文件描述符集合,有以下操作宏:
FD_ZERO(fd_set *set); // 清空集合
FD_SET(int fd, fd_set *set); // 添加描述符
FD_CLR(int fd, fd_set *set); // 移除描述符
FD_ISSET(int fd, fd_set *set); // 检查描述符
- poll系统调用:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll使用动态数组而非固定大小的位图,没有文件描述符数量限制。pollfd结构体定义如下:
struct pollfd {int fd; // 文件描述符short events; // 等待的事件short revents; // 实际发生的事件
};
- epoll机制:
epoll是为处理大并发而设计的更高效的机制,使用红黑树管理文件描述符,避免了select/poll的线性扫描问题。
3.3 轮询机制的选择
- 少量文件描述符:select或poll都可以
- 大量文件描述符:优先选择epoll
- 跨平台需求:select兼容性最好
- 精确事件通知:poll或epoll更合适
4. 驱动中的poll操作函数
4.1 poll操作函数的作用
在Linux驱动中,poll操作函数用于支持select/poll系统调用,它需要完成两个主要任务:
- 将当前文件描述符加入适当的等待队列
- 返回设备当前的状态掩码
4.2 poll函数的实现模板
static unsigned int device_poll(struct file *filp, poll_table *wait)
{struct device_data *dev = filp->private_data;unsigned int mask = 0;poll_wait(filp, &dev->read_queue, wait);poll_wait(filp, &dev->write_queue, wait);if (data_available(dev)) // 检查是否可读mask |= POLLIN | POLLRDNORM;if (space_available(dev)) // 检查是否可写mask |= POLLOUT | POLLWRNORM;return mask;
}
4.3 poll支持的事件标志
POLLIN
:有普通或优先级带数据可读POLLRDNORM
:有普通数据可读POLLRDBAND
:有优先级带数据可读POLLPRI
:有高优先级数据可读POLLOUT
:写数据不会导致阻塞POLLWRNORM
:写普通数据不会导致阻塞POLLWRBAND
:写优先级带数据不会导致阻塞POLLERR
:发生错误POLLHUP
:设备已断开连接POLLNVAL
:文件描述符未打开
5. 阻塞I/O实验:实现一个FIFO设备驱动
5.1 实验目标
实现一个支持阻塞读写的全局FIFO设备驱动:
- 当FIFO为空时,读进程阻塞
- 当FIFO满时,写进程阻塞
- 支持select/poll监控
5.2 关键数据结构
#define FIFO_SIZE 4096struct globalfifo_dev {struct cdev cdev;unsigned int current_len;unsigned char mem[FIFO_SIZE];struct mutex mutex;wait_queue_head_t read_wait;wait_queue_head_t write_wait;
};
5.3 读函数实现
static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{struct globalfifo_dev *dev = filp->private_data;DECLARE_WAITQUEUE(wait, current);int ret = 0;mutex_lock(&dev->mutex);add_wait_queue(&dev->read_wait, &wait);while (dev->current_len == 0) {if (filp->f_flags & O_NONBLOCK) {ret = -EAGAIN;goto out;}__set_current_state(TASK_INTERRUPTIBLE);mutex_unlock(&dev->mutex);schedule();if (signal_pending(current)) {ret = -ERESTARTSYS;goto out2;}mutex_lock(&dev->mutex);}if (count > dev->current_len)count = dev->current_len;if (copy_to_user(buf, dev->mem, count)) {ret = -EFAULT;goto out;}memmove(dev->mem, dev->mem + count, dev->current_len - count);dev->current_len -= count;wake_up_interruptible(&dev->write_wait);ret = count;out:mutex_unlock(&dev->mutex);
out2:remove_wait_queue(&dev->read_wait, &wait);set_current_state(TASK_RUNNING);return ret;
}
5.4 写函数实现
static ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{struct globalfifo_dev *dev = filp->private_data;DECLARE_WAITQUEUE(wait, current);int ret = 0;mutex_lock(&dev->mutex);add_wait_queue(&dev->write_wait, &wait);while (dev->current_len == FIFO_SIZE) {if (filp->f_flags & O_NONBLOCK) {ret = -EAGAIN;goto out;}__set_current_state(TASK_INTERRUPTIBLE);mutex_unlock(&dev->mutex);schedule();if (signal_pending(current)) {ret = -ERESTARTSYS;goto out2;}mutex_lock(&dev->mutex);}if (count > FIFO_SIZE - dev->current_len)count = FIFO_SIZE - dev->current_len;if (copy_from_user(dev->mem + dev->current_len, buf, count)) {ret = -EFAULT;goto out;}dev->current_len += count;wake_up_interruptible(&dev->read_wait);ret = count;out:mutex_unlock(&dev->mutex);
out2:remove_wait_queue(&dev->write_wait, &wait);set_current_state(TASK_RUNNING);return ret;
}
5.5 poll函数实现
static unsigned int globalfifo_poll(struct file *filp, poll_table *wait)
{struct globalfifo_dev *dev = filp->private_data;unsigned int mask = 0;mutex_lock(&dev->mutex);poll_wait(filp, &dev->read_wait, wait);poll_wait(filp, &dev->write_wait, wait);if (dev->current_len != 0)mask |= POLLIN | POLLRDNORM;if (dev->current_len != FIFO_SIZE)mask |= POLLOUT | POLLWRNORM;mutex_unlock(&dev->mutex);return mask;
}
5.6 测试方法
- 加载驱动模块:
insmod globalfifo.ko
- 创建设备节点:
mknod /dev/globalfifo c 250 0
- 测试阻塞读:
# 终端1
cat /dev/globalfifo# 终端2
echo "Hello World" > /dev/globalfifo
- 测试非阻塞读:
cat /dev/globalfifo &
# 应该立即返回,显示资源暂时不可用
6. 非阻塞I/O实验:按键驱动实现
6.1 实验目标
实现一个支持非阻塞读的按键驱动:
- 当没有按键事件时,非阻塞读立即返回
- 当有按键事件时,读取按键值
- 支持中断处理
6.2 关键数据结构
struct key_dev {int gpio;int irq;char name[10];atomic_t key_value;atomic_t key_pressed;wait_queue_head_t waitq;
};
6.3 读函数实现
static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{struct key_dev *dev = filp->private_data;int ret;unsigned char value;if (filp->f_flags & O_NONBLOCK) {if (!atomic_read(&dev->key_pressed))return -EAGAIN;} else {wait_event_interruptible(dev->waitq, atomic_read(&dev->key_pressed));}value = atomic_read(&dev->key_value);if (copy_to_user(buf, &value, 1))return -EFAULT;atomic_set(&dev->key_pressed, 0);return 1;
}
6.4 中断处理函数
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{struct key_dev *dev = dev_id;int gpio_value = gpio_get_value(dev->gpio);if (gpio_value == 0) { // 按键按下atomic_set(&dev->key_value, KEY_VALUE_PRESSED);} else { // 按键释放atomic_set(&dev->key_value, KEY_VALUE_RELEASED);atomic_set(&dev->key_pressed, 1);wake_up_interruptible(&dev->waitq);}return IRQ_HANDLED;
}
6.5 测试方法
- 加载驱动模块:
insmod key.ko
- 创建设备节点:
mknod /dev/key c 240 0
- 测试非阻塞读:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>int main()
{int fd = open("/dev/key", O_RDONLY | O_NONBLOCK);char key_val;while (1) {if (read(fd, &key_val, 1) == 1) {printf("Key event: %d\n", key_val);} else {printf("No key event, doing other work...\n");sleep(1);}}close(fd);return 0;
}
7. 总结与选择建议
7.1 阻塞与非阻塞I/O对比
特性 | 阻塞I/O | 非阻塞I/O |
---|---|---|
行为 | 等待直到操作完成 | 立即返回,成功或失败 |
CPU使用 | 等待时不占用CPU | 需要主动轮询 |
响应性 | 低 | 高 |
编程复杂度 | 简单 | 较复杂 |
适用场景 | 简单同步操作 | 高并发或快速响应 |
7.2 选择建议
-
选择阻塞I/O当:
- 处理简单的顺序任务
- 不需要同时处理多个I/O操作
- 资源通常能快速就绪
-
选择非阻塞I/O当:
- 需要同时监控多个I/O操作
- 要求快速响应
- 处理高并发连接
-
对于驱动开发者:
- 通常需要同时支持阻塞和非阻塞模式
- 正确实现等待队列和poll操作
- 注意并发控制和竞态条件
通过理解阻塞和非阻塞I/O的原理和实现方式,开发者可以根据具体应用场景选择最合适的I/O模型,编写出高效可靠的Linux驱动程序。
相关文章:
Linux阻塞与非阻塞I/O:从原理到实践详解
Linux阻塞与非阻塞I/O:从原理到实践详解 1. 阻塞与非阻塞I/O基础概念 1.1 阻塞与非阻塞简介 在Linux系统编程中,I/O操作可以分为两种基本模式:阻塞I/O和非阻塞I/O。这两种模式决定了当设备或资源不可用时,程序的行为方式。 阻…...

【MySQL】MySQL索引与事务
目录 前言 1. 索引 (index) 1.1 概念 1.2 作用 1.3 使用场景 1.4 索引的相关操作 查看索引 创建索引 删除索引 2. 索引背后的数据结构 2.1 B树 2.2 B+树的特点 2.3 B+树的优势 3. 事务 3.1 为什么使用事务 3.2 事…...

华为网路设备学习-19 IGP路由专题-路由策略
一、 二、 注意: 当该节点匹配模式为permit下时,参考if else 当该节点匹配模式为deny下时: 1、该节点中的apply子语句不会执行。 2、如果满足所有判断(if-match)条件时,拒绝该节点并跳出(即不…...
力扣-234.回文链表
题目描述 给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。 class Solution { public:bool isPalindrome(ListNode* head) {//快慢指针找到中间结点p1(偶数个结点…...
Spring Boot 整合 Lock4j + Redisson 实现分布式锁实战
本文基于 Spring Boot 2.7.x MyBatis Plus 3.5.9,演示如何通过 Lock4j 与 Redisson 实现高可靠的分布式锁方案,解决高并发场景下的资源竞争问题。 一、依赖配置关键点 1.1 Maven 依赖(pom.xml) <dependency><groupId&g…...

基于DrissionPage的表情包爬虫实现与解析(含源码)
目录 编辑 一、环境配置与技术选型 1.1 环境要求 1.2 DrissionPage优势 二、爬虫实现代码 三、代码解析 3.1 类结构设计 3.2 目录创建方法 3.3 图片链接获取 3.4 图片下载方法 四、技术升级对比 4.1 代码复杂度对比 4.2 性能测试数据 五、扩展优化建议 5.1 并…...
无限debugger实现原理
1. 直接调用 debugger 关键字 代码示例: debugger; // 手动触发调试器中断特点: 最简单的方式,直接插入 debugger 语句。若未在浏览器开发者工具中禁用断点,每次执行到此代码都会暂停。反制手段:可通过浏览器开发者…...

区间和数量统计 之 前缀和+哈希表
文章目录 1512.好数对的数目2845.统计趣味子数组的数目1371.每个元音包含偶数次的最长子字符串 区间和的数量统计是一类十分典型的问题:记录左边,枚举右边策略前置题目:统计nums[j]nums[i]的对数进阶版本:统计子数组和%modulo k的…...

全能 Sui 技术栈,构建 Web3 的未来
本文翻译自:FourPillarsFP,文章仅代表作者观点。 2025 年,SuiNetwork正在以一套全栈区块链策略强势出击,彻底打破加密行业的传统范式。正如 Mysten Labs 联合创始人 Adeniyi Abiodun 所说:“Sui 不只是一条区块链&…...
什么是爬虫?——从技术原理到现实应用的全面解析 V
什么是爬虫?——从技术原理到现实应用的全面解析 V 二十一、云原生爬虫架构设计 21.1 无服务器爬虫(AWS Lambda) # lambda_function.py import boto3 import requests from bs4 import BeautifulSoups3 = boto3.client(s3)def lambda_handler(event, context):# 抓取目标…...
(三) Trae 调试C++ 基本概念
调试C基本概念 一、调试基础概念1.1 调试信息格式1.2 DWARF格式和PDB格式生成(图解)1.3.典型工具链和调试信息 二、各工具链深度解析1. Clang 与 G 的 DWARF 差异 三 调试工具3.1 调试工具3.2 调试插件(Trae) 一、调试基础概念 1.1 调试信息格式 格式类型适用系统存在形式DWA…...

linux安装单节点Elasticsearch(es),安装可视化工具kibana
真的,我安装个es和kibana,找了好多帖子,问了好几遍ai才安装成功,在这里记录一下,我相信,跟着我的步骤走,99%会成功; 为了让大家直观的看到安装过程,我把我服务器的es和ki…...
Python项目--基于计算机视觉的手势识别控制系统
1. 项目概述 1.1 项目背景 随着人机交互技术的快速发展,传统的键盘、鼠标等输入设备已经不能满足人们对自然、直观交互的需求。手势识别作为一种非接触式的人机交互方式,具有操作自然、交互直观的特点,在智能家居、游戏控制、虚拟现实等领域…...
上海SMT贴片加工核心工艺与优化方案
内容概要 作为电子制造领域的核心环节,上海SMT贴片加工技术通过精密工艺实现元器件的高效贴装与可靠焊接。本文聚焦钢网印刷、回流焊、AOI检测等关键工艺节点,结合物料定位误差修正与BGA缺陷预防,系统阐述技术优化路径。同时,基于…...

RK3xxx 部分无法连接虚拟机 无法进行adb连接
我发现部分rk板子可以连接到虚拟机上,部分连接不上。其中尝试了一块是安卓系统的rk板子是可以连接虚拟机。但是用了linux系统的rk板子连接不上虚拟机。尝试了很多办法还是无法连接虚拟机。 然后也看到一些相关资料,但是太少了,只有这个链接提…...
Kohya-ss-gui v25.0.3 训练Flux.1 大模型命令参数
Kohya-ss-gui v25.0.3 训练Flux.1 大模型命令参数 本文是博主的训练笔记,这篇是记录训练Flux.1大模型的命令行参数: 数据结构 /app/data/Flux大模型/train/img . └── 10_skm qili├── 10x4096_4096x4096_flux.npz├── 10x4096.jpg├── 10x4096…...

26考研——存储系统(3)
408答疑 文章目录 一、存储器概述二、主存储器三、主存储器与 CPU 的连接四、外部存储器五、高速缓冲存储器六、虚拟存储器七、参考资料鲍鱼科技课件26王道考研书 八、总结复习提示思考题常见问题和易混淆知识点 一、存储器概述 文章链接: 点击跳转 二、主存储器 文章链接: …...
【prompt是什么?有哪些技巧?】
Prompt(提示词)是什么? Prompt 是用户输入给AI模型(如ChatGPT、GPT-4等)的指令或问题,用于引导模型生成符合预期的回答。它的质量直接影响AI的输出效果。 Prompt 的核心技巧 1. 明确目标(Clar…...
Yocto meta-toradex-security layer 创建独立数据分区
By Toradex 胡珊逢 简介 Toradex 为其产品使用的软件系统如 Linux 提供了诸多的安全功能,例如 Secure Boot、分区加密、OP-TEE 等,帮助用户应对安全合规。这些功能可以通过在 Yocto Project 中添加由 Toradex 开发的 meta-toradex-securitylayer 被轻松…...
MQTT学习资源
MQTT入门:强烈推荐...

C# 实战_RichTextBox选中某一行条目高亮,离开恢复
C# 中控件richtextbox中某一行的条目内容高亮,未选中保持不变。当鼠标点击某一行的条目高亮,离开该条目就恢复默认颜色。 运行效果: 核心代码实现功能: //高亮指定行的方法private void HighlightLine(RichTextBox rtb,int lineI…...
深度解析:从12306看混合云架构下的高并发系统设计
作为曾参与12306余票查询系统高并发升级的技术从业者,笔者注意到公众对于12306底层技术常存在认知盲区。为破解这一迷思,特此分享十年前的架构解密文献(该技术之前名叫 gemfire 现已晋升为Apache顶级项目Geode,代码库详见…...
分布式队列对消息语义的处理
在分布式系统中,消息的处理语义(Message Processing Semantics)是确保系统可靠性和一致性的关键。有三种语义: 在分布式系统中,消息的处理语义(Message Processing Semantics)是确保系统可靠性和…...

Servlet小结
视频链接:黑马servlet视频全套视频教程,快速入门servlet原理servlet实战 什么是Servlet? 菜鸟教程:Java Servlet servlet: server applet Servlet是一个运行在Web服务器(如Tomcat、Jetty)或应用…...

2025上海车展:光峰科技全球首发“灵境”智能车载光学系统
当AI为光赋予思想,汽车将会变成什么样?深圳光峰科技为您揭晓答案。 2025年4月23日,在刚刚开幕的“2025上海车展”上,全球领先的激光核心器件公司光峰科技举办了主题为“AI光影盛宴,智享未来出行”的媒体发布会&#x…...

BiliNote:开源的AI视频笔记生成工具,让知识提取与分享更高效——跨平台自动生成结构化笔记,实现从视频到Markdown的智能转化
引言:视频学习的痛点与BiliNote的解决方案 随着知识视频化趋势的加速,B站、YouTube等平台成为学习与信息获取的重要渠道,但手动记录笔记耗时低效、信息碎片化等问题依然突出。BiliNote的出现,通过AI驱动的自动化流程,将视频内容转化为结构清晰的Markdown笔记,支持截图插…...
docker 运行时权限和 Linux 能力了解
文档参考: https://docs.docker.com/engine/containers/run/#runtime-privilege-and-linux-capabilities https://docs.docker.com/reference/cli/docker/container/run/#privileged 本片主要了解容器在运行时如何赋予的格外的权限,默认情况下࿰…...

图纸安全防护管理:构建企业核心竞争力的关键屏障
在当今高度竞争的商业环境中,图纸作为企业核心技术的重要载体,其安全防护管理已成为企业知识产权保护体系中的关键环节。无论是建筑行业的施工蓝图、制造业的产品设计图,还是高科技企业的研发图纸,都承载着企业的核心竞争力和商业…...
如何用WordPress AI插件自动生成SEO文章,提升网站流量?
1. 为什么你需要一个WordPress AI文章生成插件? 每天手动写文章太耗时?SEO优化总是不达标?WordPress AI插件能帮你24小时自动生成原创内容,从关键词挖掘到智能排版,全程无需人工干预。 痛点:手动写作效率低…...

借助内核逻辑锁pagecache到内存
一、背景 内存管理是一个永恒的主题,尤其在内存紧张触发内存回收的时候。系统在通过磁盘获取磁盘上的文件的内容时,若不开启O_DIRECT方式进行读写,磁盘上的任何东西都会被缓存到系统里,我们称之为page cache。可以想象࿰…...