对于像按键这样的输入设备而言(这里仅仅只是以按键做举例,具体看应用场景),什么时候按下按键,CPU和系统时不知道的。
在内核空间中,CPU可以产生中断进行通知,但是对于在用户空间而言,只能是在不停的监听按下按键后产生的键值信号,从而可以判断按键是否按下。
这就需要一些机制来解决这个问题了。在Linux系统中提供两种机制进行解决这一类问题,阻塞与非阻塞。
一、阻塞与非阻塞概述
1、阻塞:
通常是指系统设备在执行时,如果当前不能获得资源,则将挂起进程/线程,直到满足操作条件(获得资源)后,再往下进行操作,被挂起的进程/线程进入休眠状态,被从调度器移走,直到满足条件。
2、非阻塞:
系统设备在执行时,入港当前不能获得资源,进程/线程不被挂起,它或者放弃获取资源、或者轮询的查询资源,直到满足对于操作的条件(获得资源),去处理对应的事件。
3、当用户想要获取外部事件时,而此时事件不可得,那么应该让进程/线程休眠。
4、所谓阻塞,实际上是休眠。
5、如何休眠?
(1)改变进程的状态。
在Linux系统中,可以通过
set_current_state(state_value)
set_task_state(tsk,state_value)
这两个接口函数来改变进程的状态。其原型为:
set_current_state(state_value):
其中形参state_value可以取值为:
路径为:linux-3.0.8/include/linux/sched.h
在这里比较值得提到的是:
A、TASK_RUNNING: 正在运行或处于就绪的状态,值得注意的是,一般操作系统的教科书中将正在运行的进程定义为RUNNING状态。
将可以运行但是没有被调度的进行定义为READY状态,而在Linux下被统一定义为TASK_RUNNING状态。
B、TASK_INTERRUPTIBLE: 处于等待队列中,等待资源到达而被唤醒。TASK_INTERRUPTIBLE标志的状态还支持中断唤醒。
通常情况下,进程列表中大部分的进行都是处于TASK_INTERRUPTIBLE状态。
C、TASK_UNINTERRUPTIBLE: 处于等待队列中,等待资源达到而被唤醒。但是它不支持被中断唤醒。
D、TASK_STOPPED((TASK_WAKEKILL | __TASK_STOPPED)): 进程内外部程序暂停,(例如收到SIGSTOP信号,进程会进入TASK_STOPPED状态)。
当再次允许时继续执行(进程收到SIGCONT信号,进入TASK_RUNNING状态)。
E、EXIT_ZOMBIE: 僵尸(僵死)状态,用户空间的进程资源被释放,但是内核中的进程PCB并未释放,等待父进程回收。
set_task_state(tsk,state_value):
形参state_value的取值与set_current_state接口形参的取值相同。
(2)将进程挂载在一个等待队列中(需要定义一个等待队列)。
add_wait_queue(wait_queue_head_t * q,wait_queue_t * wait)
(3)进程的调度(让出CPU资源)。
schedule(void)
以上的这些操作,实际上可以使用一个函数接口实现即可:
wait_event_interruptible(wq,condition)
这涉及到等待队列的应用。
二、等待队列
在进程软件开发中,任务经常由于某种资源/条件没有满足而不能进行运行状态,需要获取到资源/满足条件之后才能继续运行,进入运行状态。解决这种问题就需要等待队列(wait
queue)支持。
在Linux驱动程序设计中,我们可以使用等待队列(wait
queue)来实现阻塞进程的唤醒,用于实现Linux内核的异步事件通知处理机制的支持,也可以用于同步对系统资源的访问(实际上信号量在Linux内核中就是依赖于等待队列来实现的)。
1、Linux内核等待队列的原型
在Linux内核中,等待队列时以内核链表(双向循环链表)作为基础数据结构实现的,通过算法与进程的调度器结合,从而实现既能提供异步事件通知处理机制,又能提供同步访问系统资源的机制。
等待队列又两个数据结构组成,分别为等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。
其定义原型如下(路径:linux-3.0.8/include/linux/wait.h):
值得说明的是结构体struct
__wait_queue,该结构是对一个等待任务的抽象,每个等待任务都会被抽象成一个wait_queue,并且挂载到__wait_queue_head上。而结构struct
__wait_queue_head在Linux内核中被用来表示一个等待队列的头。
2、等待队列的创建
(1)定义等待队列头
wait_queue_head_t my_wait_queue;
(2)初始化等待队列头
init_waitqueue_head(&my_wait_queue);
Linux内核通过init_waitqueue_head()宏函数接口来初始化一个队列头,其原型如下:
在Linux内核中还有另一种方式直接一步到位的定义并初始化一个等待队列头的接口,其为DECLARE_WAIT_QUEUE_HEAD(),原型如下:
如上图所示,在DECLARE_WAIT_QUEUE_HEAD()宏接口的实现中,内部直接自定义了一个wait_queue_head_t类型的变量,所以用户只需将变量名通过宏参数传递进去即可。使用如下:
DECLARE_WAIT_QUEUE_HEAD(my-wait_queue);
(3)定义等待队列
在Linux内核中,提供宏接口函数DECLARE_WAITQUEUE(name, tsk)定义并初始化一个等待队列。其原型如下:
3、等待队列的操作
(1)添加等待队列
在Linux内核中,通过add_wait_queue()接口函数来添加一个等待队列到现有的等待队列中,其原型如下:
其作用是将等待队列wait添加到等待队列头q指向的等待队列链表中。
(2)删除等待队列
在Linux内核中,通过remove_wait_queue()接口函数来删除一个等待队列,其原型如下:
其作用是将等待队列wait从等待队列头q指向的等待队列链表中删除。
(3)事件的等待
在Linux内核中,等待队列为“事件等待”提供了4个接口宏函数来实现,分别为:
wait_event(queue, condition)wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
参数:
queue:表示等待队列头中要被唤醒的队列。condition:表示阻塞的条件,必须满足这个条件,否则阻塞。
timeout:表示超时机制,当没有满足condition的条件,并且所设置的时间timeout到达,那么返回,不阻塞。
wait_event()和wait_event_interruptible()的区别在于,wait_event_interruptible()可以被信号打断,而wait_event()不能。
wait_event_timeout()和wait_event_interruptible_timeout()是分别在wait_event()和wait_event_interruptible()的基础上实现超时返回机制。
(4)唤醒队列
在Linux中,提供了两个红函数接口来唤醒对应的队列,其接口分别为:
wake_up(wait_queue_head_t *queue);wake_up_interruptible(wait_queue_head_t *queue);
参数queue为一个队列头,所以这些接口唤醒的是这个队列头上挂载的队列。
wake_up()接口应该与wait_event()和wait_event_timeout()接口成对使用。
wake_up_interruptible()接口与wait_event_interruptible()和wait_event_interruptible_timeout()接口成对使用。
wake_up()可唤醒处于TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 的进程,
而 wake_up_interruptible()只能唤醒处于 TASK_INTERRUPTIBLE 的进程。
(5)在等待队列上睡眠
在Linux内核中,提供了两个函数接口实现进程在等待队列上睡眠,分别如下:
sleep_on(wait_queue_head_t *q );interruptible_sleep_on(wait_queue_head_t *q );
sleep_on()函数的作用就是将目前进程的状态置成 TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头
q,直到资源可获得, q 引导的等待队列被唤醒。
interruptible_sleep_on()与 sleep_on()函数类似,其作用是将目前进程的状态置成
TASK_INTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头 q,直到资源可获得, q引导的等待队列被唤醒或者进程收到信号。
sleep_on()函数应该与 wake_up()成对使用, interruptible_sleep_on()应该与
wake_up_interruptible()成对使用。
更多Linux教程请查看菜单