博客园  :: 首页  :: 联系 :: 管理

select 实现分析 –1 【整理】

Posted on 2013-05-09 18:55  Apprentice89  阅读(1211)  评论(1编辑  收藏  举报

select源码结构图:

 

 sys_select

 

core_sys_select

 

 

 

 

源码分析:

 

#undef __NFDBITS

#define __NFDBITS    (8 * sizeof(unsigned long))

 

#undef __FD_SETSIZE

#define __FD_SETSIZE    1024

 

#undef __FDSET_LONGS

#define __FDSET_LONGS    (__FD_SETSIZE/__NFDBITS)

 

typedef struct {

    unsigned longfds_bits [__FDSET_LONGS];   //1024bit

} __kernel_fd_set;

 //由上可以看到可以支持1024个描述符

 

//系统调用(内核态)

//参数为 maxfd, r_fds, w_fds, e_fds, timeout

asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timeval __user *tvp)

{

    s64 timeout = -1;

    struct timeval tv;

    int ret;

 

    //将超时时间换成jiffies

    if (tvp) {

        if (copy_from_user(&tv, tvp, sizeof(tv))) //将用户态参数拷贝到内核态

            return -EFAULT;

         if (tv.tv_sec < 0 || tv.tv_usec < 0)

            return -EINVAL;

         /* Cast to u64 to make GCC stop complaining */

        if ((u64)tv.tv_sec >= (u64)MAX_INT64_SECONDS)

            timeout = -1;    /* infinite */

        else {

            timeout = ROUND_UP(tv.tv_usec, USEC_PER_SEC/HZ);

            timeout += tv.tv_sec * HZ;

        }

    }

 

    // (***) 调用 core_sys_select

    ret = core_sys_select(n, inp, outp, exp, &timeout);

 

    //将剩余时间拷贝回用户空间进程

    if (tvp) {

        struct timeval rtv;

        if (current->personality & STICKY_TIMEOUTS) //判断当前环境是否支持修改超时时间(不确定)

            goto sticky;

        rtv.tv_usec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ));

        rtv.tv_sec = timeout;

        if (timeval_compare(&rtv, &tv) >= 0)

            rtv = tv;

        if (copy_to_user(tvp, &rtv, sizeof(rtv))) {

sticky:

            /*

             * 如果应用程序将timeval值放在只读存储中,

             * 我们不希望在成功完成select后引发错误(修改timeval

             * 但是,因为没修改timeval,所以我们不能重启这个系统调用。

             */

            if (ret == -ERESTARTNOHAND)

                ret = -EINTR;

        }

    }

    return ret;

}

 

 

//主要的工作在这个函数中完成

staticint core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, s64 *timeout)

{

    fd_set_bits fds;

    /*  fd_set_bits 结构如下:

     typedef struct {

         unsigned long *in, *out, *ex;

         unsigned long *res_in, *res_out, *res_ex;

    } fd_set_bits;

    这个结构体中定义的全是指针,这些指针都是用来指向描述符集合的。

     */

    void *bits;

    int ret, max_fds;

    unsigned int size;

    struct fdtable *fdt;

    /* Allocate small arguments on the stack to save memory and be faster 先尝试使用栈(因为栈省内存且快速)*/

    long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];  // SELECT_STACK_ALLOC=256

 

    ret = -EINVAL;

    if (n < 0)

        goto out_nofds;

 

    /* max_fds can increase, so grab it once to avoid race */

    rcu_read_lock(); //rcu

 

    fdt = files_fdtable(current->files); //读取文件描述符表

    /*  struct fdtable 结构如下:

    struct fdtable {

       unsigned int max_fds;

       struct file **fd;

       ...

    };

     */

    max_fds = fdt->max_fds; //files结构中获取最大值(当前进程能够处理的最大文件数目)

 

    rcu_read_unlock();

    if (n > max_fds)// 如果传入的n大于当前进程最大的文件描述符,给予修正

        n = max_fds;

 

    /* 我们需要使用6倍于最大描述符的描述符个数,

     * 分别是in/out/exception(参见fd_set_bits结构体),

     * 并且每份有一个输入和一个输出(用于结果返回) */

    size = FDS_BYTES(n);// 以一个文件描述符占一bit来计算,传递进来的这些fd_set需要用掉多少个字

    bits = stack_fds;

    if (size > sizeof(stack_fds) / 6) { // 除以6,因为每个文件描述符需要6bitmaps上的位。

        //栈不能满足,先前的尝试失败,只能使用kmalloc方式

        /* Not enough space in on-stack array; must use kmalloc */

        ret = -ENOMEM;

        bits = kmalloc(6 * size, GFP_KERNEL);

        if (!bits)

            goto out_nofds;

    }

 

    //设置fds

    fds.in      = bits;

    fds.out     = bits +   size;

    fds.ex      = bits + 2*size;

    fds.res_in  = bits + 3*size;

    fds.res_out = bits + 4*size;

    fds.res_ex  = bits + 5*size;

 

    // get_fd_set仅仅调用copy_from_user从用户空间拷贝了fd_set

    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;

 

    // 对这些存放返回状态的字段清0

    zero_fd_set(n, fds.res_in);

    zero_fd_set(n, fds.res_out);

    zero_fd_set(n, fds.res_ex);

 

    //执行do_select,完成监控功能

    ret = do_select(n, &fds, timeout);

 

    if (ret < 0) //有错误

        goto out;

    if (!ret) {// 超时返回,无设备就绪

        ret = -ERESTARTNOHAND;

        if (signal_pending(current))

            goto out;

        ret = 0;

    }

 

    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;

 

