当前位置: 首页 > news >正文

imx6ull-驱动开发篇27——Linux阻塞和非阻塞 IO(上)

目录

阻塞和非阻塞

等待队列

等待队列头

等待队列项

将队列项添加/移除等待队列头

等待唤醒

等待事件


阻塞和非阻塞

IO 指的是 Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。

当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。

对于非阻塞 IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。

阻塞式 IO 如图:

应用程序调用 read 函数从设备中读取数据,当设备不可用或数据未准备好的时候就会进入到休眠态。等设备可用的时候就会从休眠态唤醒,然后从设备中读取数据返回给应用程序。

应用程序可以使用如下所示示例代码来实现阻塞访问:

int fd;
int data = 0;// 以阻塞方式打开设备文件(默认行为)
fd = open("/dev/xxx_dev", O_RDWR); // 尝试从设备读取数据(阻塞式读取)
int ret = read(fd, &data, sizeof(data));

对于设备驱动文件的默认读取方式就是阻塞式的。

非阻塞 IO 如图:

应用程序使用非阻塞访问方式从设备读取数据,当设备不可用或数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新读取数据,这样一直往复循环,直到数据读取成功。

如果应用程序要采用非阻塞的方式来访问驱动设备文件,可以使用如下所示代码:

int fd;
int data = 0;// 以非阻塞方式打开设备文件
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); // 尝试从设备读取数据(非阻塞式读取)
int ret = read(fd, &data, sizeof(data));

使用 open 函数打开“/dev/xxx_dev”设备文件的时候添加了参数“O_NONBLOCK”,表示以非阻塞方式打开设备,这样从设备中读取数据的时候就是非阻塞方式的了。

等待队列

等待队列头

Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作。

如果我们要在驱动中使用等待队列,必须创建并初始化一个等待队列头,等待队列头使用结构体wait_queue_head_t 表示。

wait_queue_head_t 结构体定义在文件 include/linux/wait.h 中,结构体内容如下所示:

// 等待队列头结构体(内核等待机制核心数据结构)
struct __wait_queue_head {spinlock_t lock;          // 自旋锁,保护任务列表的并发访问struct list_head task_list; // 等待任务链表头(双向链表)
};// 类型重定义,简化使用
typedef struct __wait_queue_head wait_queue_head_t;

定义好等待队列头以后需要初始化, 使用 init_waitqueue_head 函数初始化等待队列头。

init_waitqueue_head函数原型如下:

void init_waitqueue_head(wait_queue_head_t *q)
  • 参数 q 就是要初始化的等待队列头。

也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义的初始化。

示例代码如下:

/*  动态初始化(非静态变量) */
wait_queue_head_t *q = kmalloc(sizeof(*q), GFP_KERNEL);
if (q) init_waitqueue_head(q);/*  快速初始化宏  */DECLARE_WAIT_QUEUE_HEAD(my_queue);  // 静态声明并初始化

等待队列项

等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。

结构体 wait_queue_t 表示等待队列项,结构体内容如下:

// 等待队列条目结构(表示一个等待任务)
struct __wait_queue {unsigned int flags;            // 状态标志(如WQ_FLAG_EXCLUSIVE)void *private;                 // 通常指向关联的进程描述符(task_struct)wait_queue_func_t func;        // 唤醒回调函数struct list_head task_list;    // 链表节点,用于链接到等待队列头
};// 类型重定义,简化使用
typedef struct __wait_queue wait_queue_t;

使用宏 DECLARE_WAITQUEUE 定义并初始化一个等待队列项,宏的内容如下:

DECLARE_WAITQUEUE(name, tsk)
  • name 就是等待队列项的名字,
  • tsk 表示这个等待队列项属于哪个任务(进程),一般设置为current 。

在 Linux 内 核 中 current 相 当 于 一 个 全 局 变 量 , 表 示 当 前 进 程 。 因 此 宏DECLARE_WAITQUEUE 就是,给当前正在运行的进程创建并初始化了一个等待队列项。

将队列项添加/移除等待队列头

当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,只有添加到等待队列头中以后进程才能进入休眠态。

当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可。

等待队列项添加 API 函数如下:

void add_wait_queue(wait_queue_head_t *q,  wait_queue_t *wait)
  • q: 等待队列项要加入的等待队列头。
  • wait:要加入的等待队列项。

等待队列项移除 API 函数如下:

void remove_wait_queue(wait_queue_head_t *q,  wait_queue_t *wait)
  • q: 要删除的等待队列项所处的等待队列头。
  • wait:要删除的等待队列项。

等待唤醒

当设备可以使用的时候就要唤醒进入休眠态的进程,唤醒可以使用如下两个函数:

