转载请注明出处
在linux驱动开发中,我们要讨论一个重要的问题,如果驱动程序无法及时满足进程的要求时,驱动程序应如何处理呢?例如,当数据不可以用时,用户可能调用read;或者进程试图写入数据,但因为输出缓冲区已经满了,设备还未准备接受数据。驱动程序该怎么处理这些情况呢?我们的驱动程序应该阻塞该进程,将此置入休眠状态,直到请求可以继续。
(一)休眠(sleep)的介绍
“休眠(sleep)”对于进程而言,将进程标识为一种特殊的状态,并将其从调度器的运行队列中移走;直到某些情况下修改这个特殊状态,进程才回在任意CPU上调度,也即运行该进程。
在linux驱动开发中,通过等待队列存放休眠的进程,当休眠进程被唤醒后,从等待队列中提取出休眠进程。一个等待队列通过一个等待队列头(wait queue head)来管理。实际上,等待队列就是进程链表,其中包含了等待某个特定事件的所有进程。
(1) 初始化等待队列头:
静态初始化:DECLARE_WAIT_QUEUE_HEAD(name);
动态初始化:wait_queue_head_t my_queue;
Init_waitqueue_head(&my_queue);
(2) 几种简单休眠的宏:
wait_event(queue, condition);
wait_event_interruptible(queue, condition);
wait_event_timeout(queue, conditon, timeout);
wait_event_interuptible_timeout(queue, conditon, timeout);
上面所有形式中,queue是等待队列头,condition是休眠前后都要对其condition进行求值进行判断,如果条件满足,就立即退出休眠;interruptible形式的休眠是中断休眠;timeout是设置一种等待限定的时间,如果timeout达到时,立即唤醒进程。
(3) 唤醒休眠的函数:
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
还有其它几种休眠唤醒函数,上面的唤醒函数已经够用了,其它几种形式可以google一下。
(二)休眠过程的具体分析
首先列出wait_event(queue, condition)宏的代码,然后根据代码进行分析休眠过程。
#define wai_event(queue,condition)\
do{ \
if(condition) /*条件满足,立即返回*/ \ (1)
break; \
_wait_event(queue,condition);/*添加到等待队列*/ \
} while(0)\
#define _wait_event(queue, conditon) \
do{\
DEFINE_WAIT(wait); /*建立并初始化一个等待队列*/ \(2)
for(;;) \
{\
prepare_to_wait(&queue, &wait, TASK_UINTERUPTIBLE);\(3)
if(condition) /*条件满足,立即返回*/ \ (4)
break; \
schedule();/*放弃CPU*/ \ (5)
}\
finish_wait(&queue, &wait); \ (6)
} while(0)\
void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state)
{
unsiged long flags;
wait->flags &=~WQ_FLAG_EXCLUSIVE;(7)
spin_lock_irqsave(&queue->lock, flags);(8)
if(list_empty(&wait->task_list)) (9)
_add_wait_queue(queue, wiat); /*加入等待队列*/(10)
set_current_state(state); /*设置进程状态*/(11)
spin_unlock_irqrestore(&queue->lock, flags);
}
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait) (12)
{
unsigned long flags;
_set_current_state(TASK_RUNNING);/*恢复进程状态TASK_RUNNING */
if(!list_empty_careful(&wait->task_list))
{
spin_lock_irqsave(&queue->lock, flags);
list_del_init(&wait->task_list);
spin_unlock_irqrestore(&queue->lock, flags);
}
}
休眠的具体操作过程
1. 首先要判断休眠条件是否满足,不满足立即返回,如标识(1)所示,在代码中你会看到多次判断休眠条件是否满足,因此这个判断条件中不能带来任何副作用。
2. 初始化一个等待队列wait_queue_t,并将其加入对应的等待队列中。这样不管谁负责唤醒他,都能找到正确的进程。在代码如(2)(3)。对应代码(3),就是调用prepare_to_wait函数,该函数不仅实现等待队列的添加而且实现了进程状态的设置,可以参考代码注释。prepare_to_wait函数中还有一个比较特殊的宏“WQ_FLAG_EXCLUSIVE”,设置标识位,主要防止爆发式唤醒和强夺资源,通过设置WQ_FLAG_EXCLUSIVE标识后,实现“独占等待”。而代码(7)没有设置了该宏的反向,也即没有实现“独占等待”。其中“独占等待”通过下面两个步骤实现的:(a)等待队列入口设置了“WQ_FLAG_EXCLUSIVE”后,将其加入等待队列的尾部,没有设置该标识的,加入头部。(b)唤醒wake_up时,唤醒第一设置WQ_FLAG_EXCLUSIVE的标识的进程,停止唤醒其它进程。
3. 让出CPU,如代码(5),该进程进入休眠状态。
4. 在代码(5)返回之后,完成一些清理工作,此时进程已经不在需要休眠,因此进程状态重新设置TASK_RUNNING.将进程从等待队列中移除。
(三)自己写休眠函数实现休眠操作
通过(二)的详细描述休眠过程,可以自己写休眠函数实现休眠操作。设置一个device,然后对device进行写操作,在此写操作中穿插休眠过程,可以自己尝试写该写操作的过程,并实现休眠过程。下面是我的写法,有不对之处,请指正(写操作的过程就省略了,主要实现休眠和唤醒过程)。
static unsigned int device_write(struct file*file, const char *buffer, size_t count, loff_t *pos)
{
……
……
DECLARE_WAITQUEUE(wait, current);/*定义等待队列*/
add_wait_queue(&device_wait, &wait);/*添加等待队列*/
while(!device_writeable(…))/*设备不可写*/
{
__set_current_state(TASK_INTERRUPTIBLE);/改变进程状态/
if(file->f_flags&O_NOBBLOCK)/*非阻塞I/O*/
{
remove_wait_queue(&device,&wait);/移除等待队列入口/
set_current_state(TASK_RUNNING);/*恢复进程状态*/
return –EAGAIN;
}
schedule();/放弃cpu/
if(signale_pending(current))
{
remove_wait_queue(&device,&wait);/移除等待队列入口/
set_current_state(TASK_RUNNING);/*恢复进程状态*/
}
}
device_write(**);/进行设备写操作/
return count;
}
转载请注明出处