在应用程序调用read,write时,若驱动程序无法立即满足要求,该如何响应?驱动程序应该(默认)阻塞该进程,将其置入休眠状态直到请求可继续。
1.休眠
进程被置为休眠,意味着它被标识为处于一个特殊的状态并且从调度器的运行队列中移走。这个进程将不被在任何 CPU 上调度,即将不会运行。 直到发生某些事情改变了那个状态。安全地进入休眠的两条规则:
(1)永远不要在原子上下文中进入休眠,即当驱动在持有一个自旋锁、seqlock或者 RCU 锁时不能睡眠;关闭中断也不能睡眠。持有一个信号量时休眠是合法的,但你应当仔细查看代码:如果代码在持有一个信号量时睡眠,任何其他的等待这个信号量的线程也会休眠。因此发生在持有信号量时的休眠必须短暂,而且决不能阻塞那个将最终唤醒你的进程。
(2)当进程被唤醒,它并不知道休眠了多长时间以及休眠时发生什么;也不知道是否另有进程也在休眠等待同一事件,且那个进程可能在它之前醒来并获取了所等待的资源。所以不能对唤醒后的系统状态做任何的假设,并必须重新检查等待条件来确保正确的响应。
(3)除非确信其他进程会在其他地方唤醒休眠的进程,否则也不能睡眠。使进程可被找到意味着:需要维护一个称为等待队列的数据结构。它是一个进程链表,其中饱含了等待某个特定事件的所有进程。在 Linux 中, 一个等待队列由一个wait_queue_head_t 结构体来管理,其定义在中。wait_queue_head_t 类型的数据结构非常简单:
- struct __wait_queue_head {
-
spinlock_t lock;
-
struct list_head task_list;
-
};
- typedef struct __wait_queue_head wait_queue_head_t;
它包含一个自旋锁和一个链表。这个链表是一个等待队列入口,它被声明做 wait_queue_t。wait_queue_head_t包含关于睡眠进程的信息和它想怎样被唤醒。
定义等待队列头
- //静态方法
-
DECLARE_WAIT_QUEUE_HEAD(name);
-
//动态方法
- wait_queue_head_t my_queue;
-
init_waitqueue_head(&my_queue);
定义等待队列
- DECLARE_WAITQUEUE(name, tsk)
添加/移除等待队列
- void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
- void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
将等待队列wait添加到q/从q移除。
在等待队列上睡眠
- sleep_on(wait_queue_head_t * q);
-
interruptible_sleep_on(wait_queue_head_t * q);
- //这两个函数将当前进程无条件休眠在给定的队列上,由于安全因素(sleep_on()不提供竞态的任何保护),不用这两个函数,ldd3建议弃用这两个函数
简单休眠
Linux 内核中最简单的休眠方式是称为 wait_event的宏(及其变种),它实现了休眠和进程等待的条件的检查。形式如下:
- wait_event(queue, condition)/*不可中断休眠,不推荐*/
-
wait_event_interruptible(queue, condition)/*推荐,返回非零值意味着休眠被中断,且驱动应返回 -ERESTARTSYS*/
-
wait_event_timeout(queue, condition, timeout)
-
wait_event_interruptible_timeout(queue, condition, timeout)
-
/*有限的时间的休眠;若超时,则不管条件为何值返回0,*/
-
唤醒休眠进程的函数称为 wake_up,形式如下:
-
void wake_up(wait_queue_head_t *queue);
-
void wake_up_interruptible(wait_queue_head_t *queue);
wake_up唤醒queue中的所有进程,wake_up_interruptible 唤醒queue中的可中断休眠
约定:用 wake_up 唤醒 wait_event ;用 wake_up_interruptible 唤醒wait_event_interruptible。
2.阻塞和非阻塞操作
全功能的 read 和 write 方法涉及到进程可以决定是进行非阻塞 I/O还是阻塞 I/O操作。明确的非阻塞 I/O 由 filp->f_flags 中的 O_NONBLOCK 标志来指示(定义再 被 自动包含)。浏览源码,会发现O_NONBLOCK 的另一个名字:O_NDELAY ,这是为了兼容 System V 代码。O_NONBLOCK 标志缺省地被清除,因为等待数据的进程的正常行为只是睡眠.
其实不一定只有read 和 write 方法有阻塞操作,open也可以有阻塞操作。
一个例子
- while(!have_date)
-
{
-
if (filp->f_flags & O_NONBLOCK)
-
return -EAGAIN;
-
-
wait_event_interruptible(wq,have_date);
-
}//用while,而不用if,是因为中断也有可能唤醒该等待队列
-
wake_up(&wq);
PS:
①默认情况下,读写操作应该是阻塞的
②驱动程序不是一个进程,更不是一个线程,只是一些被APP进程调用的函数,其阻塞的是调用该函数的进程。
3.高级休眠
①定义并初始化一个等待队列,将进程改为TASK_UNINTERRUPTIBLE/TASK_INTERRUPTERIBLE,并将等待队列添加到等待队列头。
- set_current_state(state_value);
②通过schedule()放弃CPU,调度其他进程执行。
③进程被其他地方唤醒,将等待队列移出等待队列头。
- void test(void)
-
{
-
DECLARE_WAITQUEUE(wait_name, current);
-
add_wait_queue(&my_queue, &wait_name);
-
-
set_current_state(TASK_INTERRUPTIBLE);
-
-
schedule();
-
if (signal_pending(current))
-
{
-
ret = -ERESTARTSYS;
-
goto err2;
-
}
-
-
eer2:
-
remove_wait_queue(&my_queue, &wait_name);
-
set_current_state(TASK_RUNNING);
-
-
return 0;
-
}
void test2(void)
{
wake_up(&my_queue);
}
PS:当多个等待队列,信号量出现时,谨防死锁
阅读(907) | 评论(0) | 转发(0) |