描述和API

阻塞IO和非阻塞IO的应用编程时的处理机制是不同的,如果是非阻塞IO在访问资源未就绪时就直接返回-EAGAIN,反之阻塞IO则会使当前用户进程睡眠直到资源可用。从应用场景来说两种方式分别适应不同的使用场景。而驱动开发不可避免的需要支持两种访问方式。如果不是采用现成的子框架而自己实现文件操作底层接口部分时就需要自己实现这一机制。文件的访问方式除了在打开文件时指定外还可以在打开以后通过fcnt和ioctl进行修改和获取。

阻塞IO 在实现过程依赖两个重要的数据结构等待队列头(wait_queue_head_t)和等待队列成员(wait_queue_t)具体的定义如下:

struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
}; typedef struct __wait_queue wait_queue_t; struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
void *private;
wait_queue_func_t func;
struct list_head task_list;
};

实际上都是Linux内核关于链表管理的通用方式再加上互斥资源的封装操作。使用也特别简单直接看API和具体的使用方法。

定义

使用等待队列前需要先定义一个等待队列头,可以使用动态和静态的方式进行定义。等待队列成员相同也可以使用两种方式创建,不过内核都提供了方便的宏用来完成对等待成员的定义和初始化。

初始化

等待队列头的初始化使用init_waitqueue_head(wait_queue_head_t* head)进行初始化。而等待队列成员常使用__WAITQUEUE_INITIALIZER如下宏进行定义和初始化。

//初始化队列头
#define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((q), #q, &__key); \
} while (0) //初始化队列成员
#define __WAITQUEUE_INITIALIZER(name, tsk) { \
.private = tsk, \
.func = default_wake_function, \
.task_list = { NULL, NULL } } #define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

添加和移除等待队列

//将wait 加入到 head 队列中
void add_wait_queue(wait_queue_head_t *head,wait_queue_t *wait )
//将wait 从 head 队列中移除
void remove_wait_queue(wait_queue_head_t *head,wait_queue_t *wait )

在队列上等待事件

wait_event(wq, condition)  注意传入的参数wq为队列实体而不是地址,condition 为“并”逻辑条件。

#define wait_event(wq, condition)                    \
do { \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0) #define __wait_event(wq, condition) \
(void)___wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0, \
schedule()) #define ___wait_event(wq, condition, state, exclusive, ret, cmd) \
({ \
__label__ __out; \
wait_queue_t __wait; \
long __ret = ret; /* explicit shadow */ \
\
INIT_LIST_HEAD(&__wait.task_list); \
if (exclusive) \
__wait.flags = WQ_FLAG_EXCLUSIVE; \
else \
__wait.flags = 0; \
\
for (;;) { \
long __int = prepare_to_wait_event(&wq, &__wait, state);\
\
if (condition) \
break; \
\
if (___wait_is_interruptible(state) && __int) { \
__ret = __int; \
if (exclusive) { \
abort_exclusive_wait(&wq, &__wait, \
state, NULL); \
goto __out; \
} \
break; \
} \
\
cmd; \
} \
finish_wait(&wq, &__wait); \
__out: __ret; \
})

可以中断的接口 wait_event_interruptible(wq, condition) 

#define wait_event_interruptible(wq, condition)                \
({ \
int __ret = 0; \
if (!(condition)) \
__ret = __wait_event_interruptible(wq, condition); \
__ret; \
}) #define __wait_event_interruptible(wq, condition) \
___wait_event(wq, condition, TASK_INTERRUPTIBLE, 0, 0, \
schedule())

支持超时的接口 wait_event_timeout(wq, condition, timeout) 

#define wait_event_timeout(wq, condition, timeout)            \
({ \
long __ret = timeout; \
if (!___wait_cond_timeout(condition)) \
__ret = __wait_event_timeout(wq, condition, timeout); \
__ret; \
}) #define ___wait_cond_timeout(condition) \
({ \
bool __cond = (condition); \
if (__cond && !__ret) \
__ret = 1; \
__cond || !__ret; \
}) #define __wait_event_timeout(wq, condition, timeout) \
___wait_event(wq, ___wait_cond_timeout(condition), \
TASK_UNINTERRUPTIBLE, 0, timeout, \
__ret = schedule_timeout(__ret))

支持中断和超时wait_event_interruptible_timeout(wq, condition, timeout)

#define wait_event_interruptible_timeout(wq, condition, timeout)    \
({ \
long __ret = timeout; \
if (!___wait_cond_timeout(condition)) \
__ret = __wait_event_interruptible_timeout(wq, \
condition, timeout); \
__ret; \
}) #define __wait_event_interruptible_timeout(wq, condition, timeout) \
___wait_event(wq, ___wait_cond_timeout(condition), \
TASK_INTERRUPTIBLE, 0, timeout, \
__ret = schedule_timeout(__ret))

通过观察以上接口最终都是通过调用___wait_event(wq, condition, state, exclusive, ret, cmd) 接口来实现的等待事件。

