Linux驱动开发——(七)Linux阻塞和非阻塞IO

目录

一、阻塞和非阻塞IO简介

二、等待队列

2.1 等待队列头

2.2 等待队列项

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

2.4 等待唤醒

2.5 等待事件

三、轮询

四、驱动代码

4.1 阻塞IO

4.2 非阻塞IO


一、阻塞和非阻塞IO简介

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

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

对于非阻塞IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃,当设备不可用或数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新读取数据,这样一直往复循环,直到数据读取成功:

 应用程序使用如下代码来实现阻塞访问驱动设备文件:

int fd; 
int data = 0; 
 
fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */ 
ret = read(fd, &data, sizeof(data)); /* 读取数据 */

应用程序使用如下代码来实现非阻塞访问驱动设备文件:

int fd; 
int data = 0; 
 
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */ 
ret = read(fd, &data, sizeof(data)); /* 读取数据 */

二、等待队列

2.1 等待队列头

阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里完成唤醒工作

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函数初始化等待队列头:

void init_waitqueue_head(wait_queue_head_t *q)

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

2.2 等待队列项

每个访问设备的进程都是一个队列项,当设备不可用时要将这些进程对应的等待队列项添加到等待队列里面。结构体wait_queue_t表示等待队列项:

struct __wait_queue { 
    unsigned int flags; 
    void *private; 
    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就是给当前正在运行的进程创建并初始化一个等待队列项。

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

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

等待队列项添加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:要删除的等待队列项。
返回值:无。

2.4 等待唤醒

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

void wake_up(wait_queue_head_t *q) 
void wake_up_interruptible(wait_queue_head_t *q)

q:要唤醒的等待队列头,这两个函数会将该等待队列头中的所有进程都唤醒。
wake_up函数可以唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进
程;wake_up_interruptible函数只能唤醒处于TASK_INTERRUPTIBLE状态的进程。

2.5 等待事件

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

函数描述
wait_event(wq, condition)等待以wq为等待队列头的等待队列被唤醒,前提是condition条件必须满足 (为真 ),否则一直阻塞。此函数会将进程设置为TASK_UNINTERRUPTIBLE状态。
wait_event_timeout(wq, condition, timeout)功能和wait_event类似,但是此函数可以添加超时时间,以jiffies为单位。此函数有返回值,如果返回0的话表示超时时间到,而且condition为假,返回1的话表示condition为真,也就是条件满足了。
wait_event_interruptible(wq, condition)与wait_event函数类似,但此函数将进程设置为TASK_INTERRUPTIBLE,即可以被信号打断。
wait_event_interruptible_timeout(wq, condition, timeout)与wait_event_timeout函数和wait_event_interruptible函数类似。

三、轮询

如果用户应用程序非阻塞访问设备,设备驱动程序就要提供非阻塞的处理方式,即轮询。

当应用程序通过selectepollpoll函数来查询设备是否可以操作时,设备驱动程序中的poll操作函数就会执行,如果可以操作的话就从设备读取或者向设备写入数据:

unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)

filp:要打开的设备文件(文件描述符)。
wait结构体poll_table_struct类型指针,由应用程序传递进来的。一般将此参数传递给poll_wait函数。
返回值:向应用程序返回设备或者资源状态,可以返回的资源状态如表:

POLLIN有数据可以读取。
POLLPRI有紧急的数据需要读取。
POLLOUT可以写数据。
POLLERR指定的文件描述符发生错误。
POLLHUP指定的文件描述符挂起。
POLLNVAL无效的请求。
POLLRDNORM等同于 POLLIN,普通数据可读。

需要在驱动程序的poll函数中调用poll_wait函数,poll_wait函数不会引起阻塞,只是将应用程序添加到poll_table中:

void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

wait_address:要添加到poll_table中的等待队列头;

p:poll_table,即file_operations中poll操作函数的wait参数。


四、驱动代码

以Linux驱动开发——(六)按键中断实验的驱动代码为模板修改。

4.1 阻塞IO

添加宏:

#define IMX6UIRQ_NAME "blockio"

在imx6uirq设备结构体内添加变量:

wait_queue_head_t r_wait;

在定期器服务函数添加:

/* 唤醒进程 */
if(atomic_read(&dev->releasekey)) { /* 完成一次按键过程 */ 
    /* wake_up(&dev->r_wait); */ 
    wake_up_interruptible(&dev->r_wait); 
}

在按键初始化函数添加:

