1. IO多路复用的概念
I/O多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
2. select接口
#include <sys/select.h>int select( int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
readfds,读流集合writefds,写流集合exceptfds,异常流集合nfds,上面三种事件的最大的文件描述符+1timeout 可等待的时间
struct timeval{long tv_sec;//secondlong tv_usec;//minisecond}
timeout有三种取值:
NULL,select一直阻塞,知道readfds、writefds、exceptfds集合中至少一个文件描述符可用才唤醒0,select不阻塞timeout_value,select在timeout_value这个时间段内阻塞
返回值
0:超时-1:出错大于0:可用的文件描述符数量
另外,还有一组与fd_set 有关的操作
FD_SET(fd, _fdset),把fd加入_fdset集合中FD_CLR(fd, _fdset),把fd从_fdset集合中清除FD_ISSET(fd, _fdset),判定fd是否在_fdset集合中FD_ZERO(_fdset),清除_fdset有描述符
3. select实现原理
select的实现依赖于设备的驱动函数poll,poll的功能是检查设备的哪条条流可用(一个设备一般有三条流,读流,写流,设备发生异常的异常流),如果其中一条流可用,返回一个mask(表示可用的流),如果不可用,把当前进程加入设备的流等待队列中,例如读等待队列、写等待队列,并返回资源不可用。
select正是利用了poll的这个功能,首先让程序员告知自己关心哪些io流(用文件描述符表示,也就是上文的readfds、writefds和exceptfds),并让程序员告知自己这些流的范围(也就是上文的nfds参数)以及程序的容忍度(timeout参数),然后select会把她们拷贝到内核,在内核中逐个调用流所对应的设备的驱动poll函数,当范围内的所有流也就是描述符都遍历完之后,他会检查是否至少有一个流发生了,如果有,就修改那三个流集合,把她们清空,然后把发生的流加入到相应的集合中,并且select返回。如果没有,就要取决于timeout,如果timeout等于0,说明不阻塞,让出cpu,直接返回;如果timeout是NULL,则要一直阻塞,直到某个设备的某条流可用,就去唤醒阻塞在流上的进程,这个时候,调用select的进程重新开始遍历范围内的所有描述符;如果timeout不是上面两个值,就是在这个值的时间段阻塞,如果依然没有某个流可用,那么就返回。
select实现原理图:
4. select的代码实现
int do_select(int n, fd_set_bits *fds, s64 *timeout){retval = 0; //retval用于保存已经准备好的描述符数,初始为0for (;;) {unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;long __timeout;set_current_state(TASK_INTERRUPTIBLE); //将当前进程状态改为TASK_INTERRUPTIBLE,可中断inp = fds->in; outp = fds->out; exp = fds->ex;rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;for (i = 0; i < n; ++rinp, ++routp, ++rexp) { //遍历每个描述符unsigned long in, out, ex, all_bits, bit = 1, mask, j;unsigned long res_in = 0, res_out = 0, res_ex = 0;const struct file_operations *f_op = NULL;struct file *file = NULL;in = *inp++; out = *outp++; ex = *exp++;all_bits = in | out | ex;if (all_bits == 0) {i += __NFDBITS; //all_bits的类型是unsigned long int ,大小为4个字节32位,all_bits=0,说明连续32个描述符(流)不在readdfs、writedfs、execptdfs集合中,所以i+=32,而__NFDBITS=32。continue;}for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {//遍历每个长字里的每个位int fput_needed;if (i >= n)break;if (!(bit & all_bits))continue;file = fget_light(i, &fput_needed);if (file) {f_op = file->f_op;MARK(fs_select, "%d %lld", i, (long long)*timeout);mask = DEFAULT_POLLMASK;if (f_op && f_op->poll)mask = (*f_op->poll)(file, retval ? NULL : wait);//调用设备的驱动poll函数fput_light(file, fput_needed);if ((mask & POLLIN_SET) && (in & bit)) {res_in |= bit; //如果是这个描述符可读, 将这个位置位retval++; //返回描述符个数加1}if ((mask & POLLOUT_SET) && (out & bit)) {res_out |= bit;retval++;}if ((mask & POLLEX_SET) && (ex & bit)) {res_ex |= bit;retval++;}}}if (res_in)*rinp = res_in;if (res_out)*routp = res_out;if (res_ex)*rexp = res_ex;}wait = NULL;if (retval || !*timeout || signal_pending(current))//如果retval!=0,也就是有readdfs、writedfs、execptdfs至少有一个发生,跳出循环break;/*以下处理timeout参数*/__timeout = schedule_timeout(__timeout);if (*timeout >= 0)*timeout += __timeout;}__set_current_state(TASK_RUNNING);return retval;}