唤醒队列

void wake_up(wait_queue_head_t* queue);
void wake_up_interruptible(wait_queue_head_t* queue);

需要注意的是两个接口都用来唤醒一个队列上的所有等待进程,而wake_up可以唤醒TASK_INTERRUPTIBLR和TASK_UNINTERRUPTIBLE 两种状态的进程。而wake_up_interruptible仅能用来唤醒TASK_INTERRUPTIBLR状态的进程即调用wait_event_interruptible和wait_event_interruptible_timeout()接口进入等待的进程。除此之外还有两个接口用于快速在一个访问接口中睡眠;

sleep_on(wait_queue_head_t* queue);
interruptible_sleep_on(wait_queue_head_t* queue);

两个接口都是会定义一个等待成员并将其添加到等待队列上设置进程为对应状态后睡眠直到资源可用。

使用示例

在驱动私有数据中先定义一个等待队列头并在模块安装时初始化。

int xxx_init(void)
{
...
init_waitqueue_head(&xxx_wait_head);
...
}

阻塞

static ssize_t xxx_write(struct file* file ,const char* buf,size_t cnt,lofft_t *ppos)
{
//从私有数据中或全局的方式拿到等待队列头xxx_queue //访问接口中
DECLARE_WAITQUEUE(xx_wait,currrent);//currrent 为当前进程PCB
add_wait_queue(&xxx_queue,&xx_wait);
...
if(file->flags & O_NONBLOCK)
{
return -EAGAIN;
}else
{
__set_current_state(TASK_INTERRUPTIABLE);
schedule();
if(signal_pending(currrent)){
//因为此处是可信号中断睡眠的所以,有可能因为信号唤醒
//所以需要判断是否是因为信号唤醒,如是则返回请重新调用系统调用
return -ERESTARTSYS;
}
}
...
}

唤醒

static ssize_t xxx_read(struct file* file ,const char* buf,size_t cnt,lofft_t *ppos)
{
//从私有数据中或全局的方式拿到等待队列头xxx_queue
//访问接口中
....
//或wake_up(&xxx_queue) 与前面阻塞相对应
wake_up_interruptible(&xxx_queue)
.... }

这里示例未使用wait_event相关的接口,但实际上这些接口的实现类似上面使用过程的。如 __wait_event_interruptible的旧版内核实现,这里看旧版是因为旧版的宏接口封装更加容易理解。

#define __wait_event_interruptible(wq, condition, ret)            \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \
if (condition) \
break; \
if (!signal_pending(current)) { \
schedule(); \
continue; \
} \
ret = -ERESTARTSYS; \
break; \
} \
finish_wait(&wq, &__wait); \
} while (0)

驱动的轮询支持

应用编程过程中常常也会使用非阻塞的IO操作,因为可能应用的逻辑是判断是否有数据可用如果有则执行A处理,没有则执行B处理。如果在IO操作时阻塞则就无法执行B处理过程,最重要的是如果需要同时操作多个IO时如果非阻塞的方式打开则会降低程序的性能,因为频繁的调用系统接口然后返回 -EAGAIN 且需要同时处理多个IO的情况下就需要编写很多的读取判断函数接口,过多的系统调用会降低程序的执行性能所以需要IO轮询接口从而实现IO多路复用系统提供了select()和poll()调用,在内核内部最后都会调用驱动提供的poll()接口。poll机制的实现基本思路是

1、设备驱动定义一个等待队列

2、poll接口调用将调用进程挂接到这个队列上(poll_wait(file,&xxx_queue,wait))

3、由中断或其他机制唤醒这个队列

4、返回对应事件类型的掩码(POLLRDNORM 、POLLIN数据可读、POLLRDNORM 、POLLOUT数据可写)

示例:

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

static unsigned drivers_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); /* 将进程挂接到button_waitq等待队列下 */ /* 根据实际情况,标记事件类型 */
if (ev_press)
mask |= POLLIN | POLLRDNORM; /* 如果mask为0,那么证明没有请求事件发生;如果非零说明有时间发生 */
return mask;
}
poll_wait接口详情:
/*
* Do not touch the structure directly, use the access functions
* poll_does_not_wait() and poll_requested_events() instead.
*/
typedef struct poll_table_struct {
poll_queue_proc _qproc;
unsigned long _key;
} poll_table; static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && p->_qproc && wait_address)
p->_qproc(filp, wait_address, p);
}

它是将当前进程添加到由系统提供poll_table中,实际的作用就是在驱动唤醒wait_address 指定的等待队列头时可以同时唤醒因为调用轮叙接口select或这poll接口而进入睡眠的进程。至此就是驱动中阻塞相关使用的简单学习归纳了,后续在实际使用中提炼总结。