/*** wake_up - 唤醒等待队列中的所有可唤醒进程* @q: 等待队列头指针** 唤醒所有状态为TASK_UNINTERRUPTIBLE和TASK_INTERRUPTIBLE的进程,* 但会跳过设置了WQ_FLAG_EXCLUSIVE标志的独占进程(只唤醒一个独占进程)。*/
void wake_up(wait_queue_head_t *q)
{__wake_up(q, TASK_NORMAL, 1, NULL);
}/*** wake_up_interruptible - 只唤醒可中断睡眠的进程* @q: 等待队列头指针** 仅唤醒状态为TASK_INTERRUPTIBLE的进程,* 忽略TASK_UNINTERRUPTIBLE状态的进程。*/
void wake_up_interruptible(wait_queue_head_t *q)
{__wake_up(q, TASK_INTERRUPTIBLE, 1, NULL);
}

参数 q 就是要唤醒的等待队列头,这两个函数会将这个等待队列头中的所有进程都唤醒。

wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的进程,

wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程。

等待事件

除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程。

和等待事件有关的 API 函数如表:

示例代码如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/delay.h>MODULE_LICENSE("GPL");// 定义等待队列头
static wait_queue_head_t my_wait_queue;
// 定义共享资源标志
static int resource_ready = 0;// 线程函数:模拟资源生产者
static int producer_thread(void *data)
{msleep(2000); // 模拟2秒的资源准备时间resource_ready = 1;// 使用不同唤醒API演示printk(KERN_INFO "Producer: Resource ready, waking waiters\n");wake_up(&my_wait_queue);          // 唤醒所有等待者(包括不可中断)// wake_up_interruptible(&my_wait_queue); // 只唤醒可中断等待者// wake_up_all(&my_wait_queue);           // 明确唤醒所有等待者return 0;
}static int __init mymodule_init(void)
{struct task_struct *producer;printk(KERN_INFO "Waitqueue API Demo Module init\n");// 初始化等待队列init_waitqueue_head(&my_wait_queue);// 创建生产者线程producer = kthread_run(producer_thread, NULL, "resource_producer");/**************** 等待API演示 ****************/// 方法1:基础等待(不可中断)wait_event(my_wait_queue, resource_ready);printk(KERN_INFO "wait_event() returned\n");// 重置标志用于下一次演示resource_ready = 0;// 方法2:带超时的可中断等待int ret = wait_event_interruptible_timeout(my_wait_queue, resource_ready, HZ * 3); // 3秒超时if (ret == 0)printk(KERN_INFO "Timeout occurred!\n");else if (ret == -ERESTARTSYS)printk(KERN_INFO "Interrupted by signal!\n");elseprintk(KERN_INFO "Resource ready with %d jiffies left\n", ret);return 0;
}static void __exit mymodule_exit(void)
{// 确保所有等待者被唤醒wake_up_all(&my_wait_queue);printk(KERN_INFO "Module exit\n");
}module_init(mymodule_init);
module_exit(mymodele_exit);
http://www.dtcms.com/a/334209.html

相关文章:

  • 【JS】认识并实现一个chrome扩展程序
  • 如何在 MacOS 上安装 SQL Server
  • MySQL完整重置密码流程(针对 macOS)
  • 硬核北京 | 2025世界机器人大会“破圈”,工业智能、康养科技…… 亦庄上演“机器人总动员”
  • Flink Sql 按分钟或日期统计数据量
  • 中本聪思想与Web3的困境:从理论到现实的跨越
  • 存算分离与云原生:数据平台的新基石
  • 基于Kubernetes亲和性与反亲和性的Pod调度优化实践指南
  • Linux上配置环境变量
  • 从频繁告警到平稳发布:服务冷启动 CPU 风暴优化实践01
  • Trae中`settings.json`文件的Java配置项功能详解(一)
  • Camera相机人脸识别系列专题分析之十九:MTK ISP6S平台FDNode原生代码
  • 【vscode使用说明】
  • Vue中的数据渲染【4】
  • Docker自定义镜像
  • 138-基于FLask的重庆市造价工程信息数据可视化分析系统
  • Chrome腾讯翻译插件transmart的安装
  • RK3588芯片在AR眼镜中的核心技术优势是什么?
  • VS Code配置MinGW64编译ALGLIB库
  • 新字符设备驱动实验
  • pytest tmpdir fixture介绍(tmpdir_factory)(自动在测试开始前创建一个临时目录,并在测试结束后删除该目录)
  • c# WebAssembly,在网页上能运行多线程,异步,锁,原子加,减等代码吗
  • springboot集成websocket
  • css实现圆角+边框渐变+背景半透明
  • 深入详解PCB布局布线技巧-去耦电容的摆放位置
  • 上位机知识篇---Linux日志
  • Python基础语法 从入门到精通
  • MATLAB基础训练实验
  • GitHub PR 提交流程
  • 车载控制器硬件电路-各电源轨和功能模块定义以及作用