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;/*指明该等待的进程是互斥还是非互斥,为0时为非互斥,为1时为互斥*/
#define WQ_FLAG_EXCLUSIVE 0x01
void *private;/*一般被赋值为task_struct类型的指针,等待队列就是通过此结构与一个进程相关联的*/
wait_queue_func_t func;/*函数指针,指向等待队列的唤醒函数*/
struct list_head task_list;
};

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




等待队列:
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);/*将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);/*恢复进程状态为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);/**改变进程状态为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);/*向等待队列头xxx_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);/*设置进程状态为TASK_RUNNING*/









































  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值