阻塞和非阻塞I/O是设备访问的两种模式,阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作得条件后在进行操作,被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。设备驱动中阻塞I/O一般基于等待队列或基于等待队列的内核API实现。
非阻塞操作的进程在不能进行设备操作时,并不会被挂起,它要么放弃,要么不停的查询直到可以进行操作为止。
非阻塞I/O的应用程序可以借助轮询函数来查询设备是否能被立即访问,在用户程序中,通常使用select(),poll(),epoll()接口查询是否可以对设备进行无阻塞的访问。select(),poll(),epoll()系统调用最终会使设备驱动中的poll()函数被执行。epoll()可以理解为扩展的poll(),当多路复用的文件数量庞大,I/O流量频繁的时候,不太适用select()和poll(),这种情况下,select()和poll()的性能表现较差。这是建议使用epoll(),epoll最大的好处是不会随着fd的数目增长而降低效率。注意设备驱动中的poll()函数本身不会阻塞,与select(),poll(),epoll()相关的系统调用会阻塞的等待至少一个文件描述符集合可被访问或超时。
驱动程序中的poll实现: __poll_t (*poll) (struct file *, struct poll_table_struct *); ① 对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头部添加到poll_table中,poll_wait()函数只是把当前进程添加到poll_table中,实际作用是让等待队列可以唤醒因select()而睡眠的进程。 ② 返回设备资源的可获取状态(POLLIN,POLLOUT,POLLPRI等)。 在调用wake_up_interruptible(&target->event_wait);接口即可唤醒对应进程。
应用程序调用poll的时候,经过系统调用层unistd.h,在linux内核中通过宏定义调用(fs/select.c) SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,int, timeout_msecs) -> do_sys_poll->do_poll->do_pollfd->vfs_poll do_sys_poll中主要是调用poll_initwait,它初始化一个poll_wqueues变量table,其会定义一个_pollwait回调,该回调会在poll_wait()时被真正的调用。 do_poll中主要调用do_pollfd,及在判断do_pollfd至少有一个成功,或有错误产生或有信号等待处理,应用程序调用poll后,如果do_pollfd和判断条件等都没有满足,就会调用poll_schedule_timeout()进程进入休眠,除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒(驱动程序poll operation中调用poll_wait()的原因)。 do_pollfd主要就是调用file_operations中定义的poll接口,以及处理上报事件mask。
应用程序中调用select和epoll调式: Epoll:
select
|