Select函数源码剖析

I/O复用函数——select

select是最基础的IO复用函数,对于其实现,做了一定的了解,记录如下:

  1. 源码一进来就可以发现,它的事件是通过宏来实现的
    #define FDS_IN(fds, n) (fds->in + n) //读事件
    #define FDS_OUT(fds, n) (fds->out + n) //写事件
    #define FDS_EX(fds, n) (fds->ex + n) //异常事件
    #define BITS(fds, n) (FDS_IN(fds, n)|FDS_OUT(fds, n)|*FDS_EX(fds, n))//通过一个位图可以同时监听三种事件

    • 这就是位图本来的样子

      typedef struct {
      unsigned long *in, *out, *ex;
      unsigned long *res_in, *res_out, *res_ex;
      } fd_set_bits;
  2. 这是select的函数主体

    int do_select(int n, fd_set_bits *fds, long *timeout);
  3. 首先找出最大的文件描述符

    spin_lock(&current->files->file_lock);
       retval = max_select_fd(n, fds);
       spin_unlock(&current->files->file_lock);
       
       if (retval < 0)
              return retval; //如果最大值还是小于0,报错
       n = retval; //将n设置成它最大值加1
    • 这里可以看一下max_select_fd()函数,目的是找出最大的fd的值+1

      static int max_select_fd(unsigned long n, fd_set_bits *fds)
            {
                  unsigned long *open_fds;
                unsigned long set;
                int max;
                
                /* handle last in-complete long-word first */
                set = ~(~0UL << (n & (__NFDBITS-1))); //将传入的n最高位往后取1(究竟有多少要检查的fd)
                n /= __NFDBITS; //类似于哈希函数一样,对n取一个下标位置
                 open_fds = current->files->open_fds->fds_bits+n; //打开fd
                 max = 0; 
                 if (set) {
                    set &= BITS(fds, n); //寻找一下n在fds位图中的位置
                    if (set) { //如果在容器中已经存在
                        if (!(set & ~*open_fds))  //检查一下文件是不是已经打开?
                            goto get_max; //打开都OK的话那么找到最大值开始遍历吧
                        return -EBADF;
                    }
                   }
                   //开始遍历
                   while (n) {
                    open_fds--;
                    n--;
                    set = BITS(fds, n); //检查在n处有没有注册的fd
                    if (!set) //如果没有,抬走下一位
                        continue;
                    if (set & ~*open_fds) //如果有,但是文件没有打开的话报错
                        return -EBADF;
                    if (max) //
                        continue;
                 get_max:
                       do {
                           max++;
                           set >>= 1;
                       } while (set); //max出来是最高fd的位数加1
                       max += n * __NFDBITS; //max出来是最大fd的值加一
                   }
               
                   return max;
                 }
  4. 进行初始化变量

    poll_initwait(&table);
       wait = &table.pt;
       if (!__timeout) //如果没有超时的话,wait置空
           wait = NULL;
       retval = 0;
  5. 核心部分

    for (;;) {
            unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
    
            set_current_state(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;
                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;
                    continue;
                 }
    
                for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
                    if (i >= n) //到了最大描述符+1的位置,循环结束
                        break;
                    if (!(bit & all_bits)) //如果当前位置没有注册,进入下一次
                        continue;
                    file = fget(i); //拿取当前的文件描述符
                    if (file) { //检测三种事件
                        f_op = file->f_op;
                        mask = DEFAULT_POLLMASK;
                        if (f_op && f_op->poll)
                            mask = (*f_op->poll)(file, retval ? NULL : wait);
                        fput(file);
                        if ((mask & POLLIN_SET) && (in & bit)) {
                            res_in |= bit;
                            retval++;
                        }
                        if ((mask & POLLOUT_SET) && (out & bit)) {
                            res_out |= bit;
                            retval++;
                        }
                        if ((mask & POLLEX_SET) && (ex & bit)) {
                            res_ex |= bit;
                            retval++;
                        }
                    }
                    cond_resched();
                }
                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))
                break;
            //检查是否出错,如果出错,就跳出循环
            if(table.error) {
                retval = table.error;
                break;
            }
            __timeout = schedule_timeout(__timeout);
            
            //继续当前进程
            __set_current_state(TASK_RUNNING);
            //释放位图
            poll_freewait(&table);
            //更新超时时间
            *timeout = __timeout;
            return retval;
        }

源码剖析就到这了,流程总结一下:

  1. selectIO复用函数,首先需要定义位图(struct fd_set),如果有超时事件还需要超时结构(struct timeval)。
  2. 首先必须对容器进行清空!——(FD_ZERO(fds)),利用FD_SET(fd, fds)来添加好描述符,做好准备工作
  3. 调用int select(int maxfdp1,fd_set readset,fd_set writeset,fd_set exceptset,const struct timeval timeout); 返回值会返回就绪描述符的数目,超时返回0,出错返回-1。
  4. 检测具体是否有数据流动FD_ISSET(fd,&fds);

相关推荐