init_waitqueue_head(&imx6uirq.r_wait);

在read操作函数添加: 

DECLARE_WAITQUEUE(wait, current); /* 定义一个等待队列 */ 

if(atomic_read(&dev->releasekey) == 0) { /* 没有按键按下 */ 
    add_wait_queue(&dev->r_wait, &wait); /* 添加到等待队列头 */ 
    __set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态 */ 
    schedule(); /* 进行一次任务切换 */ 

    if(signal_pending(current)) { /* 判断是否为信号引起的唤醒 */ 
        ret = -ERESTARTSYS; 
    goto wait_error; 
    } 
    __set_current_state(TASK_RUNNING); /*设置为运行状态 */ 
    remove_wait_queue(&dev->r_wait, &wait); /*将等待队列移除 */ 
}

wait_error: 
    set_current_state(TASK_RUNNING); /* 设置任务为运行态 */ 
    remove_wait_queue(&dev->r_wait, &wait); /* 将等待队列移除 */
    return ret;

4.2 非阻塞IO

添加宏:

#define IMX6UIRQ_NAME "noblockio"

在read操作函数添加:

if (filp->f_flags & O_NONBLOCK) { /* 非阻塞访问 */ 
    if(atomic_read(&dev->releasekey) == 0) /* 没有按键按下 */ 
    return -EAGAIN; 
} else { /* 阻塞访问 */ 
    /* 加入等待队列,等待被唤醒,也就是有按键按下 */ 
    ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); 
    if (ret) { 
        goto wait_error; 
    } 
} 

wait_error: 
    return ret; 
data_error: 
    return -EINVAL;

添加poll操作函数:

unsigned int imx6uirq_poll(struct file *filp, 
struct poll_table_struct *wait) 
{ 
    unsigned int mask = 0; 
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *) 
    filp->private_data; 
 
    poll_wait(filp, &dev->r_wait, wait); 
 
    if(atomic_read(&dev->releasekey)) { /* 按键按下 */ 
        mask = POLLIN | POLLRDNORM; /* 返回PLLIN */ 
    }
    return mask; 
}

