1 int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
使用非阻塞 I/O 的应用程序通常会使用 select() 和 poll() 系统调用查询是否可对设备进行无阻塞的访问。这两个系统调用最终会引发设备驱动中的 poll() 函数被执行。
- numfds:此值是需要检查的号码最高的文件描述符加 1
- readfds:被 select 监视的读文件描述符集合
- writefds:被 select 监视的写文件描述符集合
- exceptfds:被 select 监视的异常处理的文件描述符集合
- timeout:指向 struct timeval 类型的指针,可使 select 在等待 timeout 时间后若没有文件描述符准本好则返回
1 struct timeval { 2 int tv_sec; /* 秒 */ 3 int tv_usec; /* 微秒 */ 4 };
下列操作来设置、清除、判断文件描述符集合
1 FD_ZERO(fd_set *set); /* 清除一个文件描述符集合 */ 2 FD_SET(int fd, fd_set *set); /* 将一个文件描述符加入文件描述符集合中 */ 3 FD_CLR(int fd, fd_set *set); /* 将一个文件描述符从文件描述符集合中清除 */ 4 FD_ISSET(int fd, fd_set *set); /* 判断文件描述符是否被置位 */
1 #include ... 2 3 #define FIFO_CLEAR 0x01 4 #define BUFFER_LEN 20 5 6 main() 7 { 8 int fd, num; 9 char rd_ch(BUFFER_LEN); 10 fd_set rfds,wfds; /* 读/写文件描述符集 */ 11 12 /* 以非阻塞方式打开 /dev/xxx 设备文件 */ 13 fd = open("/dev/xxx", O_RDONLY | O_NONBLOCK); 14 if(fd != -1) 15 { 16 /* FIFO 清0 */ 17 if(ioctl(fd, FIFO_CLEAR, 0) < 0) 18 { 19 prinf("ioctl command failed "); 20 } 21 22 while(1) 23 { 24 FD_ZERO(&rfds); 25 FD_ZERO(&wfds); 26 FD_SET(fd, &rfds); 27 FD_SET(fd, &wfds); 28 29 select(fd + 1, &rfds, &wfds, NULL, NULL); 30 /* 数据可获得 */ 31 if(FD_ISSET(fd, &rfds)) 32 prinf("Poll monitor: can be read "); 33 34 /* 数据可写入 */ 35 if(FD_ISSET(fd, &wfds)) 36 prinf("Poll monitor: can be write "); 37 } 38 } 39 else 40 { 41 prinf("Device open failure "); 42 } 43 }
1.1 多路复用 select()
第一次对 n 个文件进行 select 的时候,若任何一个文件满足要求,select 就直接返回;第二次再进行 select 的时候,没有文件满足读写要求,select 进程阻塞且睡眠。
由于调用 select 的时候,每个驱动的 poll() 接口都会被调用到,实际上执行 select 的进程被挂到了每个驱动的等待队列上,可以被任何一个驱动唤醒,如果 FDn 变得可读写,select 返回
当多路复用的文件数量庞大,IO流量频繁的时候,一般不太适用使用 select 和 poll ,此情况下,这两个函数表现的性能较差,应使用 epoll。
epoll 的最大好处是不会随着 fd 的数目增长而降低效率。
1 /* poll 函数原型 */ 2 int poll(struct pollfd *fds, nfds_t nfds, int timeout);
1 /* 创建一个 epoll 句柄,size 告诉内核要监听多少个 fd */ 2 /* 注:当创建 epoll 句柄后,它本身也会占用一个 fd,所以在使用完 epoll 后,必须调用 close() 关闭 */ 3 int epoll_creat(int size);
1 /* 告诉内核要监听什么类型的事件 2 * 第一个参数是 epoll_creat 的返回值 3 * 第二个参数表示动作,第三个参数是需要监听的 fd,第四个参数是告诉内核需要监听的事件类型 4 */ 5 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
第二个参数的动作包含:
struct epoll_event 结构体为:
- event 可以是以下几个宏的或:
- EPOLLIN:表示对应的文件描述符可读
- EPOLLOUT:表示对应的文件描述符可写
- EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里表示的是有 socket 带外数据到来)
- EPOLLERR:表示对应的文件描述符发生错误
- EPOLLHUP:表示对应的文件描述符被挂断
- EPOLLET:将 epoll 设为边缘触发模式,
- EPOLLONESHOT:意味着一次性监听,当监听完这次事件之后,如果还需要继续监听这个 fd 的话,需要再次把这个 fd 加入到 epoll 队列中
1 /* 等待事件的产生,用来从内核得到事件的集合,maxevents 告诉内核本次最多收多少事件,maxevent<= 创建 epoll_creat() 时的size 2 * timeout 为超时时间(以毫秒为单位,0意味着立即返回,-1意味着永久等待) 3 * 返回值是需要处理的事件数目,若返回0,表示已超时 4 */ 5 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);