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 }
- 上一篇 »Linux 驱动开发
- 下一篇 »Netty的体系结构及使用