对select()参数fdset的完整理解 http://blog.csdn.net/maray/article/details/8285775

来源:互联网 发布:危机公关 知乎 编辑:程序博客网 时间:2024/05/18 13:27
 

对select()参数fdset的完整理解

分类: 应用开发 网络技术 1771人阅读 评论(0) 收藏 举报

虽然写了很多代码,但select我就从没有完整理解过,要用时不过copy paste而已。惭愧!


今天决定要对select()参数fdset有一个完整理解。Go!


先上一段代码(代码1-1),这段代码做的事情是

1. 创建一个socket来listen请求

2. 调用select等待新请求、等待已有请求的数据收发状态READY

3. 当有新连接请求来的时候就调用accpet接受请求

4. 当有数据可读可写的时候就调用do_read/do_write执行数据收发

5. loop回去

注意:上面所有的操作都是在一个线程上做的。


看完代码,再看for循环内几个细节。

细节1. select前每次都要对三个fdset进行初始化。具体初始化策略就是:

   - listen fd总是要set,这表明希望select关心listen fd的变化

   - 已经accept了的fd总是要set,这表明希望select关心这些fd上是否可读可写

细节2. select后会对三个fdset的状态进行检查。具体检查结果是:

  - listen fd如果被set了,表明有新的连接请求来了,需要调用accept

 - readfdset中如果有fd被set了,表明那个fd上有新数据可读

 - writefdset中如果有fd被set了,表明那个fd上可以写数据进去


从上面两个细节中可以发现

