对select()参数fdset的完整理解 http://blog.csdn.net/maray/article/details/8285775
来源:互联网 发布:危机公关 知乎 编辑:程序博客网 时间:2024/05/18 13:27
对select()参数fdset的完整理解
虽然写了很多代码,但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上可以写数据进去
从上面两个细节中可以发现
- 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)
- void run(void)
- {
- int listener;
- struct fd_state *state[FD_SETSIZE];
- struct sockaddr_in sin;
- int i, maxfd;
- fd_set readset, writeset, exset;
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = 0;
- sin.sin_port = htons(40713);
- for (i = 0; i < FD_SETSIZE; ++i)
- state[i] = NULL;
- listener = socket(AF_INET, SOCK_STREAM, 0);
- make_nonblocking(listener);
- if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
- perror("bind");
- return;
- }
- if (listen(listener, 16)<0) {
- perror("listen");
- return;
- }
- FD_ZERO(&readset);
- FD_ZERO(&writeset);
- FD_ZERO(&exset);
- while (1) {
- maxfd = listener;
- FD_ZERO(&readset);
- FD_ZERO(&writeset);
- FD_ZERO(&exset);
- FD_SET(listener, &readset);
- for (i=0; i < FD_SETSIZE; ++i) {
- if (state[i]) {
- if (i > maxfd)
- maxfd = i;
- FD_SET(i, &readset);
- if (state[i]->writing) {
- FD_SET(i, &writeset);
- }
- }
- }
- if (select(maxfd+1, &readset, &writeset, &exset, NULL) < 0) {
- perror("select");
- return;
- }
- if (FD_ISSET(listener, &readset)) {
- struct sockaddr_storage ss;
- socklen_t slen = sizeof(ss);
- int fd = accept(listener, (struct sockaddr*)&ss, &slen);
- if (fd < 0) {
- perror("accept");
- } else if (fd > FD_SETSIZE) {
- close(fd);
- } else {
- make_nonblocking(fd);
- state[fd] = alloc_fd_state();
- assert(state[fd]);/*XXX*/
- }
- }
- for (i=0; i < maxfd+1; ++i) {
- int r = 0;
- if (i == listener)
- continue;
- if (FD_ISSET(i, &readset)) {
- r = do_read(i, state[i]); //ref http://www.wangafu.net/~nickm/libevent-book/01_intro.html
- }
- if (r == 0 && FD_ISSET(i, &writeset)) {
- r = do_write(i, state[i]); // ref same as above
- <span style="white-space:pre"> </span> }
- if (r) {
- free_fd_state(state[i]);
- state[i] = NULL;
- close(i);
- }
- }
- }
- }
我对于select的理解,离不开对内核源码的了解。是看了源码之后才豁然开朗。也贴一下内核源码(一些无关细节被省略了,更完整的代码参考http://bbs.chinaunix.net/thread-1926858-1-1.html),我在代码中另外加了一些注释,呼应上面的分析。
- 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);
- [color=Red]wait = &table.pt;[/color]
- 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;
- // fdset的输入参数,表明关心哪些fd
- inp = fds->in; outp = fds->out; exp = fds->ex;
- // fdset的输出参数,表明哪些关心的fd状态变成了可读可写可连接
- rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
- // 这里看看n,就是外面传进来的max_fd,就很能理解为什么max_fd需要加1了
- 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) {
- // 性能优化代码。一次判断4个字节,32个fd,如果都不关心,则可以一次跳过
- i += __NFDBITS;
- continue;
- }
- // 32个fd中至少有一个fd是被用户关心的,得瞧瞧
- 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);
- if (file) {
- f_op = file->f_op;
- mask = DEFAULT_POLLMASK;
- if (f_op && f_op->poll) //调用pool看这个fd是否ready
- mask = (*f_op->poll)(file, retval ? NULL : wait);
- fput_light(file, fput_needed);
- if ((mask & POLLIN_SET) && (in & bit)) {
- res_in |= bit;
- retval++;
- }
- if ((mask & POLLOUT_SET) && (out & bit)) {
- res_out |= bit;
- retval++;
- }
- if ((mask & POLLEX_SET) && (ex & bit)) {
- res_ex |= bit;
- retval++;
- }
- }
- }
- if (res_in)
- *rinp = res_in;
- if (res_out)
- *routp = res_out;
- if (res_ex)
- *rexp = res_ex;
- cond_resched();
- }
- return retval;
- }
对select的理解欲是被昨晚看的《编程人生》第一篇文章激发的, Jamie Zawinski:好奇心是驱动一个程序员的终极原动力,唯有好奇心才能让你对code了如指掌。
我的微博:http://weibo.com/raywill2
版权声明:本文为博主原创文章,未经博主允许不得转载。
- 对select()参数fdset的完整理解 http://blog.csdn.net/maray/article/details/8285775
- 对select()参数fdset的完整理解
- 对PyStringObject的认识(Intern机制) http://blog.csdn.net/wangyuquanliuli/article/details/8522302
- 对Python内存管理的认识(重点usedpool的一个trick的理解)http://blog.csdn.net/wangyuquanliuli/article/details/8606072
- select系统调用源码分析 http://blog.csdn.net/martin_liang/article/details/9124911
- c++中临时变量不能作为非const的引用参数http://blog.csdn.net/kongying168/article/details/3864756
- WinRAR命令行参数 转自:http://blog.csdn.net/cjolj/article/details/1592529
- H264参数语法文档: SPS、PPS、IDR( http://blog.csdn.net/heanyu/article/details/6205390)
- http://blog.csdn.net/jrq/article/details/4211075(URL参数中文乱码)
- apache 参数说明 转载自http://blog.csdn.net/Zhao1234567890123456/article/details/38569139
- C++对象作为函数参数 http://blog.csdn.net/tms_li/article/details/7765626
- tcpdump参数解析及使用详解(http://blog.csdn.net/hzhsan/article/details/43445787)
- Mongodb参数详解(参考:http://blog.csdn.net/freebird_lb/article/details/8229567)
- PrintStream: - shichunle的专栏 - 博客频道 - CSDN.NET http://blog.csdn.net/shichunle/article/details/6754930
- 深度理解链式前向星 http://blog.csdn.net/acdreamers/article/details/16902023
- 深入理解TAILQ队列(转自http://blog.csdn.net/hunanchenxingyu/article/details/8648794)
- 深入理解java中的package关键字 http://blog.csdn.net/lindir/article/details/8067732
- 《深入理解mybatis原理》 MyBatis事务管理机制 http://blog.csdn.net/luanlouis/article/details/37992171
- Spring MVC入门案例(2)
- 宽度决定高度
- Spring事务配置的五种方式
- Struts DynaActionForm example
- LeetCode题解:Climbing Stairs
- 对select()参数fdset的完整理解 http://blog.csdn.net/maray/article/details/8285775
- Redis管道(Pipelining)操作
- ZOJ 1649 Rescue
- android handler 内存泄露
- Session_Flow
- jquery使用cdn加载问题
- iOS多线程之NSoperation和GCD的比较
- COSMOS&SCOPE---微软内部的大数据平台(1)
- Python算法:动态规划