结构等待队列[网络编程]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)分析的很好,大家共同学习

文章结束给大家分享下程序员的一些笑话语录: IBM和波音777
  波音777是有史以来第一架完全在电脑虚拟现实中设计制造的飞机,所用的设备完全由IBM公司所提供。试飞前,波音公司的总裁非常热情的邀请IBM的技术主管去参加试飞,可那位主管却说道:“啊,非常荣幸,可惜那天是我妻子的生日,So..”..
  波音公司的总载一听就生气了:“胆小鬼,我还没告诉你试飞的日期呢!”

posted @ 2013-04-21 09:44  xinyuyuanm  阅读(271)  评论(0编辑  收藏  举报