转载:http://blog.csdn.net/mo_hui123456/article/details/7961619
select系统调用用于多路监控,为什么称为多路呢,它可监控多个文件,例如我监控3个文件是否可读,如果这3个文件都不可读,它将在timeout阻塞进程(调用它的那个进程),如果有满足要求的文件(有文件可读),它将唤醒该进程。
它的原型:int select(int maxfd, fd_set *readfds, fd_set *writefds, fe_set *exceptfds, const struct timeval *timeout);当在timeout时间内不满足条件时,是会阻塞的,所以当有条件满足时,就会唤醒进程,从而进行下面的操作(这里为读操作)。
关于timeout的取值不同,其调用有不同的表现:
timeout为0时(指的是*timeout), 不管是否有文件满足要求,都立刻返回,无文件满足要求返回0,有文件满足要求返回一个正值.
Timeout为NULL,select将阻塞进程,直到某个文件满足要求
Timeout值为正整数,就是等待的最长时间,即select在timeout时间内阻塞进程。当超过这个数时,它就不会再阻塞了,返回0, 从而该进程继续向下工作。
Select调用返回时,返回值有如下情况:
1.正常情况下返回满足要求的文件描述符个数;
2.经过了timeout等待后仍无文件满足要求,返回值为0;
3.如果select被某个信号中断,(说明该进程阻塞可被信号打断,TASK_INTERRUPTIBLE)它将返回-1并设置errno为EINTR。
4.如果出错,返回-1并设置相应的errno。
select的使用方法:
1. 将要监控的文件添加到文件描述符集
2. 调用Select开始监控
3. 判断文件是否发生变化
它的一般模型如下:
fd_set fds; FD_ZERO(&fds); //清空集合 FD_SET(fd1,&fds); //设置描述符 FD_SET(fd2,&fds); //设置描述符 maxfdp=fd1+1; //描述符最大值加1,假设fd1>fd2 switch(select(maxfdp,&fds,NULL,NULL,&timeout)) //这里监控两个文件是否可读。 case -1: exit(-1);break; //select错误,退出程序 case 0:break; //超时退出 default: if(FD_ISSET(fd1,&fds)) //测试fd1是否可读 { }
select与poll调用最终会引发设备驱动中的poll()被执行。好像还有个扩展的poll,叫epoll.(这个不懂,高手请指导)
驱动中poll()原型:unsigned int (*poll)(struct file *filp,poll_table *wait);
Poll设备方法负责完成:
1. 使用poll_wait()将等待队列添加到poll_table中。
2.
返回描述设备是否可读或可写的掩码。
poll_wait原型:void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *wait); 这个函数是不会引起阻塞的,它所做的工作就是将可能引起设备文件状态变化的等待队列头添加到poll_table中。那么这里就有个问题了,既然驱动中的 poll()不会引起阻塞,那阻塞是发生在哪里呢?(下面说)
poll()的典型模板:
static unsigned int mem_poll(struct file *filp,poll_table *wait) { struct xxx_dev *dev =filp->private_data; //获得设备结构体指针,(从私有数据获得,前提私有数据已被赋值) unsigned int mask =0; /* 把等待队列添加到poll_table*/ poll_wait(filp,&dev->r_wait,wait); //加读等待队列头 /*返回掩码*/ if (有数据可读) mask = POLLIN |POLLRDNORM;/*设备可读*/ return mask; }
设备可读通常返回(POLLIN|POLLRDNORM )
设备可写通常返回(POLLOUT|POLLWRNORM )
Poll方法只是做一个登记,真正的阻塞发生在select.c 中的 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); wait = &table.pt; 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; 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); 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); //调用poll函数,返回掩码 } fput_light(file, fput_needed); //如果监控的文件满足条件(例如:在读集合里,可读,说明满足条件,在读集合里,可写,这是不满足条件的),retvall++。 判断文件是可读,可写,而且在这个集合里时才retall++,只要retall>0,就会不会阻塞。可见阻塞虽然并没有发生在poll()里面,但是阻塞依赖于poll返回的掩码。如果文件都不满足条件时,那在timeout里发生阻塞。 if ((mask & POLLIN_SET) && (in & bit)) { res_in |= bit; retval++; wait = NULL; } if ((mask & POLLOUT_SET) && (out & bit)) { res_out |= bit; retval++; wait = NULL; } if ((mask & POLLEX_SET) && (ex & bit)) { res_ex |= bit; retval++; wait = NULL; } } } if (res_in) *rinp = res_in; if (res_out) *routp = res_out; if (res_ex) *rexp = res_ex; cond_resched(); } wait = NULL; if (retval || timed_out || signal_pending(current)) //如果retval>0 (意味满足条件)或timeout超时,被信号打断,则跳出循环。则没有阻塞。 break; if (table.error) { retval = table.error; break; } /* * If this is the first loop and we have a timeout * given, then we convert to ktime_t and set the to * pointer to the expiry value. */ if (end_time && !to) { expire = timespec_to_ktime(*end_time); to = &expire; } //如果上面条件不满足,阻塞就发生在这里。阻塞该进程并且在timeout时间里调度其他进程。(阻塞意味着发生调度,将该进程撤换出去,调度其他进程,避免cpu空转) if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, to, slack)) timed_out = 1; } poll_freewait(&table); return retval; }