
I/O复用之select源码浅析
发布日期:2021-05-09 16:03:06
浏览次数:20
分类:精选文章
本文共 3487 字,大约阅读时间需要 11 分钟。
首先判断哪些文件描述符需要监听,然后不断循环检查是否满足条件。
static int do_select(int n, fd_set *in, fd_set *out, fd_set *ex, fd_set *res_in, fd_set *res_out, fd_set *res_ex){ int count; select_table wait_table, *wait; struct select_table_entry *entry; unsigned long set; int i,j; int max = -1; for (j = 0 ; j < __FDSET_LONGS ; j++) { // fds_bits数组每个元素是long,即32个bit,i代表最大的文件描述符 i = j << 5; // 超过了用户指定的大小 if (i >= n) break; // 逐个元素,即32位或 set = in->fds_bits[j] | out->fds_bits[j] | ex->fds_bits[j]; // i代表文件描述符,set>>=1即判断某个文件描述符是否需要监听 for ( ; set ; i++,set >>= 1) { if (i >= n) goto end_check; // 如果该位没有被设置则结束当次循环,即不需要监听 if (!(set & 1)) continue; // 判断文件描述符的有效性 if (!current->files->fd[i]) return -EBADF; if (!current->files->fd[i]->f_inode) return -EBADF; // 记录最大的文件描述符 max = i; } }end_check: // 用于下面的循环 n = max + 1; if(!(entry = (struct select_table_entry*) __get_free_page(GFP_KERNEL))) return -ENOMEM; // 清0 FD_ZERO(res_in); FD_ZERO(res_out); FD_ZERO(res_ex); count = 0; wait_table.nr = 0; wait_table.entry = entry; wait = &wait_table;repeat: current->state = TASK_INTERRUPTIBLE; // 遍历0到最大的文件描述符 for (i = 0 ; i < n ; i++) { // 需要监听的话则检查是否满足条件 if (FD_ISSET(i,in) && check(SEL_IN,wait,current->files->fd[i])) { // 满足则设置该文件描述符 FD_SET(i, res_in); // 满足条件的个数 count++; wait = NULL; } if (FD_ISSET(i,out) && check(SEL_OUT,wait,current->files->fd[i])) { FD_SET(i, res_out); count++; wait = NULL; } if (FD_ISSET(i,ex) && check(SEL_EX,wait,current->files->fd[i])) { FD_SET(i, res_ex); count++; wait = NULL; } } wait = NULL; // 还没有满足条件的,并且没有超超时则挂起进程 if (!count && current->timeout && !(current->signal & ~current->blocked)) { schedule(); goto repeat; } free_wait(&wait_table); free_page((unsigned long) entry); current->state = TASK_RUNNING; // 返回满足条件的个数 return count;}
执行完do_select后,主要是通过check函数进行判断是否满足条件。check函数根据文件描述符找到inode,然后再执行底层的文件或者网络层实现的select,这里只讲网络层的实现。
static int check(int flag, select_table * wait, struct file * file){ struct inode * inode; struct file_operations *fops; int (*select) (struct inode *, struct file *, int, select_table *); // 文件描述符对应的file结构对应的inode节点 inode = file->f_inode; // 执行底层的select函数 if ((fops = file->f_op) && (select = fops->select)) return select(inode, file, flag, wait) || (wait && select(inode, file, flag, NULL)); if (flag != SEL_EX) return 1; return 0;}
网络层select的实现主要是从sock_select->inet_select->tcp_select
static int tcp_select(struct sock *sk, int sel_type, select_table *wait){ // 监听型的socket,则判断是否有可用的连接 if (sk->state == TCP_LISTEN) return tcp_listen_select(sk, sel_type, wait); switch(sel_type) { // 是否有数据可读 case SEL_IN: if (sk->err) return 1; // 还没建立起连接,没有数据可读 if (sk->state == TCP_SYN_SENT || sk->state == TCP_SYN_RECV) break; if (sk->shutdown & RCV_SHUTDOWN) return 1; // 可读的等于已读的,没有数据可读 if (sk->acked_seq == sk->copied_seq) break; if (sk->urg_seq != sk->copied_seq || sk->acked_seq != sk->copied_seq+1 || sk->urginline || !sk->urg_data) return 1; break; // 能不能写 case SEL_OUT: if (sk->err) return 1; // 已关闭不能写 if (sk->shutdown & SEND_SHUTDOWN) return 0; // 还没建立连接不能写 if (sk->state == TCP_SYN_SENT || sk->state == TCP_SYN_RECV) break; /* * This is now right thanks to a small fix * by Matt Dillon. */ // 写空间不够不能写 if (sk->prot->wspace(sk) < sk->mtu+128+sk->prot->max_header) break; return 1; case SEL_EX: if (sk->urg_data) return 1; break; } // 阻塞,等待唤醒 select_wait(sk->sleep, wait); return 0;}
欢迎关注公众号