天天看点

Linux驱动阻塞与非阻塞IO之等待队列

上次我和大家一起探讨了Linux驱动中的竞态问题,本环节为们来探讨一下Linux

驱动编写中的阻塞与非阻塞I/O

阻塞与非阻塞I/O简介

阻塞操作:是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件

后再进行操作,被挂起的进程会进入休眠状态,被从调度器的运行队列中移除,直到条件被满足。

非阻塞操作:是指在执行设备操作时,若不能获得资源,并不挂起,或者放弃他或者不停地的查询,直到可运行时

阻塞操作的作用:

阻塞从名字上好像是在说效率低,其实不然,如果设备驱动不阻塞,则用户想获取资源的进程只能不停地查询,这样反而会

无谓的消耗CPU的资源,而阻塞操作时不能获取资源的进程将进入休眠状态,它将自己在CPU所占用的资源让给了其他进程

唤醒:

既然阻塞会进入休眠状态,那么他也就需要被唤醒,唤醒进程最大可能发生在中断里面。

实例:以阻塞和非阻塞方式访问串口

1.阻塞地读串口的一个字符

char buf;

fd = open("/dev/ttySi",O_RDWR);

...

res = read(fd,&buf,1);

if(res == 1)

printf("%c\n",buf);

2.以非阻塞的方式读串口的一个字符

char buf;

fd = open("/dev/ttyS1",O_RDWR|O_NONBLOCK);

...

while(read(fd,&buf,1)!=1)

continue;

printf("%c\n",buf);

阻塞与唤醒进程的实现方法:

通常我我们用等待队列来实现线程的阻塞

在介绍等待队列的实现之前首先需要介绍三个个结构体,了解这三个结构体的组成对学习等待队列十分必要

(1). struct task_struct {

struct files_struct* files; //文件描述符

struct signal_struct* sig; //信号控制signal handler

struct mm_struct* mm;       //内存管理模块

long state                       //进程状态

struct list_head runlist;                    //用于联结RUN队列

long priority;             //基本优先权

long counter;              //变动优先权

char comm[];                                    //命令名

struct thread_struct tss;   //上下文保存领域

...

};

Linux中所有的进程由task_struct结构管理,每生成一个进程,就会生成一个task_struct结构的对象

通过此结构的state成员就可以改变进程的状态,等待队列的底层实现中正式通过将

将 __wait_queue中断额private指针指向此结构来实现对线程的睡眠与唤醒操作的,

(2).等待队列头结构体

.struct __wait_queue_head{

spinlock_t lock;

struct list_head task_list;

}

(3).//等待队列项结构体

typedef struct __wait_queue wait_queue_t;

struct __wait_queue{

unsigned int flag;

#define WQ_FLAG_EXCLUSIVE 0x01

void *private;

wait_queue_func_t func;

struct list_head task_list;

};

//内核等待队列的结构示意图

Linux驱动阻塞与非阻塞IO之等待队列

等待队列:

Linux驱动程序中可以使用等待队列(wait queue)来唤醒阻塞的进程

Linux2.6中关于等待队列的操作:

前言:对于等待队列的理解其实是有一定难度,而且在许多参考书籍上介绍的只是它的使用方法

,许多学员根本就能不请其中是怎样一回事,这里我简要概述一下:

Linux等待队列的实现机制:

首先Linux定义了一个链表,通常来说每个连表都存在以一个头节点,等待这里对应的

就是等待队列的等待队列头结构,而各个字节点对应于等待队列项结构,在等待队列项的数据域

中存在一个private指针,这个指针在定义等待队列项的时候被赋予了current值(刚才说过每个进程在

创建时系统都会分配一个task_struct结构对象,current就是代表当前线程的那个task_struct对象)

对于等待队列的操作几乎是与链表一致,无非是增删改查,那么你可能会问,这些跟线程的休眠又有屁关系那

.刚才讲了在等待队列项中存在一个成员private代表了当前的进程,通过这个指针你就可以控制某个进程

的状态。其实简单的说等待队列的实现机制就是这样的:定义等待队列项与某一(当前)进程相关联,然后加入到等待队列,

此后就可以通过这个"与某一线程相关联的等待队列项"对那个线程实施唤醒和阻塞

1.定义"等待队列头"

wait_queue_head_t my_queue;

2.初始化"等待队列头"

init_waitqueue_head(&my_queue);

//等并初始化等待队列头的快捷方式:

DECLARE_WAIT_QUEUE_HEAD(name);//这里讲name处填上my_queue后就与my_queue完全一致

3.定义等待队列

DECLARE_WAITQUEUE(name,tsk);

//定义并初始化一个名为name的等待队列,并让该等待队列的与一task_struct(即与一进程)相关联

//来看一下此宏的定义

#define DECLARE_WAITQUEUE(name,tsk)

wait_queue_t name = __WAITQUEUE_INITLIZER(name,tsk);

