Linux 驱动框架---驱动中的阻塞

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

描述和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接口而进入睡眠的进程。至此就是驱动中阻塞相关使用的简单学习归纳了,后续在实际使用中提炼总结。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: k8s

“Linux 驱动框架---驱动中的阻塞” 的相关文章

Go调度器学习之系统调用的方法是什么 - 开发技术

本篇内容主要讲解“Go调度器学习之系统调用的方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Go调度器学习之系统调用的方法是什么”吧!1. 系统调用下面,我们将以一个简单的文件打开的系统调用,来分析一下Go调度器在...

php如何实现边输入边显示查询结果功能 - 编程语言

这篇文章主要介绍了php如何实现边输入边显示查询结果功能的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇php如何实现边输入边显示查询结果功能文章都会有所收获,下面我们一起来看看吧。 PHP是一种常用的服务器端脚...

UVa 272 TEX Quotes (water ver.)

272 - TEX QuotesTime limit: 3.000 secondshttp://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_probl...

vue中如何判断用户是否安装了特定软件 - web开发

今天小编给大家分享一下vue中如何判断用户是否安装了特定软件的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。 首先,我们需要知...

Vue路由跳转没用的原因是什么及怎么解决 - web开发

这篇“Vue路由跳转没用的原因是什么及怎么解决”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Vue路由跳转没用的原因是什么及怎么解决”文章吧。...

一种APK打包构建代码版本信息的插件

VersionBuilder介绍基于Grovvy语言编写的Gradle插件,旨在解决应用apk在发布打包时,无法与git(svn)版本控制工程提交记录对应的问题。 这样做的主要目的是,在多渠道或复杂项目管理中,历史版本问题回溯排查方便。原理通过插件的方式,在Android工程构建编译时,hook资源...