select 实现分析

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;

}

 

 

l  select相关的结构体

比较重要的结构体由四个:struct poll_wqueues、struct poll_table_page、struct poll_table_entry、structpoll_table_struct

 

每一个调用select()系统调用的应用进程都会存在一个struct poll_wqueues结构体,用来统一辅佐实现这个进程中所有待监测的fd的轮询工作,后面所有的工作和都这个结构体有关,所以它非常重要。

 

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_wqueues内嵌的poll_table_entry数组inline_entries[] 的大小是有限的,如果空间不够用,后续会动态申请物理内存页以链表的形式挂载poll_wqueues.table上统一管理。接下来的两个结构体就和这项内容密切相关:

 

struct poll_table_page { // 申请的物理页都会将起始地址强制转换成该结构体指针

       struct poll_table_page   *next;      // 指向下一个申请的物理页

       struct poll_table_entry  *entry;     // 指向entries[]中首个待分配(空的) poll_table_entry地址

       struct poll_table_entry  entries[0]; // page页后面剩余的空间都是待分配的poll_table_entry结构体

};

 

 

对每一个fd调用fop->poll() => poll_wait() => __pollwait()都会先从poll_wqueues.inline_entries[]中分配一个poll_table_entry结构体,直到该数组用完才会分配物理页挂在链表指针poll_wqueues.table上然后才会分配一个poll_table_entry结构体poll_get_entry函数)。

poll_table_entry具体用处:函数__pollwait声明如下:

static void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p);

 

该函数调用时需要3个参数,第一个是特定fd对应的file结构体指针,第二个就是特定fd对应的硬件驱动程序中的等待队列头指针,第3个是调用select()的应用进程中poll_wqueues结构体的poll_table该进程监测的所有fd调用fop->poll函数都用这一个poll_table结构体

 

struct poll_table_entry {

       struct file     *filp;                 // 指向特定fd对应的file结构体;

       unsigned long   key;                   // 等待特定fd对应硬件设备的事件掩码,如POLLIN POLLOUTPOLLERR;

       wait_queue_t    wait;                  // 代表调用select()的应用进程,等待在fd对应设备的特定事件 (读或者写)的等待队列头上,的等待队列项;

       wait_queue_head_t   *wait_address;     // 设备驱动程序中特定事件的等待队列头(该fd执行fop->poll,需要等待时在哪等,所以叫等待地址);

};

 

总结几点:

1.  特定的硬件设备驱动程序的事件等待队列头是有限个数的,通常是有读事件和写事件的等待队列头;

2.  而一个调用了select()的应用进程只存在一个poll_wqueues结构体;

3.  该应用程序可以有多个fd在进行同时监测其各自的事件发生,但该应用进程中每一个fd有多少个poll_table_entry存在,那就取决于fd对应的驱动程序中有几个事件等待队列头了,也就是说,通常驱动程序的poll函数中需要对每一个事件的等待队列头调用poll_wait()函数。比如,如果有读写两个等待队列头,那么就在这个应用进程中存在两个poll_table_entry结构体,在这两个事件的等待队列头中分别将两个等待队列项加入;

4.  如果有多个应用进程使用select()方式同时在访问同一个硬件设备,此时硬件驱动程序中加入等待队列头中的等待队列项对每一个应用程序来说都是相同数量的一个事件等待队列头一个,数量取决于事件等待队列头的个数

 

 

do_select函数中,遍历所有nfd,对每一个fd调用对应驱动程序中的poll函数。

驱动程序中poll一般具有如下形式:

static unsigned int XXX_poll(struct file *filp, poll_table *wait)

{

   unsigned int mask = 0;

   struct XXX_dev *dev = filp->private_data;

   ...

   poll_wait(filp, &dev->r_wait, wait);

   poll_wait(filp ,&dev->w_wait, wait);

   if(CAN_READ)//读就绪

   {

      mask |= POLLIN | POLLRDNORM;

   }

   if(CAN_WRITE)//写就绪

   {

      mask |= POLLOUT | POLLRDNORM;

   }

   ...

   return mask;

}

 

以字符设备evdev为例(文件drivers/input/evdev.c)

static unsigned int evdev_poll(struct file *file, poll_table *wait)

