Linux 设备驱动中的阻塞与非阻塞 I/O

概念1:阻塞与非阻塞

  阻塞是指在执行设备操作时,若不能获得资源则挂起进程,同时将CPU 礼让给其他进程使用,被挂起的进程进入休眠态,被从调度器的运行队列移走,直到条件被满足,它又将被调度器调度进来,再次判断能否获得资源。

  而非阻塞在获取不到资源时并不挂起,它会不停的查询,直到它的时间片用完(放弃,等待下一次调度)为止,这样反而占用CPU 

 

概念2:进程的休眠

  休眠(被阻塞)进程被标志为一个特殊的不可执行状态,并从调度器的运行队列中移走。

  进程休眠有各种原因,但肯定都是为了等待一些事件。事件可能是一段时间、从文件I/O读更多数据,或者是某个硬件事件。

  休眠有两种相关的进程状态:TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE。它们的惟一区别是处于TASK_UNINTERRUPTIBLE 状态的进程不会被信号打断,而处于TASK_INTERRUPTIBLE 状态的进程可以被信号打断,从而唤醒并处理信号(然后再次进入等待睡眠状态)。

 

概念3:等待队列

  休眠进程可以使用等待队列(wait queue)来唤醒。

  等待队列是由等待某些事件发生的进程组成的简单链表。内核用 wake_queue_head_t 来代表等待队列。

  等待队列可以通过DECLARE_WAITQUEUE_HEAD() 静态创建,也可以用 init_waitqueue_head() 动态创建。

 

概念4:在等待队列上休眠

  在Linux 设备驱动中,可以通过sleep_on() 和 interruptible_sleep_on() 来使进程在等待队列上休眠。

  代码清单:sleep_on () 函数

 

 1 void __sched sleep_on(wait_queue_head_t *q)
 2 {
 3      sleep_on_common(q,TASK_UNINTERRUPTIBLE,MAX_SCHEDULE_TIMEOUT);
 4 }
 5 
 6 static long __sched sleep_on_common(wait_queue_head_t *q,int state,long timeout)
 7 {
 8     unsigned long flags;
 9     wait_queue_t wait;
10     
11     init_waitqueue_entry(&wait,current);
12 
13     __set_current_state(state);
14 
15     spin_lock_irqsave(&q->lock,flags);
16     __add_wait_queue(q,&wait);  /* 加入等待队列 */
17     spin_unlock(&q->lock);
18     timeout = schedule_timeout(timeout);  /* 进程切换 */
19     spin_lock_irq(&q->lock);
20     __remove_wait_queue(q,&wait);  /* 移出等待队列 */
21     spin_unlock_irqrestore(&q->lock,flags);
22 
23     return timeout;
24 }

  代码清单2:interruptible_sleep_on() 函数

1 void __sched interruptible_sleep_on(wait_queue_head_t *q)
2 {
3     sleep_on_common(q,TASK_INTERRUPTIBLE,MAX_SCHEDULE_TIMEOUT);
4 }

  由两个代码清单可以看出,不论是sleep_on() 函数还是interruptible_sleep_on() 函数,都会调用sleep_on_common() 函数,其流程如下:

(1)定义并初始化一个等待队列,将进程状态切换为TASK_UNINTERRUPTIBLE 和 TASK_INTERRUPTIBLE ,并将等待队列添加至等待队列头;

(2)通过schedule_timeout() 放弃CPU ,调度其他进程执行;

(3)进程被其他地方唤醒,将等待队列移出等待队列头。

 

概念5:进程状态切换

  从上面的代码清单中,我们已经知道进程状态切换的函数是:set_current_state() 。

  值得注意的是:在内核中通常使用set_current_state() 或 __add_current_state() 函数来实现目前进程状态的改变,还有一种方法就是采用:current->state = TASK_UNINTERRUPTIBLE 类似的赋值语句。

  一般说来,set_current_state() 在任何环境下都可以使用,不会存在并发问题,但是效率要低于__add_current_state() 。 

  进程切换发生在schedule()函数中。

  代码清单3: 在驱动程序中改变进程状态并调用schedule() 

 1 static ssize_t xxx_write(strcut file *file,const char *buffer,size_t count,loff_t *ppos) 
2 {
3
... 4 DECLARE_WAITQUEUE(wait,current); /* 定义等待队列 */ 5 add_wait_queue(&xxx_wait,&wait); /* 添加等待队列 */ 6 7 ret = count; /* 等待设备缓冲区可写 */ 8 do { 9 avail = device_writable(...); 10 if (avail < 0) 11 __set_current_state(TASK_INTERRUPTIBLE); /*改变进程状态 */ 12 if (avail < 0){ 13 if(file->f_flags &O_NONBLOCK) {/* 非阻塞 */ 14 if (!ret) 15 ret = - EAGAIN; 16 goto out; 17 } 18 schedule(); /* 调度其他进程执行 */ 19 if (signal_pending(current)) {/* 如果是因为信号唤醒 */ 20 if (!ret) 21 ret = - ERESTARTSYS; 22 goto out; 23 } 24 } 25 }while (avail < 0); 26 27 /* 写设备缓冲区 */ 28 device_write(...); 29 out: 30 remove_wait_queue(&xxx_wait, &wait); /* 将等待队列移出等待队列头 */ 31 set_current_state(TASK_RUNNING); /* 设置进程状态为TASK_RUNNING */ 32 return ret; 33 }

 

 

posted @ 2013-10-04 15:55  Jan5  阅读(571)  评论(0编辑  收藏  举报