作者:武汉华嵌嵌入式培训 长沙分中心 讲师:周龙
1.poll和select
使用非阻塞I/O 的应用程序常常使用poll, select, 和epoll 系统调用. poll, select 和epoll 本质上有相同的功能: 每个都允许一个进程来决定它是否可读或者可写一个或多个文件而不阻塞。 这些调用也可阻塞进程直到任何一个给定集合的文件描述符可用来读或写。因此, 它们常常用在必须使用多输入输出流的应用程序, 而不必粘连在它们任何一个上。相同的功能常常由多个函数提供, 因为2 个是由不同的团队在几乎相同时间完成的:select 在BSD Unix 中引入, 而poll 是System V 的解决方案。epoll 调用添加在2.5.45, 作为使查询函数扩展到几千个文件描述符的方法。
1.1 select
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
nfds is the highest-numbered file descriptor in any of the three sets, plus 1;
fd_set表示文件描述符集合的一个结构体,操作这个结构体,有一些特定的函数:
void FD_CLR(int fd, fd_set *set); //把文件描述符fd, 从集合里清掉。
int FD_ISSET(int fd, fd_set *set); //判断一个文件描述符fd, 是否在集合set里
void FD_SET(int fd, fd_set *set); //把文件描述符fd,加到集合set中去
void FD_ZERO(fd_set *set); //把文件描述符集合set清空。
Select 返回值:
On success, select() eturn the number of file descriptors contained in the three returned descriptor sets (that is, the total number of bits that are set in readfds, writefds, exceptfds) which may be zero if the timeout expires before anything interesting happens. On error, -1 is returned, and errno is set appropriately; the sets and timeout become undefined, so do not rely on their contents after an error。
用法实例:参见代码。
1.2 poll
poll的作用与select类似,it waits for one of a set of file descriptors to become ready to perform I/O。原理如下:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
每个要监听的文件描述符对应一个struct pollfd结构,参数fds指向所有要监听的文件描述符对应结构体struct pollfd的数组;参数nfds 表示struct pollfd结构的数目(亦即是要监听的文件描述符的个数);timeout是超时时间,单位为milliseconds(指定一个负数表示一直阻塞)。
Struct pollfd 结构体:
Struct pollfd {
int fd ; /* file descriptor */
short events; /* requested events */
short revents; /* returned events*/
};
要监听的事件(events)和返回的事件(revents),是由一些位掩码组成的,定义在<linux/poll.h>中:
POLLIN
如果设备可被不阻塞地读, 这个位必须设置.
POLLRDNORM
这个位必须设置, 如果"正常"数据可用来读. 一个可读的设备返回( POLLIN|POLLRDNORM ).
POLLRDBAND
这个位指示带外数据可用来从设备中读取. 当前只用在Linux 内核的一个地方( DECnet 代码)并且通常对设备驱动不可用.
POLLPRI
高优先级数据(带外)可不阻塞地读取. 这个位使select 报告在文件上遇到一个异常情况, 因为selct 报告带外数据作为一个异常情况.
POLLHUP
当读这个设备的进程见到文件尾, 驱动必须设置POLLUP(hang-up). 一个调用select 的进程被告知设备是可读的, 如同selcet 功能所规定的.
POLLERR
一个错误情况已在设备上发生. 当调用poll, 设备被报告位可读可写, 因为读写都返回一个错误码而不阻塞.
POLLOUT
这个位在返回值中设置, 如果设备可被写入而不阻塞.
POLLWRNORM
这个位和POLLOUT 有相同的含义, 并且有时它确实是相同的数. 一个可写的设备返回( POLLOUT|POLLWRNORM).
POLLWRBAND
如同POLLRDBAND , 这个位意思是带有零优先级的数据可写入设备. 只有poll 的数据报实现使用这个位, 因为一个数据报看传送带外数据.
应当重复一下POLLRDBAND 和POLLWRBAND 仅仅对关联到socket 的文件描述符有意义: 通常设备驱动不使用这些标志.
poll 返回值:
On success, a positive number is returned; this is the number of structures which have nonzero revents fields (in other words, those descriptors with events or errors reported)。A value of 0 indicates that the call timed out and no file descriptors were ready. On error, -1 is returned, and errno is set appropriately.
用法实例:参见代码。
2. 驱动实现
其实select(或pselect)、poll(或epoll,ppoll)都是通过poll系统调用进入内核的。支持任何一个这些调用都需要来自设备驱动的支持。这个支持(对所有3 个调用)由驱动的poll 方法调用。这个方法由下列的原型:
unsigned int (*poll) (struct file *filp, poll_table *wait);
无论何时用户空间程序进行一个poll, select, 或者epoll 系统调用,涉及一个和驱动相关的文件描述符时,这个驱动方法被调用。
在驱动中,poll方法的实现,只需要两步:
⑴在一个或多个可指示查询状态变化的等待队列上调用poll_wait. 如果没有文件描述符可用作I/O, 内核使这个进程在等待队列上等待所有的传递给系统调用的文件描述符。
poll_wait的原型是:
void poll_wait (struct file *, wait_queue_head_t *, poll_table *);
在调用这个函数时,我们只需要关注第二个参数,另外两个参数和poll方法相同。第二个参数就是我们驱动里定义的等待队列指针。
⑵ 根据条件返回一个事件位掩码。(见上文)
用法实现:参见代码。
注意:
驱动里调用poll_wait是不会在阻塞的,真正的“轮询”是通过虚拟文件系统层的sys_poll来实现的。
拓展(内核poll机制大致流程, 可以阅读内核相关代码fs/select.c):
⑴poll > sys_poll > do_sys_poll > poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。
⑵接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数。它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;它还判断一下设备是否就绪。
⑶如果设备未就绪,do_sys_poll里会让进程休眠一定时间
⑷进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。
⑸如果驱动程序没有去唤醒进程,那么schedule_timeout(__timeou)超时后,会重复⑵、⑶动作,直到应用程序的poll调用传入的时间到达。
更多博文请到华嵌官网:http://www.embedhq.org