• select 实现分析 –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

  • 相关阅读:
    python学习笔记 day44 mysql练习题(三)
    python学习笔记 day44 mysql 练习题(二)
    python学习笔记 day44 mysql练习题(一)
    python学习笔记 day44 数据库三范式
    python学习笔记 day44 表与表之间的关系
    西北大学集训队选拔赛(重现赛)
    #Leetcode# 196. Delete Duplicate Emails
    #Leetcode# 596. Classes More Than 5 Students
    #Leetcode# 176. Second Highest Salary
    #Leetcode# 620. Not Boring Movies
  • 原文地址:https://www.cnblogs.com/apprentice89/p/3064975.html
Copyright © 2020-2023  润新知