Linux 驱动框架---驱动中的阻塞的更多相关文章

  1. Linux 驱动框架---驱动中的异步

    异步IO是对阻塞和轮询IO的机制补充,所谓异步IO就是在设备数据就绪时主动通知所属进程进行处理的机制.之所以说是异步是相对与被通知进程的,因为进程不知道也无法知道什么时候会被通知:这一机制非常类似于硬 ...

  2. Linux 驱动框架---驱动中的中断

    在单片机开发中中断就是执行过程中发生了一些事件需要及时处理,所以需要停止当前正在运行的处理的事情转而去执行中断服务函数,已完成必要的事件的处理.在Linux中断一样是如此使用但是基于常见的中断控制器的 ...

  3. Linux 驱动框架---驱动中的并发

    并发指多个执行单元被同时.并行的执行,而并发执行的单元对共享资源的访问就容易导致竟态.并发产生的情况分为抢占和并行(多核)和硬抢占(中断).Linux为解决这一问题增加了一系列的接口来解决并发导致的竟 ...

  4. Linux 驱动框架---驱动中的时间相关

    内核中的时间 Linux 系统内核对于时间的管理依赖于硬件,硬件按一定的周期产生中断,周期由内核的一个配置值HZ决定在系统启动时会将定时器配置为HZ值指定的频率产生中断:同时内核和维护一个64位(X8 ...

  5. 驱动框架入门——以LED为例[【转】

    本文转载自;http://blog.csdn.net/oqqHuTu12345678/article/details/72783903 以下内容源于朱有鹏<物联网大讲堂>课程的学习,如有侵 ...

  6. I2C驱动框架(四)

    参考:I2C子系统之platform_driver初始化——I2C_adap_s3c_init() 在完成platform_device的添加之后,i2c子系统将进行platform_driver的注 ...

  7. Linux设备驱动中的阻塞和非阻塞I/O

    [基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件后再进行操作.被挂起的进程进入休眠状态(不占用cpu资源),从调度器的运行队列转移到等待队列,直到 ...

  8. 蜕变成蝶~Linux设备驱动中的阻塞和非阻塞I/O

    今天意外收到一个消息,真是惊呆我了,博客轩给我发了信息,说是俺的博客文章有特色可以出本书,,这简直让我受宠若惊,俺只是个大三的技术宅,写的博客也是自己所学的一些见解和在网上看到我一些博文以及帖子里综合 ...

  9. Linux设备驱动中的阻塞和非阻塞I/O <转载>

    Green 博客园 首页 新随笔 联系 订阅 管理 Linux设备驱动中的阻塞和非阻塞I/O   [基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件 ...

随机推荐

  1. 找出10000内的素数 CSP

    "Problem: To print in ascending order all primes less than 10000. Use an array of processes, SI ...

  2. HTML Standard系列:Event loop、requestIdleCallback 和 requestAnimationFrame

    HTML Standard系列:Event loop.requestIdleCallback 和 requestAnimationFrame - 掘金 https://juejin.im/post/5 ...

  3. 【笔记】学习markdown

    经过来自学长(姐?)的 嘲讽 善意提醒后,我才知道这个博客园好像 资瓷 markdown 于是我决定要认真学习markdown(绝不是因为洛谷题解又过不去了) 正常点: 由于没人教,我上网查了一下 一 ...

  4. MySql(三)存储过程和函数

    MySql(三)存储过程和函数 一.什么是存储过程和函数 二.存储过程和函数的相关操作 一.什么是存储过程和函数 存储过程和函数是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程和函数 ...

  5. Go语言学习笔记(3)——面向对象编程

    把存货赶紧更新一波(捂脸) 1. 类型系统 类型系统,就是说一种编程语言怎么设计的它的类型的体系结构. 比如基础类型啊,复合类型啊,一些可以指向任意对象的类型啊,以及类型的语义,面向对象的特性,接口, ...

  6. Session (简介、、相关方法、流程解析、登录验证)

    Session简介 Session的由来 Cookie虽然在一定程度上解决了"保持状态"的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能 ...

  7. Django(图书管理系统)

    图书管理系统 注意事项 1.models 要创建好,规划好自己的表,以及各种表关系 2.url正则要写好 3.settings的配置 4.利用bootstarp 进行布局更漂亮哦 5.注意orm  各 ...

  8. 我用了半年的时间,把python学到了能出书的程度

    Python难学吗?不难,我边做项目边学,过了半年就通过了出版社编辑的面试,接到了一本Python选题,并成功出版. 有同学会说,你有编程基础外带项目实践机会,所以学得快.这话不假,我之前的基础确实加 ...

  9. 开发环境管理利器Vagrant

    引言 不知道你是否经历过,开发环境与生产环境不一致.Windows开发和Linux上的包效果不一样.在我这运行时好的啊 等等等问题,那有没有解决方法呢? 答案就是Vagrant.Docker 1.简介 ...

  10. 深入Jar包:Gradle构建可执行jar包与访问jar包中文件夹与文件

    前言 Java的跨平台功能听起来很诱人可口,号称"Write Once,Run Everywhere",实际上是"Run Once,Debug Everywh" ...