out:

    if (bits != stack_fds)

        kfree(bits);

out_nofds:

    return ret;

}

 

 

 

 

#define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR)

#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)

#define POLLEX_SET (POLLPRI)

 

int do_select(int n, fd_set_bits *fds, s64 *timeout)

{

    struct poll_wqueues table;

    /*

     struct poll_wqueues {

          poll_table pt;

          struct poll_table_page *table;

          struct task_struct *polling_task; //保存当前调用select的用户进程struct task_struct结构体

          int triggered;         // 当前用户进程被唤醒后置成1,以免该进程接着进睡眠

          int error;             // 错误码

          int inline_index;      // 数组inline_entries的引用下标

          struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];

    };

     */

    poll_table *wait;

    int retval, i;

 

    rcu_read_lock();

    //根据已经设置好的fd位图检查用户打开的fd, 要求对应fd必须打开, 并且返回最大的fd

retval = max_select_fd(n, fds);

    rcu_read_unlock();

 

    if (retval < 0)

        return retval;

    n = retval;

 

    /* 一些重要的初始化:

       poll_wqueues.poll_table.qproc函数指针初始化,

       该函数是驱动程序中poll函数(fop->poll)实现中必须要调用的poll_wait()中使用的函数。  */

    poll_initwait(&table);

    wait = &table.pt;

    if (!*timeout)

        wait = NULL;        // 用户设置了超时时间为0

    retval = 0;

 

    for (;;) {

        unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;

        long __timeout;

        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;

 

        // 所有nfd的循环

        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;

             // 先取出当前循环周期中的32(设long32位)个文件描述符对应的bitmaps

            in = *inp++; out = *outp++; ex = *exp++;

            all_bits = in | out | ex;// 组合一下,有的fd可能只监测读,或者写,或者err,或者同时都监测

            if (all_bits == 0) {

                i += __NFDBITS; //如果这个字没有待查找的描述符, 跳过这个长字(32位,__NFDBITS=32),取下一个32fd的循环中

                continue;

            }

 

            // 本次32fd的循环中有需要监测的状态存在

            for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {

                int fput_needed;

                if (i >= n)

                    break;

                if (!(bit & all_bits)) // bit每次循环后左移一位的作用在这里,用来跳过没有状态监测的fd

                    continue;

 

                file = fget_light(i, &fput_needed);//得到file结构指针,并增加引用计数字段f_count

 

                if (file) {// 如果file存在(这个文件描述符对应的文件确实打开了)

                    f_op = file->f_op;

                    mask = DEFAULT_POLLMASK;

                    if (f_op && f_op->poll) //这个文件对应的驱动程序提供了poll函数(fop->poll)。

                        mask = (*f_op->poll)(file, retval ? NULL : wait);//调用驱动程序中的poll函数。

                    /*  调用驱动程序中的poll函数,以evdev驱动中的evdev_poll()为例

                     *  该函数会调用函数poll_wait(file, &evdev->wait, wait)

                     *  继续调用__pollwait()回调来分配一个poll_table_entry结构体,

                     *  该结构体有一个内嵌的等待队列项,

                     *  设置好wake时调用的回调函数后将其添加到驱动程序中的等待队列头中。  */

 

                    fput_light(file, fput_needed);  // 释放file结构指针,实际就是减小他的一个引用计数字段f_count

 

                    //记录结果。poll函数返回的mask是设备的状态掩码。

                    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++;

                    }

                }

                /*

                 *  cond_resched()将判断是否有进程需要抢占当前进程,

                 *  如果是将立即发生调度,这只是为了增加强占点。

                 *  (给其他紧急进程一个机会去执行,增加了实时性)

                 *  在支持抢占式调度的内核中(定义了CONFIG_PREEMPT),

                 *  cond_resched是空操作。

                 */

                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)) // signal_pending(current)检查当前进程是否有信号要处理

            break;

        if(table.error) {

            retval = table.error;

            break;

        }

 

        if (*timeout < 0) {

            /* Wait indefinitely 无限期等待*/

            __timeout = MAX_SCHEDULE_TIMEOUT;

        } elseif (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT - 1)) {

            /* Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in a loop */

            __timeout = MAX_SCHEDULE_TIMEOUT - 1;

            *timeout -= __timeout;

        } else {

            __timeout = *timeout;

            *timeout = 0;

        }

         /* schedule_timeout 用来让出CPU

          * 在指定的时间用完以后或者其它事件到达并唤醒进程(比如接收了一个信号量)时,

          * 该进程才可以继续运行  */

        __timeout = schedule_timeout(__timeout);

        if (*timeout >= 0)

            *timeout += __timeout;

    }

    __set_current_state(TASK_RUNNING);

 

    poll_freewait(&table);

 

    return retval;

}

 

 

 

 

参考链接: http://blog.csdn.net/lizhiguo0532/article/details/6568964