网络篇之select

文章详细介绍了Linux网络编程中的select函数,包括fd_set数据结构如何存储文件描述符,以及select函数的用户空间和内核空间实现。通过对do_select内核函数的分析,解释了如何遍历并检测文件描述符的可读、可写和错误状态,特别提到了socket的poll过程及其在TCP状态下的处理逻辑。
摘要由CSDN通过智能技术生成

网络篇之socket

网络篇之bind

网络篇之listen

网络篇之accept

网络篇之connect

网络篇之三次握手(SYN+ACK)

网络篇之三次握手(ACK)

网络篇之send

一 剖析fd_set

fd_set是select中重要的一个数据结构,fd_set中的__fds_bits是64位整型数据的数组,首先通过一段代码来看下fd_set是怎样保存fd句柄数据的。 

int main(void)
{
	fd_set fs1;
	FD_ZERO(&fs1);
	FD_SET(3, &fs1);
	FD_SET(64, &fs1);

	u_int64_t *p = fs1.__fds_bits;

	printf("p[0] = %X   \np[1] = %x \n", p[0], p[1]);

    return 0;
}

上述代码,用FD_SET将句柄3和64添加到fs1中,下面是执行结果:

p[0] = 8
p[1] = 1 

每个64位的整数,可以保存64个句柄,0-63保存在第一个整数上,64-127保存到第二个整数上,以此类推。下面这张图,便于理解数据的存储 :

理解了句柄在fd_set中的保存逻辑,有助于理解下面的代码。

二 select用户空间函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

nfds 为待查询的最大的句柄数+1

readfds 句柄的集合,检查集合中的句柄是否可读 

writefds 集合中的句柄是否可写

exceptfds 结合中的句柄是否产生错误

timeout 查询的超时时间,如设置的额数据内没有待查询的数据产生,则返回

返回值

     0 没有数据产生

    -1 函数执行中有错误发生

    >0 三个集合中返回的句柄的数量

三 select内核代码

SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,
		fd_set __user *, exp, struct timeval __user *, tvp)
{
	struct timespec64 end_time, *to = NULL;
	struct timeval tv;
	int ret;

	if (tvp) {
		if (copy_from_user(&tv, tvp, sizeof(tv)))
			return -EFAULT;

		to = &end_time;
		if (poll_select_set_timeout(to,
				tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),
				(tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
			return -EINVAL;
	}

	ret = core_sys_select(n, inp, outp, exp, to);
	ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);

	return ret;
}

 将用户空间设置超时时间的timeval,转称内核中的timespce64,然后调用core_sys_select。

在core_sys_select中,申请内存,划分成6块,分别保存用户输入/返回给用户的可读,可写,发生错误的句柄集合,然后调用do_select。

看do_select之前,先看下max_select_fd。用户空间创建的句柄,保存在内核进程的current->files中,在max_select_fd根据用户空间调用select传入的句柄,是否在current->files中,获取到最大的有效句柄。max_select_fd返回的是最大有效句柄+1。比如用户空间传入的最大的句柄是64,则max_select_fd返回65。

static int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
{
	ktime_t expire, *to = NULL;
	struct poll_wqueues table;
	poll_table *wait;
	int retval, i, timed_out = 0;
	u64 slack = 0;
	unsigned int busy_flag = net_busy_loop_on() ? POLL_BUSY_LOOP : 0;
	unsigned long busy_start = 0;

	rcu_read_lock();
	retval = max_select_fd(n, fds);
	rcu_read_unlock();

	if (retval < 0)
		return retval;
	n = retval; // 最大的fd+1

	poll_initwait(&table);
	wait = &table.pt;
	if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
		wait->_qproc = NULL;
		timed_out = 1;
	}

	if (end_time && !timed_out)
		slack = select_estimate_accuracy(end_time);

	retval = 0;
	for (;;) {
		unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
		bool can_busy_loop = false;

		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;

			in = *inp++; out = *outp++; ex = *exp++;
			all_bits = in | out | ex;
			if (all_bits == 0) {
				i += BITS_PER_LONG;
				continue;
			}

			for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1) {
				struct fd f;
				if (i >= n)
					break;
				if (!(bit & all_bits))
					continue;
				f = fdget(i); // i为有效的fd
				if (f.file) {
					const struct file_operations *f_op;
					f_op = f.file->f_op;
					mask = DEFAULT_POLLMASK;
					if (f_op->poll) { // sock_poll,在sock_alloc_file中设置的
						wait_key_set(wait, in, out,
							     bit, busy_flag);
						mask = (*f_op->poll)(f.file, wait);
					}
					... ...

				}
			}
			if (res_in)
				*rinp = res_in;
			if (res_out)
				*routp = res_out;
			if (res_ex)
				*rexp = res_ex;
			cond_resched();
		}
		wait->_qproc = NULL;
		if (retval || timed_out || signal_pending(current))
			break;
		if (table.error) {
			retval = table.error;
			break;
		}

		/* only if found POLL_BUSY_LOOP sockets && not out of time */
		if (can_busy_loop && !need_resched()) {
			if (!busy_start) {
				busy_start = busy_loop_current_time();
				continue;
			}
			if (!busy_loop_timeout(busy_start))
				continue;
		}
		busy_flag = 0;

		/*
		 * 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 = timespec64_to_ktime(*end_time);
			to = &expire;
		}

		if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
					   to, slack))
			timed_out = 1;
	}

	poll_freewait(&table);

	return retval;
}

for (i = 0; i < n; ++rinp, ++routp, ++rexp)

for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1)

 上面两个循环,实际上遍历小于n的每个句柄,如果句柄有效,调用fdget可以获取到对应的struct fd,继续调用sock_poll。

struct socket *sock = file->private_data;

在sock_poll利用上面方法获取到对应的socket,继续调用tcp_poll。 

unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)
{
	unsigned int mask;
	struct sock *sk = sock->sk;
	const struct tcp_sock *tp = tcp_sk(sk);
	int state;

	sock_rps_record_flow(sk);

	sock_poll_wait(file, sk_sleep(sk), wait); // 将wait添加到sk_sleep(sk)中

	state = sk_state_load(sk);

	// 只分析已经离连接的情况
	if (state == TCP_LISTEN)
		return inet_csk_listen_poll(sk);


	mask = 0;


	if (sk->sk_shutdown == SHUTDOWN_MASK || state == TCP_CLOSE)
		mask |= POLLHUP;
	if (sk->sk_shutdown & RCV_SHUTDOWN)
		mask |= POLLIN | POLLRDNORM | POLLRDHUP;

	/* Connected or passive Fast Open socket? */
	if (state != TCP_SYN_SENT &&
	    (state != TCP_SYN_RECV || tp->fastopen_rsk)) {
		int target = sock_rcvlowat(sk, 0, INT_MAX);

		if (tp->urg_seq == tp->copied_seq &&
		    !sock_flag(sk, SOCK_URGINLINE) &&
		    tp->urg_data)
			target++;

		if (tp->rcv_nxt - tp->copied_seq >= target)
			mask |= POLLIN | POLLRDNORM;

		if (!(sk->sk_shutdown & SEND_SHUTDOWN)) {
			if (sk_stream_is_writeable(sk)) {
				mask |= POLLOUT | POLLWRNORM;
			} else {  /* send SIGIO later */
				sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
				set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);

				/* Race breaker. If space is freed after
				 * wspace test but before the flags are set,
				 * IO signal will be lost. Memory barrier
				 * pairs with the input side.
				 */
				smp_mb__after_atomic();
				if (sk_stream_is_writeable(sk))
					mask |= POLLOUT | POLLWRNORM;
			}
		} else
			mask |= POLLOUT | POLLWRNORM;

		if (tp->urg_data & TCP_URG_VALID)
			mask |= POLLPRI;
	} else if (state == TCP_SYN_SENT && inet_sk(sk)->defer_connect) {
		/* Active TCP fastopen socket with defer_connect
		 * Return POLLOUT so application can call write()
		 * in order for kernel to generate SYN+data
		 */
		mask |= POLLOUT | POLLWRNORM;
	}
	/* This barrier is coupled with smp_wmb() in tcp_reset() */
	smp_rmb();
	if (sk->sk_err || !skb_queue_empty(&sk->sk_error_queue))
		mask |= POLLERR;

	return mask;
}