[cpp] view plaincopy
  1. if (select(maxfd+1, &readset, &writeset, &exset, NULL) < 0) {  

select的三个fdset参数是in/out型的参数,既负责输入、也负责输出。select内部只关心fdset上被置位为1的操作符状态,对于为0的它一概不关心。

想一下,既然fdset上置位为0,表明应用根本不关心,应用都不关心,内核何必死乞白赖的去查一下那个为0的状态位对应的fd的状态呢?

我之前之所以对select函数存在疑惑,就是对这一点不理解。我原来的理解方式是:

1. 应用只需要跟内核说:喂兄弟,我这个进程内哪些fd的状态变了呢?我也不知道我关心哪些fd,你帮我看着办吧,别漏掉了就行。

2. 内核就把系统中所有状态变了的fd都收集到一起,全部奉送给应用

3. 应用就根据这个集合来进行数据处理


现实世界的处理方式是:

1. 应用跟内核说:为兄弟,我关心a、b、c、d这几个fd的状态,您帮我查查?

2. 内核乖乖把a、b、c、d这几个fd的状态查了一把,其余fd统统忽略,反正查了也没用,还是省省吧

3. 应用拿到a、b、c、d这几个fd的状态后,根据他们的状态就行对应处理。


因此,应用需要关心这么两个问题:

1. 知道自己需要关心哪些fd,这需要用一个数据结构来跟踪。

2. 应用需要关心的fd有两类:一是自己的listen fd,二是自己accept过的fd,没有其它的了。


总算是明白select的猫腻了。


代码1-1 (来自http://www.wangafu.net/~nickm/libevent-book/01_intro.html)

[cpp] view plaincopy
  1. void run(void)  
  2. {  
  3.     int listener;  
  4.     struct fd_state *state[FD_SETSIZE];  
  5.     struct sockaddr_in sin;  
  6.     int i, maxfd;  
  7.     fd_set readset, writeset, exset;  
  8.   
  9.     sin.sin_family = AF_INET;  
  10.     sin.sin_addr.s_addr = 0;  
  11.     sin.sin_port = htons(40713);  
  12.   
  13.     for (i = 0; i < FD_SETSIZE; ++i)  
  14.         state[i] = NULL;  
  15.   
  16.     listener = socket(AF_INET, SOCK_STREAM, 0);  
  17.     make_nonblocking(listener);  
  18.   
  19.     if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {  
  20.         perror("bind");  
  21.         return;  
  22.     }  
  23.   
  24.     if (listen(listener, 16)<0) {  
  25.         perror("listen");  
  26.         return;  
  27.     }  
  28.   
  29.     FD_ZERO(&readset);  
  30.     FD_ZERO(&writeset);  
  31.     FD_ZERO(&exset);  
  32.   
  33.     while (1) {  
  34.         maxfd = listener;  
  35.   
  36.         FD_ZERO(&readset);  
  37.         FD_ZERO(&writeset);  
  38.         FD_ZERO(&exset);  
  39.   
  40.         FD_SET(listener, &readset);  
  41.   
  42.         for (i=0; i < FD_SETSIZE; ++i) {  
  43.             if (state[i]) {  
  44.                 if (i > maxfd)  
  45.                     maxfd = i;  
  46.                 FD_SET(i, &readset);  
  47.                 if (state[i]->writing) {  
  48.                     FD_SET(i, &writeset);  
  49.                 }  
  50.             }  
  51.         }  
  52.   
  53.         if (select(maxfd+1, &readset, &writeset, &exset, NULL) < 0) {  
  54.             perror("select");  
  55.             return;  
  56.         }  
  57.   
  58.         if (FD_ISSET(listener, &readset)) {  
  59.             struct sockaddr_storage ss;  
  60.             socklen_t slen = sizeof(ss);  
  61.             int fd = accept(listener, (struct sockaddr*)&ss, &slen);  
  62.             if (fd < 0) {  
  63.                 perror("accept");  
  64.             } else if (fd > FD_SETSIZE) {  
  65.                 close(fd);  
  66.             } else {  
  67.                 make_nonblocking(fd);  
  68.                 state[fd] = alloc_fd_state();  
  69.                 assert(state[fd]);/*XXX*/  
  70.             }  
  71.         }  
  72.   
  73.         for (i=0; i < maxfd+1; ++i) {  
  74.             int r = 0;  
  75.             if (i == listener)  
  76.                 continue;  
  77.   
  78.             if (FD_ISSET(i, &readset)) {  
  79.                 r = do_read(i, state[i]);  //ref http://www.wangafu.net/~nickm/libevent-book/01_intro.html  
  80.             }  
  81.             if (r == 0 && FD_ISSET(i, &writeset)) {  
  82.                 r = do_write(i, state[i]); // ref same as above  
[cpp] view plaincopy
  1. <span style="white-space:pre">  </span>    }  
  2.             if (r) {  
  3.                 free_fd_state(state[i]);  
  4.                 state[i] = NULL;  
  5.                 close(i);  
  6.             }  
  7.         }  
  8.     }  
  9. }  

我对于select的理解,离不开对内核源码的了解。是看了源码之后才豁然开朗。也贴一下内核源码(一些无关细节被省略了,更完整的代码参考http://bbs.chinaunix.net/thread-1926858-1-1.html),我在代码中另外加了一些注释,呼应上面的分析。

[cpp] view plaincopy
  1. int do_select(int n, fd_set_bits *fds, struct timespec *end_time)  
  2. {  
  3.         ktime_t expire, *to = NULL;  
  4.         struct poll_wqueues table;  
  5.         poll_table *wait;  
  6.         int retval, i, timed_out = 0;  
  7.         unsigned long slack = 0;  
  8.   
  9.         rcu_read_lock();  
  10.         retval = max_select_fd(n, fds);  
  11.         rcu_read_unlock();  
  12.   
  13.         if (retval < 0)  
  14.                 return retval;  
  15.         n = retval;  
  16.   
  17.         poll_initwait(&table);  
  18.         [color=Red]wait = &table.pt;[/color]  
  19.         if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {  
  20.                 wait = NULL;  
  21.                 timed_out = 1;  
  22.         }  
  23.   
  24.         if (end_time && !timed_out)  
  25.                 slack = estimate_accuracy(end_time);  
  26.   
  27.         retval = 0;  
  28.         for (;;) {  
  29.                 unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;  
  30.   
  31.         // fdset的输入参数,表明关心哪些fd  
  32.                 inp = fds->in; outp = fds->out; exp = fds->ex;  
  33.   
  34.         // fdset的输出参数,表明哪些关心的fd状态变成了可读可写可连接  
  35.                 rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;  
  36.           
  37.         // 这里看看n,就是外面传进来的max_fd,就很能理解为什么max_fd需要加1了  
  38.                 for (i = 0; i < n; ++rinp, ++routp, ++rexp) {  
  39.                         unsigned long in, out, ex, all_bits, bit = 1, mask, j;  
  40.                         unsigned long res_in = 0, res_out = 0, res_ex = 0;  
  41.                         const struct file_operations *f_op = NULL;  
  42.                         struct file *file = NULL;  
  43.   
  44.                         in = *inp++; out = *outp++; ex = *exp++;  
  45.                         all_bits = in | out | ex;    
  46.                         if (all_bits == 0) {  
  47.                 // 性能优化代码。一次判断4个字节,32个fd,如果都不关心,则可以一次跳过  
  48.                                 i += __NFDBITS;  
  49.                                 continue;     
  50.                         }  
  51.   
  52.              // 32个fd中至少有一个fd是被用户关心的,得瞧瞧  
  53.                         for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {    
  54.                                 int fput_needed;  
  55.                                 if (i >= n)  
  56.                                         break;  
  57.                                 if (!(bit & all_bits))  
  58.                                         continue;  
  59.                                 file = fget_light(i, &fput_needed);  
  60.                                 if (file) {  
  61.                                         f_op = file->f_op;  
  62.                                         mask = DEFAULT_POLLMASK;  
  63.                                         if (f_op && f_op->poll)   //调用pool看这个fd是否ready  
  64.                                                 mask = (*f_op->poll)(file, retval ? NULL : wait);  
  65.                                         fput_light(file, fput_needed);  
  66.                                         if ((mask & POLLIN_SET) && (in & bit)) {  
  67.                                                 res_in |= bit;   
  68.                                                 retval++;  
  69.                                         }  
  70.                                         if ((mask & POLLOUT_SET) && (out & bit)) {  
  71.                                                 res_out |= bit;  
  72.                                                 retval++;  
  73.                                         }  
  74.                                         if ((mask & POLLEX_SET) && (ex & bit)) {  
  75.                                                 res_ex |= bit;  
  76.                                                 retval++;  
  77.                                         }  
  78.                                 }  
  79.                         }  
  80.                         if (res_in)  
  81.                                 *rinp = res_in;  
  82.                         if (res_out)  
  83.                                 *routp = res_out;  
  84.                         if (res_ex)  
  85.                                 *rexp = res_ex;  
  86.                         cond_resched();  
  87.                 }  
  88.         return retval;  
  89. }  


对select的理解欲是被昨晚看的《编程人生》第一篇文章激发的, Jamie Zawinski:好奇心是驱动一个程序员的终极原动力,唯有好奇心才能让你对code了如指掌。


我的微博:http://weibo.com/raywill2



版权声明:本文为博主原创文章,未经博主允许不得转载。

0 0
原创粉丝点击