题记:写这篇博客要主是加深自己对结构等待队列的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢。

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)分析的很好,大家共同学习
http://blog.csdn.net/lizhiguo0532/article/details/6568964 

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

转载于:https://www.cnblogs.com/xinyuyuanm/archive/2013/04/21/3033478.html

结构等待队列[网络编程]select流程分析相关推荐

  1. linux网络编程--select/poll/epoll 详解

    目录 参考链接 epoll函数 close epoll event EL/LT ET Edge Trigger 边沿触发工作模式 LT Level Trigger 水平触发工作模式 epoll 源码解 ...

  2. C++网络编程Select函数用法

    Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如 connect.accept.recv或recvfrom这样的阻塞程序 ...

  3. Android 网络请求OkHttp3流程分析

    基本概念 首先从使用出发,其次再结合源码来分析OkHttp3的内部实现的,建议大家下载 OkHttp 源码跟着本文,过一遍源码.首先来看一下OkHttp3的请求代码. OkHttpClient cli ...

  4. UNIX网络编程——select函数的并发限制和 poll 函数应用举例

    http://blog.csdn.net/chenxun_2010/article/details/50489577 一.用select实现的并发服务器,能达到的并发数,受两方面限制 1.一个进程能打 ...

  5. socket网络编程——TCP编程流程及端口号占用问题

    1.TCP编程流程 1.1TCP服务器端客户端及方法介绍 TCP 提供的是面向连接的.可靠的.字节流服务.TCP 的服务器端和客户端编程流程如下: socket()方法是用来创建一个套接字,有了套接字 ...

  6. socket网络编程——套接字地址结构

    声明:此博客是本人根据老师课件总结的,如有抄袭行为,本人会即刻删除. 1.主机字节序列和网络字节序列 主机字节序列分为大端字节序和小端字节序,不同的主机采用的字节序列可能不同.大端字节序是指一个整数的 ...

  7. 套接字编程-TCP网络编程

    文章目录 套接字地址结构 通用套接字地址数据结构 以太网协议的套接字地址数据结构 Netlink协议套接字地址结构 TCP网络编程 套接字初始化socket() domain type protoco ...

  8. L6网络编程--IO多路复用(day6)

    目录 一.I/O模型 二.阻塞I/O模式 : 1.读阻塞 2.写阻塞 三.非阻塞模式I/O 1.非阻塞模式的实现 fcntl()函数 四.多路复用I/O 基本常识: 多路复用服务器模型:​编辑 sel ...

  9. 实习秋招linux和网络编程知识点总结

    实习/秋招时按自己需求总结的知识点,内容并不十分详细,建议选择性阅读. 部分图片已失效. git常用命令速查表 git回滚 https://www.jianshu.com/p/f7451177476a ...

  10. C++网络编程(一):TCP套接字编程

    目录 基本数据结构 TCP服务器端的默认函数调用顺序 TCP客户端的默认函数调用情况 TCP网络编程主要流程 TCP客户端套接字的地址分配 TCP套接字的I/O缓存 代码实例 面试常见问题详解 参考资 ...

最新文章

  1. Leetcode: 101. Symmetric Tree
  2. PHP同时连接多个mysql数据库_php如何同时连接多个数据库
  3. 使用iostat分析IO性能
  4. 库存管理-历史库存和收发存系列-MB5B
  5. leetcode 705. 设计哈希集合
  6. spring学习(50):延迟加载
  7. 挂载报错:“/dev/vda1 is apparently in use by the system;”
  8. 数据分析的常用工具有哪些
  9. 微信公众号推文怎么做?
  10. HEVC之CU\PU\TU
  11. mysql的sql语句没错但是报错_sql语句没错·但是却报错,怎么回事?
  12. 大数据架构师——音乐数据中心平台离线数仓综合项目(一)
  13. 《深入理解Java虚拟机》内存管理机制 部分 读书笔记
  14. 用C++制作一款电话簿
  15. python输入一个字符串、计算其中小写字符的个数_利用键盘录入,输入一个字符串,统计该字符串中各个字符的数量,并输出(c/c++实现)...
  16. MySQL索引数据结构二叉树、红黑树、B-Tree、B+Tree、Hash
  17. axis2 默认端口_axis2 webservice 问题,高手进,急!!!!!!!!!!!!!!!!!!!!!
  18. iOS代码质量要求_苹果发布 iOS amp; iPadOS 13.1 beta 4 版本;Dart 2.5正式公布;SwiftUI View的生命周期...
  19. Win10常用的快捷键和触摸板操作合集
  20. Visual Studio 2019 和 qt 5.15.1 下 opengl 的运用 - Lighting - 01 - Colors

热门文章

  1. 荣新广源B班20121207作业
  2. Android成长的幕后推手:工程师鲁宾
  3. 金蝶BOS开发之--非空验证、时间、电话号码验证
  4. c#中的一些容易混淆的概念
  5. 最新!Oracle/ MySQL/ MSSQL 三大数据库集体跳水。。
  6. 面试官:MyBatis的SQL执行流程说这么详细,网上抄的吧!
  7. 爆红Github!再来一个小白练手项目,20个项目随你造!
  8. 你知道Arrays.asList 有坑吗?千万别踩啊!
  9. 推荐10个趣味实战项目,从零入门人工智能和数据分析,看这篇就够了
  10. 最全 MySQL 优化方法,从此优化不再难