在tcp_poll中,有以下处理逻辑

1. 调用sock_poll_wait 将wait添加到sk_sleep(sk)列表中。如果当前进行没有用户空间请求的数据,且设置了超时时间,则进程会进入休眠。当数据到来时,激活此进程。这块逻辑,可以参考下《linux内核DEFINE_WAKE_Q的使用》。

2. 根据sk当前的状态,设置mask

if (tp->rcv_nxt - tp->copied_seq >= target)

mask |= POLLIN | POLLRDNORM;

rcv_nxt表示内核已经接收到数据的索引,copied_seq表示已经拷贝到用户空间的数据的索引。两个数据的差表示内核已经接收到,但未传给用户空间的数据,即有数据可读。可读数据超过一定阈值,设置可读标记(POLLIN)。

if (sk_stream_is_writeable(sk)) {

    ask |= POLLOUT | POLLWRNORM;

如果有发送空间,设置可写标记(POLLOUT)。

 回到de_select中

static int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
{
	... ...
				if (f.file) {
					const struct file_operations *f_op;
					f_op = f.file->f_op;
					mask = DEFAULT_POLLMASK;
					if (f_op->poll) { // sock_poll,在sock_alloc_file中设置的
						wait_key_set(wait, in, out,
							     bit, busy_flag);
						mask = (*f_op->poll)(f.file, wait);
					}
					fdput(f);
					if ((mask & POLLIN_SET) && (in & bit)) {
						res_in |= bit;
						retval++;
						wait->_qproc = NULL;
					}
					if ((mask & POLLOUT_SET) && (out & bit)) {
						res_out |= bit;
						retval++;
						wait->_qproc = NULL;
					}
					if ((mask & POLLEX_SET) && (ex & bit)) {
						res_ex |= bit;
						retval++;
						wait->_qproc = NULL;
					}
					/* got something, stop busy polling */
					if (retval) {
						can_busy_loop = false;
						busy_flag = 0;

					/*
					 * only remember a returned
					 * POLL_BUSY_LOOP if we asked for it
					 */
					} else if (busy_flag & mask)
						can_busy_loop = true;

				}
	... ...
}

根据tcp_poll返回的标记mask和用户空间请求的标记,设置函数返回值。

if ((mask & POLLIN_SET) && (in & bit)) {

    res_in |= bit;

    retval++;

    wait->_qproc = NULL;

}

这块代码表示,此sk有数据可读,且用户空间查询此socket是否可读,则将socket添加到res_in集合中。

 retval记录可读,可写,发生错误的socket的数量。

if (retval || timed_out || signal_pending(current))

    break;

如果读取到了任何数据,跳出最外层的循环。

if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,

to, slack))

    timed_out = 1;

如果没有任何数据可以返回,则开始休眠,等待数据。

最后,在core_sys_select中,将数据拷贝到用户空间

if (set_fd_set(n, inp, fds.res_in) || set_fd_set(n, outp, fds.res_out) ||

    set_fd_set(n, exp, fds.res_ex))

    ret = -EFAULT;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值