原创 阻塞与非阻塞I/O

2010-9-6 10:21 3886 5 5 分类: MCU/ 嵌入式

阻塞与非阻塞IO


阻塞操作是指在执行设备操作时若不能获得资源,则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待条件被满足。而非阻塞操作的进程不能进行设备时并不挂起,它或者放弃,或者不停地查询,直到可以进行操作为止。


阻塞的进程会进入睡眠状态,必须有一个地方能够唤醒休眠的进程。唤醒进程的地方最大可能发生在中断里面,因为硬件资源获得的同时往往伴随一个中断。


 


等待队列


可以使用等待队列(wait queue)来唤醒阻塞进程。wait queue很早就作为一个基本的功能单位出现在linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步通知机制。等待队列可以用来同步对系统资源的访问,信号量在内核中也依赖等待队列来实现。


1.定义等待队列头


 wait_queue_head my_queue;


2. 初始化等待队列头


init_waitqueue_head(&my_queue);


   DELCARE_WAIT_QUEUE_HEAD(name)/定义并初始化


3. 定义等待队列


DECLARE_WAITQUEUE(name,tsk)


4. 添加/移除等待队列


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);


5. 等待事件


wait_event(queue,condition);


wait_event_interruptible(queue,condition);


wait_event_timeout(queue,timeout);


wait_event_interruptible_timeout(queue,condition,timeout);


 


wait_event()代码:


  #define
wait_event(wq,condition)


do {


          if
(condition)


                  break;


          __wait_event(wq,condition);//添加到等待队列


} while (0)


 


#define __wait_event(wq,condition)


do {


          DEFINE_WAIT(__wait);


         


          for
(;;) {


                  prepare_to_wait(&wq,&__wait,TASK_UNINTERRUPTIBLE);


                  if
(condition)


                          break;


                  schedule();//放弃cpu


          }


          finsh_wait(&wq,&__wait);


} while(0)


 


void fastcall
prepare_to_wait(wait_queue_head_t *q,wait_queue_t *wait,int state)


{


          unsigned long flags;


 


          wait->flags
&= ~WQ_FLAG_EXCLUSIVE;


          sping_lock_irqsave(&q->lock,flags);


          if
(list_empty(&wait->task_list))


                  __add_wait_queue(q,wait);//添加等待队列


          if
(is_sync_wait(wait))


                        set_current_state(state);//改变当前进程的状态为休眠


                spin_unlock_irqrestore(&q->lock,flags);


}


 


void
fastcall finsh_wait(wait_queue_head_t *q,wait_queue_t *wait)


{


        unsigned long flags;


 


        __set_current_state(TASK_RUNNING);//恢复当前进程的状态为TASK_RUNING


        if
(!list_empty_careful(&wait->task_list)) {


                spin_lock_irqsave(&q->lock,flags);


                list_del_init(&wait->task_list);


                spin_unlock_irqrestore(&q->lock,flags);


}


 


6. 唤醒队列


void wake_up(wait_queue_head_t *queue);


void wake_up_interruptible(wait_queue_head_t  *queue);


wake_up()唤醒wait_event()或wait_event_timeout(),wake_up_interruptible()或wake_up_interruptible_timeout().wake_up()可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程,而wake_up_interruptibel()只能唤醒处于TASK_INTERRUPTIBLE的进程。


7.在等待队列上睡眠


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()类似,其作用是将目前进程的状态置成TASK_INTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得的,q引导的等待队列被唤醒或者收到信号。


sleep_on()与wake_up()成对使用,interruptible_sleep_on()与wake_up_interruptible()成对使用。


sleep_on():


void fastcall __sched
sleep_on(wait_queue_head_t *q)


{


          SLEEP_ON_VAR


          /*


          #define
SLEEP_ONVAR


          unsigned
long flags;


          wait_queue_t
wait;


          init_waitqueue_entry(&wait,current);*/


          current->state
= TASK_UNINTERRUPTIBLE


 


SLEEP_ON_HEAD


/*


#define SLEEP_ONHEAD


spin_lock_irqsave(&q->lock,flags);


__add_wait_queue(q,&wait);


sping_unlock(&q->lock,flags);*/


 


schedule();


SLEEP_ON_TALL


/*


#define SLEEP_ON_TALL


spin_lock_irq(&q->lock);


__remove_wait_queue(q,&wait);*/


spin_unlock_irqrestore(&q->lock,flags);*/


}


 


interruptible_sleep_on():


void fastcall __sched
interruptible_sleep_on(wait_queue_head_t *q)


{


          SLEEP_ON_VAR


 


          current->state
= TASK_INTERRUPTIBLE;


 


          SLEEP_ON_HEAD


          schedule();


          SLEEP_ON_TALL


}


 


note:在内核中使用set_current_state()或__add_current_queue()函数来实现目前进程状态的改变,直接采用current->state = TASK_INTERRUPTIBLE类似的赋值语句也是可行。通常而言,set_current_state()在任何环境下都可以使用,不会存在并发问题,但是效率要低于__add_current_queue().因此,许多设备驱动并不调用sleep_on()或interruptible_sleep_on(),而是直接进程状态改变和切换。


 


轮询操作


轮询


使用非阻塞I/O的应用程序通常会使用select()和poll()系统调用查询设备是否可对设备进行无阻塞的访问。selecct()和poll()系统调用最终会引发设备驱动中的poll()函数被执行。


应用程序中的轮询编程


应用程序最广泛用到select()系统调用。


int select(int numfds,fd_set *reafds, fd_set
*writefds, fd_set *exceptfds, struct timeval *timeout);


struct timeval


{


          int
tv_sec;//second


          int
tv_usec;//1/1000 second


}


 


FD_ZERO(fd_set *set);//清零set


FD_SET(int fd, fd_set *set);//加fd到set


FD_CLR(int fd, fd_set *set);//清除fd从set


FD_ISSET(int fd, fd_set *set);//判断fd是否被置位


 


设备驱动中的轮询编程


设备驱动中poll()函数:


unsigned int (*poll)(struct file *filp,
struct poll_table *watit);


struct poll_table 轮询表。


此函数,对可能引起设备文件状态变化的等待队列调用poo_wait()函数,将对应的等待队列头添加到poll_table; 返回表示是是否能对设备进行无阻塞读写访问的掩码。


poll_wait()注册等待队列:


void poll_wait(struct file *filp,
wait_queue_head_t *queue, poll_table *wait);


只是把当前进程加入到wait等待队列表中。


驱动程序poll()返回设备资源可获取状态,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏位“或”的结果。


 


阻塞与非阻塞访问是IO访问的两种I/O操作暂时不可进行时会让进程睡眠。


在设备驱动中阻塞IO一般基于等待队列,等待队列可用于同步驱动中事件发生的先后顺序。使用非阻塞IO的应用程序也可借助轮询函数来查询设备是否能立即被访问,用户空间调用select()和poll()接口,设备驱动提供poll()函数,设备驱动的poll()本身不会阻塞,但是poll()和select系统调用则会阻塞地等待文件描述集合中的至少一个可访问或超时。

文章评论0条评论)

登录后参与讨论
我要评论
0
5
关闭 站长推荐上一条 /2 下一条