select源码剖析

来源:互联网 发布:java实现国际化 编辑:程序博客网 时间:2024/06/09 20:08

linux 2.6.11
select的底层是通过调用sys_select实现的,它将用户的数据拷贝到内核态,select中一个描述符对应一个位,通过给每个事件分配一个bitmap,来对用户的读写,异常事件进行监听,然后由do_select函数去完成链表的建立,回调函数的设置,最后将就绪事件返回给了sys_select,由sys_select把发生就绪事件的描述符返回给用户;

主要用到的数据结构:fd_set_bits,poll_wqueues,
poll_table,poll_table_page,poll_table_entry;

这里写图片描述

接下来我们进入到sys_select这个函数:(节选部分重要代码)

fd_set_bits fds;char *bits;long timeout;int ret, size, max_fdset;size = FDS_BYTES(n);//判断需要分配多少个longbits = select_bits_alloc(size);//分配6*size个字节;//给fds的成员变量赋值fds.in      = (unsigned long *)  bits;fds.out     = (unsigned long *) (bits +   size);fds.ex      = (unsigned long *) (bits + 2*size);fds.res_in  = (unsigned long *) (bits + 3*size);fds.res_out = (unsigned long *) (bits + 4*size);fds.res_ex  = (unsigned long *) (bits + 5*size);//将用户传进来的发生可读可写或者异常的描述符拷贝到内核空间;if ((ret = get_fd_set(n, inp, fds.in)) ||    (ret = get_fd_set(n, outp, fds.out)) ||    (ret = get_fd_set(n, exp, fds.ex)))       goto out;//对返回字段清零zero_fd_set(n, fds.res_in);zero_fd_set(n, fds.res_out);zero_fd_set(n, fds.res_ex);ret = do_select(n, &fds, &timeout);//将发生事件的描述符拷贝给用户传进来的参数,返回给用户if (set_fd_set(n, inp, fds.res_in) ||    set_fd_set(n, outp, fds.res_out) ||    set_fd_set(n, exp, fds.res_ex))        ret = -EFAULT;select_bits_free(bits, size);//释放为每一个事件分配的bitmapreturn ret;

这里写图片描述
然后进入sys_select的核心函数do_select:

struct poll_wqueues table;poll_table *wait;int retval, i;retval = max_select_fd(n, fds);//根据已经设置好的fd位图检查用户打开的fd, 要求对应fd必须打开, 并且返回最大的fdpoll_initwait(&table);//初始化回调函数,在fop->poll时被调用for (;;) {    set_current_state(TASK_INTERRUPTIBLE);//设置当前进程为阻塞状态,直到某一个信号将其唤醒如果当前进程不阻塞了,说明是有就绪事件产生了,或者时间超时了,或者发生异常事件了;    for (i = 0; i < n; ++rinp, ++routp, ++rexp) {        //检测每一个描述符对应的是否有可读、可写、异常事件发生,如果有,执行下面的for循环        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) {            mask = (*f_op->poll)(file, retval ? NULL : wait);//在poll成功后会将本进程唤醒执行,在poll函数里面调用回调函数__pollwait        /*retval为返回给用户空间的描述符的总数,由源码可以看出这是in,out,ex集合的总和;*/        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++;        }     }}poll_freewait(&table);//释放Poll_table_entry节点,poll_table_page节点;return retval;}   

重点说一下f_op->poll操作的作用:
1、查看文件操作状态,有完成或者异常发生,在对应的fdset中标记这些文件描述符;
2、如果retval == 0,并且没有超时,那么通知这些文件,让他们在文件操作完成时唤醒本进程;
3、在f_op->poll中调用了回调函数__pollwait,它的主要作用是将进程放入等待队列中;
这里写图片描述

接下来让我们看看__pollwait回调函数:

struct poll_wqueues *p = container_of(_p, struct poll_wqueues, pt);struct poll_table_page *table = p->table;//获取poll_wqueues中的tablestruct poll_table_page *new_table;//定义一个局部的new_table;new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);//为new_table分配一个页大小的空间new_table->entry = new_table->entries;以头插的方式将new_table插入poll_wqueues中的table单链表中;new_table->next = table;p->table = new_table;table = new_table;/* 添加一个新的entry */{    struct poll_table_entry * entry = table->entry;    table->entry = entry+1;//直接加到柔性数组所在地址;     get_file(filp);     entry->filp = filp;    entry->wait_address = wait_address;    init_waitqueue_entry(&entry->wait, current);    add_wait_queue(wait_address,&entry->wait);}

这里写图片描述

总结:通过以上源码剖析,我们就能够看出select需要遍历所有的文件描述符,一位一位进行判断,就时间复杂度来说为O(N),在检测用户传进来的文件描述符的时候,我们还需要建立poll_wqueues表,来保存所有等待队列的节点信息,还需要注册回调函数,还记录所有等待队列节点;当把检测到的文件描述符所发生的事件返回给用户的时候,这些表的结构就得释放,等到下一次还需要监听描述符的时候,还需要把每一个描述符及其所对应的事件一起重新注册到内核,内核需要再重新建立表结构,这样的话,就增大了内核的负担;所以说select所监听的描述符是有限制的,一般是1024个文件描述符。select主要适用于连接少,活动连接多的情况。

总结就告一段落了。。。