#define __WAITQUEUE_INITLIZER(name,tsk){

.tsk = tsk,

.func = default_wake_function,

.task_list = {NULL,NULL}}

}

4.添加/移除等待队列

void fastcall add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);

//向等待队列头q指向的等待队列“链表”中插入一个等待队列元素

void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);

//这个是移除啦

5.等待事件

//使用这个函数是实现线程阻塞的最简单方法,因为此函数已经帮我们把许多工作已经做好

//在后面的范例中你也许会惊讶为什么没有用到这个函数,其实在后面范例中所做的工作

//与此函数原理是相同的,我想作者只是想为我们演示一下

//下面四个函数必须保证condition被满足,否则继续阻塞

wait_event(queue,condition);//等待直到queue被唤醒【注意:queue(等待队列)必须是是等待队列头元素的时候】

wait_event_interrupible(queue,condition);

wait_event_timeout(queue,condition,timeout);//定义一个超时时间

wait_event_interruption_timeout(queue,condition,timeout);

//下面我们就深度剖析一下

#define wait_event(wq,condition)

do{

if(condition)

break;

__wait_event(wq_condition);

}while(0);

#define __wait_event(wq,condition)

do{

DEFINE_WAIT(__wait);

for(;;){

prepare_to_wait(&wq,&__wait,TASK_UNINTERRUPTIBLE);//设置当前进程状态为TASK_UNINTERRUPTIBLE,并将其对应的等待队列项加入等待队列

if(condition)

break;

schedule();//调度其他线程

}

finish_wait(&wq,&__wait);

}while(0)

void prepare_to_wait(wait_queue_head_t *q,wait_queue_t *wait,int state)

{

unsigned long flag;

wait->flags & = ~WQ_FLAG_EXCLUSIVE;

spin_lock_irqsave(&q->lock,flags);

if(list_empty(&wait->task_list))

__add_wait_queue(q,wait);

set_current_state(state);

spin_unlock_irqrestore(&q->lock,flags);

}

void finish_wait(wait_queue_head_t *q,wait_queue_t *wait)

{

unsigned long flags;

__set_current_state(TASK_RUNNING);

if(!list_empty_careful(&wait_list)){

spin_lock_irqsave(&q->lock,flags);

list_del_init(&wait->task_list);//task_list从其所在的链表中移除

spin_unlock_riqrestore(&q->lock,flags);

}

}

6.唤醒队列

void wake_up(wait_queue_head_t *queue);

void wake_up_interrupt(wait_queue_head_t *queue);

作用:唤醒以queue作为等待队列头的所有等待队列所对应的进程

,什么?这两个函数如何区分:大哥你四级没过吧.好吧还是说一下吧:

wait_event()可以唤醒处于TASK_INTERRUPTIBLE和TAK_UNINTERRUPTIBLE的进程,而

wake_up_interruptible()只能唤醒处于TASK_INTERRUPTIBLE的进程

7.在等待队列上睡眠

sleep_on(wait_queue_head_t *q);

//将目前进程状态设置为TASK_UNINTERRUPTIBLE,并定义一个等待队列,把它附属到等待队列头q指向等待队列群中,直到资源

可获得,q引导的等待队列被唤醒

interruptible_sleep_on(wait_queue_head_t *q);

//将目前的进程状态设置为TASK_INTERRUPTIBLE,并定义一个等待队列,其他与上一个函数一样

《在Linux设备驱动开发》的范例中你会发现作者并没有使用上面两个函数,而是使用了一个叫__set_current_state()的函数

一开始我也也很纳闷,可是后来仔细翻了一下书,发现原来sleep_on函数的使用中其实就是

调用了此函数,这里作者只是直接在范例中实现了sleep_on()

8.(补充两个函数)改变进程状态

__set_current_state(TASK_INTERRUPTIBLE);

__set_current_state(TASK_UNINTERRUPTIBLE);//你懂得

注意:6和7中的函数成对使用

static ssize_t xxx_write(struct file *file,const char *buffer,size_t count,loff_t *ppos)

{

...

DECLARE_WAITQUEUE(wait,current);

add_wait_queue(&xxx_wait,&wait);

ret = count;

do{

avail = device_writeable(...);

if(avail < 0)//小于0表示设备忙

__set_current_state(TASK_INTERRUPTIBLE);//改变进程状态为睡眠

if(avail < 0)

if(file->f_flags &O_NONBLOCK){

if(!ret)

ret = - EAGAIN;

goto out;

}

//如果设置的是阻塞状态,就调度其他进程

schedule();

if(signal_pending(current)){

if(!ret)

ret = -ERESTARTSYS;

goto out;

}

}

}while(avail < 0);

device_wirte(...);

out;

reomove_wait_queue(&xxx_wait,&wait);

set_current_state(TASK_RUNNING);

继续阅读