{

       struct evdev_client *client = file->private_data;

       struct evdev *evdev = client->evdev;

       poll_wait(file, &evdev->wait, wait);

       return ((client->head == client->tail) ? 0 : (POLLIN | POLLRDNORM)) | (evdev->exist ? 0 : (POLLHUP | POLLERR));

}

 

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

{

       if (p && wait_address)

              p->qproc(filp, wait_address, p);

}

 

其中wait_address是驱动程序需要提供的等待队列头,来容纳后续等待该硬件设备就绪的进程对应的等待队列项。

 

typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

 

 

typedef struct poll_table_struct {

       poll_queue_proc qproc;

       unsigned long key;

} poll_table;

 

fop->poll()函数的poll_table参数是从哪里传进来的?好生阅读过代码就可以发现,do_select()函数中存在一个结构体structpoll_wqueues,其内嵌了一个poll_table的结构体,所以在后面的大循环中依次调用各个fd的fop->poll()传递的poll_table参数都是poll_wqueues.poll_table。

poll_table结构体的定义其实蛮简单,就一个函数指针,一个key值。这个函数指针在整个select过程中一直不变,而key则会根据不同的fd的监测要求而变化。

qproc函数初始化在函数do_select() –> poll_initwait() -> init_poll_funcptr(&pwq->pt, __pollwait)中实现,回调函数就是__pollwait()。

 

int do_select(int n, fd_set_bits *fds, struct timespec *end_time)

{

       struct poll_wqueues table;

       ...

       poll_initwait(&table);

       ...

}

 

 

void poll_initwait(struct poll_wqueues *pwq)

{

       init_poll_funcptr(&pwq->pt, __pollwait);

       ...

}

 

static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)

{

       pt->qproc = qproc;

       pt->key   = ~0UL; /* all events enabled */

}

 

/* Add a new entry */

static void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)

{

struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);

struct poll_table_entry *entry = poll_get_entry(pwq);

if (!entry)

return;

get_file(filp);

entry->filp = filp;      // 保存对应的file结构体

entry->wait_address = wait_address;  // 保存来自设备驱动程序的等待队列头

entry->key = p->key;  // 保存对该fd关心的事件掩码

init_waitqueue_func_entry(&entry->wait, pollwake);// 初始化等待队列项,pollwake是唤醒该等待队列项时候调用的函数

entry->wait.private = pwq; // poll_wqueues作为该等待队列项的私有数据,后面使用

add_wait_queue(wait_address, &entry->wait);// 将该等待队列项添加到从驱动程序中传递过来的等待队列头中去。

}

 

驱动程序在得知设备有IO事件时(通常是该设备上IO事件中断),会调用wakeup,wakeup –> __wake_up_common -> curr->func(即pollwake)。

static int pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)

{

       struct poll_table_entry *entry;

       entry = container_of(wait, struct poll_table_entry, wait);// 取得poll_table_entry结构体指针

       if (key && !((unsigned long)key & entry->key))/*这里的条件判断至关重要,避免应用进程被误唤醒,什么意思?*/

              return 0;

       return __pollwake(wait, mode, sync, key);

}

 

pollwake调用__pollwake,最终调用default_wake_function。

 

static int __pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)

{

struct poll_wqueues *pwq = wait->private;

DECLARE_WAITQUEUE(dummy_wait, pwq->polling_task);

smp_wmb();

pwq->triggered = 1; // select()用户进程只要有被唤醒过,就不可能再次进入睡眠,因为这个标志在睡眠的时候有用

return default_wake_function(&dummy_wait, mode, sync, key); // 默认通用的唤醒函数

}

 

最终唤醒调用select的进程,在do_select函数的schedule_timeout函数之后继续执行(继续for(;;),也即从新检查每一个fd是否有事件发生),此次检查会发现设备的该IO事件,于是select返回用户层。

 

结合这两节的内容,select的实现结构图如下:

 

 

 

参考:

http://blog.youkuaiyun.com/lizhiguo0532/article/details/6568969

http://blog.youkuaiyun.com/lizhiguo0532/article/details/6568968

推荐阅读该博客上的博文。

 


from: http://www.cnblogs.com/apprentice89/archive/2013/05/09/3064975.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值