• 结构等待队列[网络编程]select流程分析


    上班之余抽点间时出来写写博文,希望对新接触的朋友有帮助。今天在这里和大家一起学习一下结构等待队列

        

    select的调用径路sys_select()->core_sys_select()-> do_select()

        

    最主要的作工就是在 do_select中成完,面前为数参的断判及准备作工

        


        int 

        do_select

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

        

    {
         ktime_t expire, *to = NULL;
         struct poll_wqueues table;                      
         poll_table *wait;
         int retval, i, timed_out = 0;
         unsigned long slack = 0;

         rcu_read_lock();
          retval = max_select_fd(n, fds);     
         rcu_read_unlock();

         if (retval < 0)
              return retval;
         n = retval;

          poll_initwait(&table);             对   poll_wqueues结构停止一些初始化

        

          wait = &table.pt;                  得取poll_table结构
         if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
              wait = NULL;
              timed_out = 1;
         }

         if (end_time && !timed_out)
              slack = estimate_accuracy(end_time);

         retval = 0;
         for (;;) {
              unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;

              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;
                   const 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;                     如果这个字没有待查找的描述符, 跳过这个长字(32位)
                        continue;
                   }

                   for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {           历遍每一个长字里的每一位个
                        int fput_needed;
                        if (i >= n)
                             break;
                        if (!(bit & all_bits))
                             continue;
                        file = fget_light(i, &fput_needed);               失掉指位定对应的fd的file结构
                        if (file) {
                             f_op = file->f_op;
                             mask = DEFAULT_POLLMASK;
                             if (f_op && f_op->poll) {
                                  wait_key_set(wait, in, out, bit);
                                  mask = (*f_op->poll)(file, wait);  在这里循环调用所监测的fd_set内的有所件文描述符对应的poll数函

        

                                   还记得socket创立那一文中说到:

        

                                      init_file(file, sock_mnt, dentry, FMODE_READ | FMODE_WRITE,  & socket_file_ops );           

        

                                     对file结构成员赋值,并将file->f_op 更新为 socket_file_ops socket类型件文的作操数函集    

        

    那poll数函为: .poll = sock_poll,

        

                                      sock_poll()见上面分析
                             }
                             fput_light(file, fput_needed); 
                              if ((mask & POLLIN_SET) && (in & bit)) {             
                                  res_in |= bit;        如果是这个描述符可读, 将这位个置位
                                  retval++;             返回描述符个数加1
                                  wait = NULL;
                             }
                             if ((mask & POLLOUT_SET) && (out & bit)) {   
                                  res_out |= bit;         如果是这个描述符有异常错误, 将这位个置位
                                  retval++;                返回描述符个数加1
                                  wait = NULL;
                             }
                             if ((mask & POLLEX_SET) && (ex & bit)) {   
                                  res_ex |= bit;                如果是这个描述符有异常错误, 将这位个置位
                                  retval++;                      返回描述符个数加1
                                  wait = NULL;

                             }
                        }
                   }
                   if (res_in)
                        *rinp = res_in;
                   if (res_out)
                        *routp = res_out;
                   if (res_ex)
                        *rexp = res_ex;
                   cond_resched();
              }

        


        

         到这里历遍结束。retval保存了检测到的可作操的件文描述符的个数。如果有件文可作操,则跳出for(;;)循环,直接返回。

        

        若没有件文可作操且timeout间时未到同时没有收到signal,则执行                

        

         schedule_timeout就寝。就寝间时是非由__timeout定决,直一等到该进程被唤醒

        


              wait = NULL;
              if (retval || timed_out || signal_pending(current))
                   break;                                                                    跳出循环 返回结果
              if (table.error) {
                   retval = table.error;
                   break;
              }

              if (end_time && !to) {
                   expire = timespec_to_ktime(*end_time);
                   to = &expire;
              }

              if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,            就寝,可由信号唤醒
                                to, slack))
                   timed_out = 1;
         }

         poll_freewait(&table);

         return retval;
    }

        


        主要结构体

        每日一道理
    古人云:“海纳百川,有容乃大。”人世间,不可能没有矛盾和争吵,我们要以磊落的胸怀和宽容的微笑去对面它 。哈伯德也曾说过:“宽恕和受宽恕的难以言喻的快乐,是连神明会都为之羡慕的极大乐事。”让我们从宽容中享受快乐,从谅解中体会幸福吧!

        

    typedef struct {
         unsigned long *in, *out, *ex;
         unsigned long *res_in, *res_out, *res_ex;
    fd_set_bits;           这个结构体保存了select在户用态的数参
    在select()中,个一每件文描述符用一位个表现,其中1表现这个件文是被视监的。
    in,out,ex指向的bit数组表现对应的读,写,异常件文的描述符,
    res_in,res_out,res_ex表现对应的读,写,异常件文的描述符的检测结果。

    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];
    };
    个一每调用select()系统调用的应用进程会都存在一个 struct poll_weueues结构体,
    用来一统协助实现这个进程中有所待监测的fd的轮询作工,面后有所的作工和都这个结构体有关,所以它非常主要


    struct  poll_table_page {
         struct poll_table_page * next;
         struct poll_table_entry * entry;
         struct poll_table_entry entries[0];
    };
    这个表记录了select进程中有所待等列队的节点。
    由于select要视监多个fd,并且要把前当进程放入这些fd的待等列队中去,因此要分配待等列队的节点。
    这些节点可能如此之多,以至于不可能像平日做的那样,在堆栈中分配它们。
    所以,select以动态分配的式方把它保存在poll_table_page中。
    保存的式方是单向链表,每一个节点以页为位单,分配多个poll_table_entry项。

    struct  poll_table_entry {
         struct file *filp;
         unsigned long key;
         wait_queue_t wait;                               内嵌了一个待等列队
         wait_queue_head_t *wait_address;
    };
    filp是select要视监的struct file结构体,wait_address是件文作操的待等列队的队首,wait是待等列队的节点。


    void poll_initwait(struct poll_wqueues *pwq)
    {
          init_poll_funcptr(&pwq->pt,  __pollwait);           保存回调数函
         pwq->polling_task = current;                          置设polling_task为前当进程
         pwq->triggered = 0;
         pwq->error = 0;
         pwq->table = NULL;
         pwq->inline_index = 0;
    }
    static inline void  init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
    {
         pt->qproc = qproc;
         pt->key   = ~0UL; /* all events enabled */
    }

    static unsigned int  sock_poll(struct file *file, poll_table *wait)
    {
         struct socket *sock;
          sock = file->private_data;         失掉socket结构,存于file的私有数据区中          这里就从fd转化为对对应socket结构的作操了
         return  sock->ops->poll(file, sock, wait);
                      在面前socket的创立一文中分析过
                          sock->ops = answer->ops;        以TCP为例 即为   .ops =        &inet_stream_ops,       .poll = tcp_poll,
                                                                         以UDP为例 即为   .ops =        & inet_dgram_ops ,       .poll = udp_poll,
           对应的poll数函就是去看查对应的sock结构中     
                struct sk_buff_head     sk_receive_queue;
                struct sk_buff_head     sk_write_queue;  这些列队是不是可读,可写,以及其他一些状态的断判,体具的不进入分析了 
          我们只大概看下udp_poll
    }

    unsigned int  datagram_poll(struct file *file, struct socket *sock,
                      poll_table *wait)
    {
         struct sock *sk = sock->sk;
         unsigned int mask;

          sock_poll_wait(file, sk->sk_sleep, wait);                     将wait加入到sock的sk_sleep待等列队头中
         mask = 0;
                               上对面各种可读,可写,异常错误等状态断判,返回mask
         /* exceptional events? */
         if (sk->sk_err || !skb_queue_empty(&sk->sk_error_queue))
              mask |= POLLERR;
         if (sk->sk_shutdown & RCV_SHUTDOWN)
              mask |= POLLRDHUP;
         if (sk->sk_shutdown == SHUTDOWN_MASK)
              mask |= POLLHUP;

         /* readable? */
         if (!skb_queue_empty(&sk->sk_receive_queue) ||
             (sk->sk_shutdown & RCV_SHUTDOWN))
              mask |= POLLIN | POLLRDNORM;

         /* Connection-based need to check for termination and startup */
         if (connection_based(sk)) {
              if (sk->sk_state == TCP_CLOSE)
                   mask |= POLLHUP;
              /* connection hasn't started yet? */
              if (sk->sk_state == TCP_SYN_SENT)
                   return mask;
         }

         /* writable? */
         if (sock_writeable(sk))
              mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
         else
              set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);

         return mask;
    }

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

    {
         if (p && wait_address) {
               poll_wait(filp, wait_address, p);
              smp_mb();
         }
    }

    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);  这里qproc即为  init_poll_funcptr(&pwq->pt,  __pollwait);           保存回调数函
    }

    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);         从poll_table结构失掉poll_wqueues 结构
         struct poll_table_entry * entry = poll_get_entry(pwq);       得获一个poll_table_entry 
         if (!entry)
              return;
         get_file(filp);
         entry->filp = filp;                                        保存file结构变量
         entry->wait_address = wait_address;            这里wait_address为sk->sk_sleep结构
         entry->key = p->key;
          init_waitqueue_func_entry(&entry->wait, pollwake);       初始化待等列队项,pollwake是唤醒该待等列队项时候调用的数函
         entry->wait.private = pwq;                                             将poll_wqueues作为该待等列队项的私有数据,面后应用
         add_wait_queue(wait_address, &entry->wait);              
    将该待等列队项添加到从驱动程序中递传来过的待等列队头中去 为 sk->sk_sleep结构
    }
    该数函首先通过container_of宏来失掉结构体poll_wqueues的址地,然后调用poll_get_entry()数函来得获一个poll_table_entry结构体,这个结构体是用来连接驱动和应用进程的键关结构体,其实联系很简单,这个结构体中内嵌了一个待等列队项wait_queue_t,和一个待等列队头 wait_queue_head_t,它就是驱动程序中义定的待等列队头,应用进程就是在这里保存了个一每件硬设备驱动程序中的待等列队头( 当然个一每fd都有一个poll_table_entry结构体)。

    我们看下就寝
    poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, to, slack)
    int  poll_schedule_timeout(struct poll_wqueues *pwq, int state,
                     ktime_t *expires, unsigned long slack)
    {
         int rc = -EINTR;

         set_current_state(state);
         if (!pwq->triggered)            这个triggered在什么时候被置1的呢?只要有一个fd对应的设备将前当应用进程唤醒后将会把它置设成1
              rc = schedule_hrtimeout_range(expires, slack, HRTIMER_MODE_ABS);
         __set_current_state(TASK_RUNNING);
         set_mb(pwq->triggered, 0);
         return rc;
    }

    看下唤醒进程:
    面前分析了select会循环历遍它所监测的fd_set内的有所件文描述符对应的驱动程序的poll数函。
    驱动程序供给的poll数函首先会将调用select的户用进程插入到该设备驱动对应源资的待等列队(如读/写待等列队),
    然后返回一个bitmask告知select前当源资哪些可用。

    上面poll数函中已将wait 即前当进程插入到了待等列队中。

    唤醒该进程的进程平日是在所监测件文的设备驱动内实现的,驱动程序维护了针对自身源资读写的待等列队。
    当设备驱动现发自身源资变成可读写并且有进程就寝在该源资的待等列队上时,就会唤醒这个源资待等列队上的进程。
    在这里,比如UDP,有udp数据包来了后,挂载到了对应sock的收接列队上时,会看查是不是有进程正在就寝待等,如果有的话就调用注册的
    唤醒数函停止唤醒,我们这里注册的唤醒数函就是上面提到的 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);    从wait失掉poll_table_entry 结构
         if (key && !((unsigned long)key & entry->key))             断判key,检查是不是有错误唤醒
              return 0;
         return  __pollwake(wait, mode, sync, key);
    }


    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;

         return  default_wake_function(&dummy_wait, mode, sync, key);   将待等进程从待等列队上摘下,加入运行进程列队等一系列复杂作操,达到唤醒目的
    }
    到这里明白了select进程被唤醒的进程。
    由于该进程是阻塞在有所监测的件文对应的设备待等列队上的,因此在timeout间时内,只要任意个设备变成可作操,
    会都立即唤醒该进程,从而继续往下执行。这就实现了select的当有一个件文描述符可作操时就立即唤醒执行的基本原理。

    这篇文章对select(poll)分析的很好,大家共同学习

    文章结束给大家分享下程序员的一些笑话语录: 看到有人回帖“不顶不是中国人”,他的本意是想让帖子沉了。

  • 相关阅读:
    GO-GRPC实践(二) 增加拦截器,实现自定义context(带request_id)、recover以及请求日志打印
    第六章-堆
    第五章-本地方法接口和本地方法栈
    第四章-虚拟机栈
    第三章-运行时数据区及程序计数器
    04-再谈类的加载器
    03-类的加载过程(类的生命周期)详解
    1.编程入门
    SpringBoot 整合 SpringSecurity 梳理
    pip版本过低无法升级问题
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3033482.html
Copyright © 2020-2023  润新知