static struct file_operations imx6uirq_fops = { 
    .poll = imx6uirq_poll, 
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/577550.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

十个案例学习Flume

在上一篇文章中,已经知道了Flume的架构、概述、与安装,现在我们来用十个案例去学习flume的使用。 在使用之前,提供一个大致思想,使用Flume的过程是确定scource类型,channel类型和sink类型,编写conf文件并开…

零基础HTML教程(30)--迈入HTML5新时代

文章目录 1. 从H4时代到H5时代2. 属性值可以不用引号3. 标签使用大小写均可4. 部分属性值可以省略5. 浏览器支持情况6. 小结 1. 从H4时代到H5时代 之前讲的29篇HTML教程,内容基本都是H4时代就有的。 随着时代的发展,H4多少有点不够用,所以H…

Kotlin基础​​

数据类型 定义变量 var表示定义变量,可以自动推导变量类型,所以Int可以不用写。 定义常量 条件语句 if表达式可以返回值,该值一般写在if里的最后一行 类似switch的用法 区间 循环 a是标签,可以直接break到标签的位置&#xf…

【八大排序(二)】选择排序与堆排序

❣博主主页: 33的博客❣ ▶️文章专栏分类:八大排序◀️ 🚚我的代码仓库: 33的代码仓库🚚 🫵🫵🫵关注我带你了解更多排序知识 目录 1.前言2.选择排序2.1基本思想2.2画图理解2.3单向选择排序代码实现2.4双向选择排序代码…

从零入门区块链和比特币(第一期)

欢迎来到我的区块链与比特币入门指南!如果你对区块链和比特币感兴趣,但不知道从何开始,那么你来对地方了。本博客将为你提供一个简明扼要的介绍,帮助你了解这个领域的基础知识,并引导你进一步探索这个激动人心的领域。…

swagger xss漏洞复现

swagger xss漏洞复现 文章目录 swagger xss漏洞复现漏洞介绍影响版本实现原理漏洞复现修复建议: 漏洞介绍 Swagger UI 有一个有趣的功能,允许您提供 API 规范的 URL - 一个 yaml 或 json 文件,将被获取并显示给用户 根本原因非常简单 - 一个过时的库Dom…

预见预判|AIRIOT智慧交通管理解决方案

随着机动车保有量的逐步增加,城市交通压力日益增大。同时,新能源车辆的快速发展虽然带来了环保效益,但也因不限号政策而进一步加剧了道路拥堵问题。此外,各类赛事和重大活动的交通管制措施也时常导致交通状况复杂多变。面对这些挑…

Java 基础常见面试题整理

目录 1、java的基本数据类型有哪些?2、java为什么要有包装类型?3、String a "123" 和 String a new String("123") 区别?4、String、StringBuilder和StringBuffer的区别?5、如何理解面向对象和面向过程&…

MySQL常见问题与解决方案详述

MySQL:常见问题与解决方案详述 作为一款广泛使用的开源关系型数据库管理系统,MySQL对于初学者来说既充满吸引力又充满挑战。本文将列举初学者在使用MySQL过程中可能遇到的一些典型问题,并提供详细的解决方案,配以图片辅助说明&am…

修改后门ctime | Linux 后门系列

0x00 前情提要 在 alias 后门 | Linux 后门系列一文中,我们为了让后门完美一些,修改了后门文件的 atime、mtime,但是 ctime 一直没有办法修改,今天我们来把这一块补齐,让后门更加完美 atime -> access t…

Chrome 网络调试程序 谷歌网络调试 network

目录 1.网络面板总览2.概况了解3.Waterfall接口排队等待时间4.关注请求接口的Size,可能是占据内存溢出的接口5.过滤器一栏 fetch/xhr 什么意思6. Stalled 什么意思7.Queueing 什么意思8.Queueing和Stalled之间什么关系9.为什么会有阻塞状态10.Time列是pending 什么意思 1.网络面…

Vue入门篇:生命周期,钩子函数,工程化开发Vue(脚手架安装),组件化开发(全局注册,局部注册)

目录 1.Vue生命周期和生命周期的四个阶段2.Vue生命周期函数(钩子函数)3.工程化开发&脚手架Vue CLI1.在powershell管理员权限下打开命令行安装脚手架:2.查看vue版本:3.创建项目架子4.运行项目 4.组件化开发&根组件1.App.vue文件&#…

解决双击PDF文件出现打印的问题【Adobe DC】

问题描述 电脑安装Adobe Acrobat DC之后,双击PDF文件就会出现打印,而无法直接打开。 右键PDF文件就会发现,第一栏出现的不是用Adobe打开,而是打印。 重装软件多次仍然无法解决。 原因 右键菜单被改写了。双击其实是执行右键菜…

计算机网络—— book

文章目录 一、概述1.1互联网的核心部分1.电路交换的主要特点2.分组交换的主要特点 1.2.计算机网络的性能1.速率2.带宽3.吞吐量4.时延5.利用率 1.3.计算机网络体系结构协议与划分层次具有五层协议…

Git如何配合Github使用

1.安装Git https://git-scm.com/ ##2.配置 Git 安装完成后,你需要设置 Git 的用户名和邮箱地址,这样在提交代码时就能知道是谁提交的。你可以在命令行中输入以下命令来配置: git config --global user.name "Your Name" git con…

JavaScript创建和填充数组的更多方法

空数组fill()方法创建并填充数组 ● 我们之前创建数组的方式都是手动去创建去一个数据,例如 console.log([1, 2, 3, 4, 5, 6, 7]);● 当然我们也可以使用Array对象来构造数组 console.log([1, 2, 3, 4, 5, 6, 7]); console.log(new Array(1, 2, 3, 4, 5, 6, 7));…

惊爆:Apple重启OpenAI谈判为iphone引入其技术

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

用宝塔部署一套自己的漏洞扫描OpenVAS

一、OpenVAS简单说明 OpenVAS是一个开源且功能开放的网络安全漏洞评估系统,它集成了多种相关工具,构成了一套全面的网络扫描解决方案。因此,OpenVAS能够免费提供给用户部署和使用。在其最新版本中,仅需安装一个基于浏览器/服务器架…

【OceanBase诊断调优 】—— 如何快速定位SQL问题

作者简介: 花名:洪波,OceanBase 数据库解决方案架构师,目前负责 OceanBase 数据库在各大型互联网公司及企事业单位的落地与技术指导,曾就职于互联网大厂和金融科技公司,主导过多项数据库升级、迁移、国产化…

论文解读-面向高效生成大语言模型服务:从算法到系统综述

一、简要介绍 在快速发展的人工智能(AI)领域中,生成式大型语言模型(llm)站在了最前沿,彻底改变了论文与数据交互的方式。然而,部署这些模型的计算强度和内存消耗在服务效率方面带来了重大挑战&a…