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;}

欢迎关注公众号欢迎关注公众号

上一篇:tcp三次握手源码解析(服务端角度)
下一篇:重定向标准流到文件

发表评论

最新留言

不错!
[***.144.177.141]2025年04月14日 14时42分42秒