kk Blog —— 通用基础

date [-d @int|str] [+%s|"+%F %T"]

linux内核中tcp连接的断开处理

http://simohayha.iteye.com/blog/503856

我们这次主要来分析相关的两个断开函数close和shotdown以及相关的套接口选项SO_LINGER。这里要注意SO_LINGER对shutdown无任何影响。它只对close起作用。

先来坎SO_LINGER所对应的数据结构:

1
2
3
4
5
6
struct linger {
	//linger的开关
	int     l_onoff;    /* Linger active        */
	//所等待的时间。
	int     l_linger;   /* How long to linger for   */
};

这里对这个套接口选项就不详细介绍了,在unix网络编程中有详细的介绍,我们这里只会分析内核的处理代码。

首先来看close函数,我们知道缺醒情况下,close是立即返回,但是如果套接口的发送缓冲区还有未发送的数据,系统将会试着把这些数据发送给对端。而这个缺醒情况我们是可以通过SO_LINGER来改变的。还有一个要注意就是close调用并不一定会引发tcp的断开连接。因为close只是将这个socket的引用计数减一(主要是针对多个进程),而真正要直接引发断开,则需要用shutdown函数。

内核中socket的close的系统调用是sock_close,而在sock_close中,直接调用sock_release来实现功能,因此这里我们直接看sock_release的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void sock_release(struct socket *sock)
{
	if (sock->ops) {
		struct module *owner = sock->ops->owner;

		//调用inet_stream_ops的inet_release函数
		sock->ops->release(sock);
		//将ops致空。
		sock->ops = NULL;
		module_put(owner);
	}

	//这个域貌似是26.31新加的,具体做什么的还不知道。
	if (sock->fasync_list)
		printk(KERN_ERR "sock_release: fasync list not empty!\n");

	//更新全局的socket数目
	percpu_sub(sockets_in_use, 1);
	if (!sock->file) {
		//更新inode的引用计数
		iput(SOCK_INODE(sock));
		return;
	}
	sock->file = NULL;
}

然后来看inet_release的实现,这个函数主要用来通过SO_LINGER套接字来得到超时时间,然后调用tcp_close来关闭sock。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int inet_release(struct socket *sock)
{
	struct sock *sk = sock->sk;

	if (sk) {
		long timeout;

		/* Applications forget to leave groups before exiting */
		ip_mc_drop_socket(sk);

		timeout = 0;
		//判断是否设置SO_LINGER并且不是处于正在shutdowning,则设置timeout为l_linger(也就是我们设置的值).
		if (sock_flag(sk, SOCK_LINGER) &&
			!(current->flags & PF_EXITING))
			timeout = sk->sk_lingertime;
		sock->sk = NULL;
		//调用tcp_close.
		sk->sk_prot->close(sk, timeout);
	}
	return 0;
}

tcp_close函数比较长我们这里分段来分析它,首先来看第一部分。这里要注意几点:

1 当close掉一个服务端的父socket的时候,内核会先处理半连接队列然后是已经accept了的队列,最后才会处理父sock。

2 处理接收缓冲区的数据的时候,直接遍历receive_queue(前面blog有介绍),然后统计未发送的socket。我们知道close是不管接收buf的,也就是他会把接收buf释放掉,然后发送rst给对端的。

3 当so_linger有设置并且超时时间为0,则发送rst给对端,并且清空发送和接收buf。这个也不会引起最终的四分组终止序列。

4 当接收缓冲区有未读数据,则直接发送rst给对端。这个也不会引起最终的四分组终止序列。

5 当so_linger有设置,并且超时不为0,或者so_linger没有设置,此时都会引起最终的四分组终止序列来终止连接。(通过send_fin来发送fin,并引发四分组终止序列).而在send_fin中会发送掉发送缓冲区中的数据。

来看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void tcp_close(struct sock *sk, long timeout)
{
	struct sk_buff *skb;
	int data_was_unread = 0;
	int state;

	lock_sock(sk);
	sk->sk_shutdown = SHUTDOWN_MASK;

	//如果处于tcp_listen说明将要关闭的这个socket是一个服务端的主socket。
	if (sk->sk_state == TCP_LISTEN) {
		//设置sock状态.
		tcp_set_state(sk, TCP_CLOSE);

		//这个函数主要用来清理半连接队列(下面会简要分析这个函数)
		/* Special case. */
		inet_csk_listen_stop(sk);
		//处理要关闭的sock
		goto adjudge_to_death;
	}

	//遍历sk_receive_queue也就是输入buf队列。然后统计还没有读取的数据。
	while ((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) {
		u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq -
			  tcp_hdr(skb)->fin;
		data_was_unread += len;
		//free这个skb
		__kfree_skb(skb);
	}

	sk_mem_reclaim(sk);


	//第一个if主要是实现了rfc2525的2.17,也就是关闭的时候,如果接收buf中有未读数据,则发送一个rst给对端。(下面有摘抄相关内容)
	if (data_was_unread) {
		/* Unread data was tossed, zap the connection. */
		NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
		//设置状态
		tcp_set_state(sk, TCP_CLOSE);
		//发送rst
		tcp_send_active_reset(sk, GFP_KERNEL);
	}
	//第二个if主要是判断so_linger套接字,并且超时时间为0。此时我们就直接丢掉所有的发送缓冲区中的数据
	else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {
		/* Check zero linger _after_ checking for unread data. */
		//调用tcp_disconnect,这个函数主要用来断开和对端的连接,这个函数下面会介绍。
		sk->sk_prot->disconnect(sk, 0);
		NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
	}
	//这个函数主要用来判断是否需要发送fin,也就是判断状态。下面我会详细介绍这个函数。
	else if (tcp_close_state(sk)) {

		//发送fin.
		tcp_send_fin(sk);
	}

	//等待一段时间。这里的timeout,如果有设置so_linger的话就是l_linger.这里主要是等待发送缓冲区的buf发送(如果超时时间不为0).
	sk_stream_wait_close(sk, timeout);
	........................

}
rfc2525的2.17的介绍:
1
2
3
When an application closes a connection in such a way that it can no longer read any received data, 
the TCP SHOULD, per section 4.2.2.13 of RFC 1122, send a RST if there is any unread received data, 
or if any new data is received. A TCP that fails to do so exhibits "Failure to RST on close with data pending".

ok,现在来看上面遇到的3个函数,一个是inet_csk_listen_stop,一个是tcp_close_state,一个是tcp_disconnect.我们一个个来看他们。

首先是inet_csk_listen_stop函数。我们知道这个函数主要用来清理所有的半连接队列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void inet_csk_listen_stop(struct sock *sk)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct request_sock *acc_req;
	struct request_sock *req;

	//首先删除keepalive定时器。
	inet_csk_delete_keepalive_timer(sk);

	/* make all the listen_opt local to us */
	//得到accept 队列。
	acc_req = reqsk_queue_yank_acceptq(&icsk->icsk_accept_queue);

	//然后销毁掉所有的半连接队列,也就是listen_sock队列
	reqsk_queue_destroy(&icsk->icsk_accept_queue);


	//遍历accept队列断开与对端的连接。
	while ((req = acc_req) != NULL) {
	...............................................

		//调用tcp_disconnect来断开与对端的连接。这里注意是非阻塞的。
		sk->sk_prot->disconnect(child, O_NONBLOCK);

		sock_orphan(child);

		percpu_counter_inc(sk->sk_prot->orphan_count);

		//销毁这个sock。
		inet_csk_destroy_sock(child);

		........................................
	}
	WARN_ON(sk->sk_ack_backlog);
}

接下来来看tcp_disconnect函数。这个函数主要用来断开和对端的连接.它会释放读写队列,发送rst,清除定时器等等一系列操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int tcp_disconnect(struct sock *sk, int flags)
{
	struct inet_sock *inet = inet_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	int err = 0;
	int old_state = sk->sk_state;

	if (old_state != TCP_CLOSE)
		tcp_set_state(sk, TCP_CLOSE);
	...................

	//清除定时器,重传,delack等。
	tcp_clear_xmit_timers(sk);
	//直接free掉接收buf。
	__skb_queue_purge(&sk->sk_receive_queue);
	//free掉写buf。
	tcp_write_queue_purge(sk);
	__skb_queue_purge(&tp->out_of_order_queue);
#ifdef CONFIG_NET_DMA
	__skb_queue_purge(&sk->sk_async_wait_queue);
#endif

	inet->dport = 0;

	if (!(sk->sk_userlocks & SOCK_BINDADDR_LOCK))
		inet_reset_saddr(sk);
		..........................................
	//设置状态。
	tcp_set_ca_state(sk, TCP_CA_Open);
	//清理掉重传的一些标记
	tcp_clear_retrans(tp);
	inet_csk_delack_init(sk);
	tcp_init_send_head(sk);
	memset(&tp->rx_opt, 0, sizeof(tp->rx_opt));
	__sk_dst_reset(sk);

	WARN_ON(inet->num && !icsk->icsk_bind_hash);

	sk->sk_error_report(sk);
	return err;
}

紧接着是tcp_close_state函数这个函数就是用来判断是否应该发送fin:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//这个数组表示了当close后,tcp的状态变化,可以看到注释很清楚,包含了3部分。这里也就是通过current也就是tcp的状态取得new state也就是close的状态,然后再和TCP_ACTION_FIN按位于,得到action
static const unsigned char new_state[16] = {
  /* current state:        new state:      action:  */
  /* (Invalid)      */ TCP_CLOSE,
  /* TCP_ESTABLISHED    */ TCP_FIN_WAIT1 | TCP_ACTION_FIN,
  /* TCP_SYN_SENT   */ TCP_CLOSE,
  /* TCP_SYN_RECV   */ TCP_FIN_WAIT1 | TCP_ACTION_FIN,
  /* TCP_FIN_WAIT1  */ TCP_FIN_WAIT1,
  /* TCP_FIN_WAIT2  */ TCP_FIN_WAIT2,
  /* TCP_TIME_WAIT  */ TCP_CLOSE,
  /* TCP_CLOSE      */ TCP_CLOSE,
  /* TCP_CLOSE_WAIT */ TCP_LAST_ACK  | TCP_ACTION_FIN,
  /* TCP_LAST_ACK   */ TCP_LAST_ACK,
  /* TCP_LISTEN     */ TCP_CLOSE,
  /* TCP_CLOSING    */ TCP_CLOSING,
};

static int tcp_close_state(struct sock *sk)
{
	//取得new state
	int next = (int)new_state[sk->sk_state];
	int ns = next & TCP_STATE_MASK;

	tcp_set_state(sk, ns);

	//得到action
	return next & TCP_ACTION_FIN;
}

接下来来看tcp_close的剩余部分的代码,剩下的部分就是处理一些状态以及通知这里只有一个要注意的就是TCP_LINGER2这个套接字,这个套接字能够设置等待fin的超时时间,也就是tcp_sock的域linger2.我们知道系统还有一个sysctl_tcp_fin_timeout,也就是提供了一个sys文件系统的接口来修改这个值,不过我们如果设置linger2为一个大于0的值的话,内核就会取linger2这个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
adjudge_to_death:

	//得到sock的状态。
	state = sk->sk_state;
	sock_hold(sk);
	sock_orphan(sk);

	//唤醒阻塞在这个sock的队列(前面有详细介绍这个函数)
	release_sock(sk);

	local_bh_disable();
	bh_lock_sock(sk);
	WARN_ON(sock_owned_by_user(sk));

	//全局的cpu变量引用计数减一。
	percpu_counter_inc(sk->sk_prot->orphan_count);

	/* Have we already been destroyed by a softirq or backlog? */
	if (state != TCP_CLOSE && sk->sk_state == TCP_CLOSE)
		goto out;

	//如果状态为TCP_FIN_WAIT2,说明接收了ack,在等待对端的fin。
	if (sk->sk_state == TCP_FIN_WAIT2) {
		struct tcp_sock *tp = tcp_sk(sk);
		//超时时间小于0,则说明马上超时,设置状态为tcp_close,然后发送rst给对端。
		if (tp->linger2 < 0) {
			tcp_set_state(sk, TCP_CLOSE);
			tcp_send_active_reset(sk, GFP_ATOMIC);
			NET_INC_STATS_BH(sock_net(sk),
					LINUX_MIB_TCPABORTONLINGER);
		} else {
			//得到等待fin的超时时间。这里主要也就是在linger2和sysctl_tcp_fin_timeout中来取得。
			const int tmo = tcp_fin_time(sk);
			//如果超时时间太长,则启动keepalive定时器发送探测报。
			if (tmo > TCP_TIMEWAIT_LEN) {
				inet_csk_reset_keepalive_timer(sk,
						tmo - TCP_TIMEWAIT_LEN);
			} else {
				//否则进入time_wait状态。
				tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
				goto out;
			}
		}
	}
	......................................

	//如果sk的状态为tcp_close则destroy掉这个sk
	if (sk->sk_state == TCP_CLOSE)
		inet_csk_destroy_sock(sk);
	/* Otherwise, socket is reprieved until protocol close. */

out:
	bh_unlock_sock(sk);
	local_bh_enable();
	sock_put(sk);
}

然后来看send_fin的实现,这个函数用来发送一个fin,并且尽量发送完发送缓冲区中的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void tcp_send_fin(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	//取得写bufer的尾部。
	struct sk_buff *skb = tcp_write_queue_tail(sk);
	int mss_now;

	/* Optimization, tack on the FIN if we have a queue of
	 * unsent frames.  But be careful about outgoing SACKS
	 * and IP options.
	 */
	mss_now = tcp_current_mss(sk);
	//如果发送队列不为空,此时我们只需要设置sk buffer的标记位(也就是tcp报文的控制位为fin),可以看到我们是加到写buffer的尾部,这里是为了能尽量将写buffer中的数据全部传出)
	if (tcp_send_head(sk) != NULL) {
		TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_FIN;
		TCP_SKB_CB(skb)->end_seq++;
		tp->write_seq++;
	} else {
	..................................
		//到这里标明发送缓冲区位空,因此我们需要新建一个sk buffer,然后设置标记位,并加入到写buffer。
		skb_reserve(skb, MAX_TCP_HEADER);
		/* FIN eats a sequence byte, write_seq advanced by tcp_queue_skb(). */
		tcp_init_nondata_skb(skb, tp->write_seq,
					 TCPCB_FLAG_ACK | TCPCB_FLAG_FIN);
		tcp_queue_skb(sk, skb);
	}
	//发送写缓冲区中的数据。
	__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_OFF);
}
void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss,
				   int nonagle)
{
	struct sk_buff *skb = tcp_send_head(sk);

	if (!skb)
		return;

	/* If we are closed, the bytes will have to remain here.
	 * In time closedown will finish, we empty the write queue and
	 * all will be happy.
	 */
	if (unlikely(sk->sk_state == TCP_CLOSE))
		return;
	//发送数据,这里关闭了nagle。也就是立即将数据全部发送出去(我前面的blog有详细解释这个函数).
	if (tcp_write_xmit(sk, cur_mss, nonagle, 0, GFP_ATOMIC))
		tcp_check_probe_timer(sk);
}

接下来来看shutdown的实现。在2.26.31中,系统调用的实现有些变化。

这里我们要知道shutdown会将写缓冲区的数据发出,然后唤醒阻塞的进程,来读取读缓冲区中的数据。

这个系统调用所对应的内核函数就是os_shutdown_socket。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define SHUT_RD 0
#define SHUT_WR 1
#define SHUT_RDWR 2

int os_shutdown_socket(int fd, int r, int w)
{
	int what, err;

	if (r && w)
		what = SHUT_RDWR;
	else if (r)
		what = SHUT_RD;
	else if (w)
		what = SHUT_WR;
	else
		return -EINVAL;

	//调用socket的shutdown也就是kernel_sock_shutdown
	err = shutdown(fd, what);
	if (err < 0)
		return -errno;
	return 0;
}


int kernel_sock_shutdown(struct socket *sock, enum sock_shutdown_cmd how)
{
	//他最终会调用inet_shutdown
	return sock->ops->shutdown(sock, how);
}

来看inet_shutdown的实现.这个函数的主要工作就是通过判断sock的状态不同来调用相关的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
int inet_shutdown(struct socket *sock, int how)
{
	struct sock *sk = sock->sk;
	int err = 0;

	/* This should really check to make sure
	 * the socket is a TCP socket. (WHY AC...)
	 */
	//这里要注意每个how都是加1的,这说明在内核里读写是为1,2,3
	how++; /* maps 0->1 has the advantage of making bit 1 rcvs and
			   1->2 bit 2 snds.
			   2->3 */
	//判断how的合法性。
	if ((how & ~SHUTDOWN_MASK) || !how) /* MAXINT->0 */
		return -EINVAL;
	//锁住sock
	lock_sock(sk);

	//SS_CONNECTING说明这个sock的连接正在处理中。state域表示socket当前的内部状态
	if (sock->state == SS_CONNECTING) {
		//如果状态为这几个状态,说明是处于半连接处理阶段,此时设置状态为SS_DISCONNECTING
		if ((1 << sk->sk_state) &
			(TCPF_SYN_SENT | TCPF_SYN_RECV | TCPF_CLOSE))
			sock->state = SS_DISCONNECTING;
		else
			//否则设置为连接完毕
			sock->state = SS_CONNECTED;
	}

	//除过TCP_LISTEN以及TCP_SYN_SENT状态外的其他状态最终都会进入sk->sk_prot->shutdown也就是tcp_shutdown函数。

	switch (sk->sk_state) {
	//如果状态为tco_close则设置错误号,然后进入default处理
	case TCP_CLOSE:
		err = -ENOTCONN;
		/* Hack to wake up other listeners, who can poll for
		   POLLHUP, even on eg. unconnected UDP sockets -- RR */
	default:
		sk->sk_shutdown |= how;
		if (sk->sk_prot->shutdown)
			sk->sk_prot->shutdown(sk, how);
		break;

	/* Remaining two branches are temporary solution for missing
	 * close() in multithreaded environment. It is _not_ a good idea,
	 * but we have no choice until close() is repaired at VFS level.
	 */
	case TCP_LISTEN:
		//如果不为SHUT_RD则跳出switch,否则进入tcp_syn_sent的处理。
		if (!(how & RCV_SHUTDOWN))
			break;
		/* Fall through */
	case TCP_SYN_SENT:
		//断开连接,然后设置state
		err = sk->sk_prot->disconnect(sk, O_NONBLOCK);
		sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
		break;
	}

	/* Wake up anyone sleeping in poll. */
	//唤醒阻塞在这个socket上的进程,这里是为了将读缓冲区的数据尽量读完。
	sk->sk_state_change(sk);
	release_sock(sk);
	return err;
}

来看tcp_shutdown函数。

这里要注意,当只关闭读的话,并不会引起发送fin,也就是只会设置个标记,然后在读取数据的时候返回错误。而关闭写端,则就会引起发送fin。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void tcp_shutdown(struct sock *sk, int how)
{
	/*  We need to grab some memory, and put together a FIN,
	 *  and then put it into the queue to be sent.
	 *      Tim MacKenzie(tym@dibbler.cs.monash.edu.au) 4 Dec '92.
	 */
	//如果为SHUT_RD则直接返回。
	if (!(how & SEND_SHUTDOWN))
		return;

	/* If we've already sent a FIN, or it's a closed state, skip this. */
	//这里英文注释很详细我就不多解释了。
	if ((1 << sk->sk_state) &
		(TCPF_ESTABLISHED | TCPF_SYN_SENT |
		 TCPF_SYN_RECV | TCPF_CLOSE_WAIT)) {
		/* Clear out any half completed packets.  FIN if needed. */
		//和tcp_close那边处理一样
		if (tcp_close_state(sk))
			tcp_send_fin(sk);
	}
}

最后来看sock_def_readable它就是sk->sk_state_change。也就是用来唤醒阻塞的进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
static void sock_def_readable(struct sock *sk, int len)
{
	read_lock(&sk->sk_callback_lock);
	//判断是否有进程在等待这个sk
	if (sk_has_sleeper(sk))
	//有的话,唤醒进程,这里可以看到递交给上层的是POLLIN,也就是读事件。
	wake_up_interruptible_sync_poll(sk->sk_sleep, POLLIN |
						POLLRDNORM | POLLRDBAND);

	//这里异步唤醒,可以看到这里也是POLL_IN.
	sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);
	read_unlock(&sk->sk_callback_lock);
}

可以看到shutdown函数只会处理SEND_SHUTDOWN。并且当调用shutdown之后,读缓冲区,还可以继续读取。

Socket层实现系列 — I/O事件及其处理函数

http://blog.csdn.net/zhangskd/article/details/45787989

主要内容:Socket I/O事件的定义、I/O处理函数的实现。

内核版本:3.15.2

I/O事件定义

sock中定义了几个I/O事件,当协议栈遇到这些事件时,会调用它们的处理函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct sock {
	...
	struct socket_wq __rcu *sk_wq; /* socket的等待队列和异步通知队列 */
	...
	/* callback to indicate change in the state of the sock.
	 * sock状态改变时调用,比如从TCP_SYN_SENT或TCP_SYN_RECV变为TCP_ESTABLISHED,
	 * 导致connect()的唤醒。比如从TCP_ESTABLISHED变为TCP_CLOSE_WAIT。
	 */
	void (*sk_state_change) (struct sock *sk);

	/* callback to indicate there is data to be processed.
	 * sock上有数据可读时调用,比如服务器端收到第三次握手的ACK时会调用,导致accept()的唤醒。
	 */
	void (*sk_data_ready) (struct sock *sk);

	/* callback to indicate there is buffer sending space available.
	 * sock上有发送空间可写时调用,比如发送缓存变得足够大了。
	 */
	void (*sk_write_space) (struct sock *sk);

	/* callback to indicate errors (e.g. %MSG_ERRQUEUE)
	 * sock上有错误发生时调用,比如收到RST包。
	 */
	void (*sk_error_report) (struct sock *sk);
	...
};

Socket I/O事件的默认处理函数在sock初始化时赋值。

对于SOCK_STREAM类型的Socket,sock有发送缓存可写事件会被更新为sk_stream_write_space。

1
2
3
4
5
6
7
8
9
void sock_init_data(struct socket *sock, struct sock *sk)
{
	...
	sk->sk_state_change = sock_def_wakeup; /* sock状态改变事件 */
	sk->sk_data_ready = sock_def_readable; /* sock有数据可读事件 */
	sk->sk_write_space = sock_def_write_space; /* sock有发送缓存可写事件 */
	sk->sk_error_report = sock_def_error_report; /* sock有IO错误事件 */
	...
}

判断socket的等待队列上是否有进程。

1
2
3
4
5
static inline bool wq_has_sleeper(struct socket_wq *wq)
{
	smp_mb();
	return wq && waitqueue_active(&wq->wait);
}

状态改变事件

sk->sk_state_change的实例为sock_def_wakeup(),当sock的状态发生改变时,会调用此函数来进行处理。

1
2
3
4
5
6
7
8
9
10
static void sock_def_wakeup(struct sock *sk)
{
	struct socket_wq *wq; /* socket的等待队列和异步通知队列 */

	rcu_read_lock();
	wq = rcu_dereference(sk->sk_wq);
	if (wq_has_sleeper(wq)) /* 有进程阻塞在此socket上 */
		wake_up_interruptible_all(&wq->wait); /* 唤醒此socket上的所有睡眠进程 */
	rcu_read_unlock();
}
1
2
3
4
5
6
7
8
9
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)

void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key)
{
	unsigned long flags;
	spin_lock_irqsave(&q->lock, flags);
	__wake_up_common(q, mode, nr_exclusive, 0, key);
	spin_unlock_irqrestore(&q->lock, flags);
}

初始化等待任务时,如果flags设置了WQ_FLAG_EXCLUSIVE,那么传入的nr_exclusive为1,

表示只允许唤醒一个等待任务,这是为了避免惊群现象。否则会把t等待队列上的所有睡眠进程都唤醒。

1
2
3
4
5
6
7
8
9
10
11
12
13
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode, int nr_exclusive,
							 int wake_flags, void *key)
{
	wait_queue_t *curr, *next;

	list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
		unsigned flags = curr->flags;

		if (curr->func(curr, mode, wake_flags, key) && (flags & WQ_FLAG_EXCLUSIVE)
			!--nr_exclusive)
			break;
	}
}

最终调用的是等待任务中的处理函数,默认为autoremove_wake_function()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

#define DEFINE_WAIT_FUNC(name, function)    \
	wait_queue_t name = {    \
		.private = current,    \
		.func = function,    \
		.task_list = LIST_HEAD_INIT((name).task_list),    \
	}

int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
	int ret = default_wake_function(wait, mode, sync, key); /* 默认的唤醒函数 */

	if (ret)
		list_del_init(&wait->task_list); /* 从等待队列中删除 */

	return ret;
}

int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags, void *key)
{
	return try_to_wake_up(curr->private, mode, wake_flags);
}

try_to_wake_up()通过把进程的状态设置为TASK_RUNNING,并把进程插入CPU运行队列,来唤醒睡眠的进程。

有数据可读事件

sk->sk_data_ready的实例为sock_def_readable(),当sock有输入数据可读时,会调用此函数来处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void sock_def_readable(struct sock *sk)
{
	struct socket_wq *wq; /* socket的等待队列和异步通知队列 */

	rcu_read_lock();
	wq = rcu_dereference(sk->sk_wq);

	if (wq_has_sleeper(wq)) /* 有进程在此socket的等待队列 */
		wake_up_interruptible_sync_poll(&wq->wait, POLLIN | POLLPRI |
			POLLRDNORM | POLLRDBAND); /* 唤醒等待进程 */

	/* 异步通知队列的处理。
	 * 检查应用程序是否通过recv()类调用来等待接收数据,如果没有就发送SIGIO信号,
	 * 告知它有数据可读。
	 * how为函数的处理方式,band为用来告知的IO类型。
	 */
	sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define wake_up_interruptible_sync_poll(x, m) \
	__wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))

void __wake_up_sync_key(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key)
{
	unsigned long flags;
	int wake_flags = 1; /* XXX WF_SYNC */

	if (unlikely(!q))
		return;
	if (unlikely(nr_exclusive != 1))
		wake_flags = 0;

	spin_lock_irqsave(&q->lock, flags);
	__wake_up_common(q, mode, nr_exclusive, wake_flags, key);
	spin_unlock_irqrestore(&q->lock, flags);
}

最终也是调用__wake_up_common()。初始化等待任务时,flags |= WQ_FLAG_EXCLUSIVE。

传入的nr_exclusive为1,表示只允许唤醒一个等待任务。所以这里只会唤醒一个等待的进程。

有缓存可写事件

sk->sk_write_space的实例为sock_def_write_space()。

如果socket是SOCK_STREAM类型的,那么函数指针的值会更新为sk_stream_write_space()。

sk_stream_write_space()在TCP中的调用路径为:

1
2
3
4
tcp_rcv_established / tcp_rcv_state_process
	tcp_data_snd_check
		tcp_check_space
			tcp_new_space
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* When incoming ACK allowed to free some skb from write_queue,
 * we remember this event in flag SOCK_QUEUE_SHRUNK and wake up socket
 * on the exit from tcp input handler.
 */
static void tcp_new_space(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (tcp_should_expand_sndbuf(sk)) {
		tcp_sndbuf_expand(sk);
		tp->snd_cwnd_stamp = tcp_time_stamp;
	}

	/* 检查是否需要触发有缓存可写事件 */
	sk->sk_write_space(sk);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void sk_stream_write_space(struct sock *sk)
{
	struct socket *sock = sk->sk_socket;
	struct socket_wq *wq; /* 等待队列和异步通知队列 */

	/* 如果剩余的发送缓存不低于发送缓存上限的1/3,且尚未发送的数据不高于一定值时 */
	if (sk_stream_is_writeable(sk) && sock) {
		clear_bit(SOCK_NOSPACE, &sock->flags); /* 清除发送缓存不够的标志 */

		rcu_read_lock();
		wq = rcu_dereference(sk->sk_wq); /* socket的等待队列和异步通知队列 */
		if (wq_has_sleeper(wq)) /* 如果等待队列不为空,则唤醒一个睡眠进程 */
			wake_up_interruptible_poll(&wq->wait, POLLOUT | POLLWRNORM | POLLWRBAND);

		/* 异步通知队列不为空,且允许发送数据时。
		 * 检测sock的发送队列是否曾经到达上限,如果有的话发送SIGIO信号,告知异步通知队列上
		 * 的进程有发送缓存可写。
		 */
		if (wq && wq->fasync_list && !(sk->sk_shutdown & SEND_SHUTDOWN))
			sock_wake_async(sock, SOCK_WAKE_SPACE, POLL_OUT);

		rcu_read_unlock();
	}
}

#define wake_up_interruptible_poll(x, m) \
	__wake_up(x, TASK_INTERRUPTIBLE, 1, (void *) (m))

最终也是调用__wake_up_common()。初始化等待任务时,flags |= WQ_FLAG_EXCLUSIVE。

传入的nr_exclusive为1,表示只允许唤醒一个等待进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct sock {
	...
	/* 发送队列中,skb数据区的总大小 */
	atomic_t sk_wmem_alloc;
	...
	int sk_sndbuf; /* 发送缓冲区大小的上限 */
	struct sk_buff_head sk_write_queue; /* 发送队列 */
	...
	/* 发送队列的总大小,包含发送队列中skb数据区的总大小,
	 * 以及sk_buff、sk_shared_info结构体、协议头的额外开销。
	 */
	int sk_wmem_queued;
	...
};

如果剩余的发送缓存大于发送缓存上限的1/3,且尚未发送的数据少于一定值时,才会触发有发送

缓存可写的事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static inline bool sk_stream_is_writeable(const struct sock *sk)
{
	return sk_stream_wspace(sk) >= sk_stream_min_wspace(sk) &&
}

static inline int sk_stream_wspace(const struct sock *sk)
{
	return sk->sk_sndbuf - sk->sk_wmem_queued;
}

static inline int sk_stream_min_wspace(const struct sock *sk)
{
	return sk->sk_wmem_queued >> 1;
}

检查尚未发送的数据是否已经够多了,如果超过了用户设置的值,就不用触发有发送缓存可写事件,

以免使用过多的内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static inline bool sk_stream_memory_free(const struct sock *sk)
{
	if (sk->sk_wmem_queued >= sk->sk_sndbuf)
		return false;

	return sk->sk_prot->stream_memory_free ? sk->sk_prot->stream_memory_free(sk) : true;
}

struct proto tcp_prot = {
	...
	.stream_memory_free = tcp_stream_memory_free,
	...
};

static inline bool tcp_stream_memory_free(const struct sock *sk)
{
	const struct tcp_sock *tp = tcp_sk(sk);
	u32 notsent_bytes = tp->write_seq - tp->snd_nxt; /* 尚未发送的数据大小 */

	/* 当尚未发送的数据,少于配置的值时,才触发有发送缓存可写的事件。
	 * 这是为了避免发送缓存占用过多的内存。
	 */
	return notsent_bytes < tcp_notsent_lowat(tp);
}

如果有使用TCP_NOTSENT_LOWAT选项,则使用用户设置的值。

否则使用sysctl_tcp_notsent_lowat,默认为无穷大。

1
2
3
4
static inline u32 tcp_notsent_lowat(const struct tcp_sock *tp)
{
	return tp->notsent_lowat ?: sysctl_tcp_notsent_lowat;
}

有I/O错误事件

sk->sk_error_report的实例为sock_def_error_report()。

在以下函数中会调用I/O错误事件处理函数:

1
2
3
4
tcp_disconnect
tcp_reset
tcp_v4_err
tcp_write_err
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void sock_def_error_report(struct sock *sk)
{
	struct socket_wq *wq; /* 等待队列和异步通知队列 */

	rcu_read_lock();
	wq = rcu_dereference(sk->sk_wq);
	if (wq_has_sleeper(wq)) /* 有进程阻塞在此socket上 */
		wake_up_interruptible_poll(&wq->wait, POLLERR);

	/* 如果使用了异步通知,则发送SIGIO信号通知进程有错误 */
	sk_wake_async(sk, SOCK_WAKE_IO, POLL_ERR);
}

#define wake_up_interruptible_poll(x, m) \
	__wake_up(x, TASK_INTERRUPTIBLE, 1, (void *) (m))

最终也是调用__wake_up_common(),由于nr_exclusive为1,只会唤醒socket上的一个等待进程。

Socket层实现系列 — 睡眠驱动的同步等待

http://blog.csdn.net/zhangskd/article/details/45770323

主要内容:Socket的同步等待机制,connect和accept等待的实现。

内核版本:3.15.2

概述

socket上定义了几个IO事件:状态改变事件、有数据可读事件、有发送缓存可写事件、有IO错误事件。对于这些事件,socket中分别定义了相应的事件处理函数,也称回调函数。

Socket I/O事件的处理过程中,要使用到sock上的两个队列:等待队列和异步通知队列,这两个队列中都保存着等待该Socket I/O事件的进程。

Q:为什么要使用两个队列,等待队列和异步通知队列有什么区别呢?
A:等待队列上的进程会睡眠,直到Socket I/O事件的发生,然后在事件处理函数中被唤醒。异步通知队列上的进程则不需要睡眠,Socket I/O事件发时,事件处理函数会给它们发送到信号,这些进程事先注册的信号处理函数就能够被执行。

等待队列

Socket层使用等待队列来进行阻塞等待,在等待期间,阻塞在此socket上的进程会睡眠。

1
2
3
4
5
6
7
8
9
10
11
12
struct sock {
	...
	struct socket_wq __rcu *sk_wq; /* socket的等待队列和异步通知队列 */
	...
}

struct socket_wq {
	/* Note: wait MUST be first field of socket_wq */
	wait_queue_head_t wait; /* 等待队列头 */
	struct fasync_struct *fasync_list; /* 异步通知队列 */
	struct rcu_head *rcu;
};
(1) socket的等待队列头
1
2
3
4
5
struct __wait_queue_head {
	spinlock_t lock;
	struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
(2) 进程的等待任务
1
2
3
4
5
6
7
8
9
10
struct __wait_queue {
	unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
	void *private; /* 指向当前的进程控制块 */
	wait_queue_func_t func; /* 唤醒函数 */
	struct list_head task_list; /* 用于链接入等待队列 */
};
typedef struct __wait_queue wait_queue_t;
typedef int (*wait_queue_func_t) (wait_queue_t *wait, unsigned mode, int flags, void *key);
int default_wake_function(wait_queue_t *wait, unsigned mode, int flags, void *key);
(3) 初始化等待任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

#define DEFINE_WAIT_FUNC(name, function)    \
	wait_queue_t name = {    \
		.private = current,    \
		.func = function,    \
		.task_list = LIST_HEAD_INIT((name).task_list),    \
	}

int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
	int ret = default_wake_function(wait, mode, sync, key); /* 默认的唤醒函数 */

	if (ret)
		list_del_init(&wait->task_list); /* 从等待队列中删除 */

	return ret;
}

int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags, void *key)
{
	return try_to_wake_up(curr->private, mode, wake_flags);
}

try_to_wake_up()通过把进程的状态设置为TASK_RUNNING,并把进程插入CPU运行队列,来唤醒睡眠的进程。

(4) 把等待任务插入到等待队列中

获取sock的等待队列。

1
2
3
4
5
static inline wait_queue_head_t *sk_sleep(struct sock *sk)
{
	BUILD_BUG_ON(offsetof(struct socket_wq, wait) != 0);
	return &rcu_dereference_raw(sk->sk_wq)->wait;
}

把等待任务加入到等待队列中,同时设置当前进程的状态,TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
	unsigned long flags;
	wait->flags &= ~WQ_FLAG_EXCLUSIVE; /* 可以同时唤醒多个等待进程 */

	spin_lock_irqsave(&q->lock, flags);

	if (list_empty(&wait->task_list))
		__add_wait_queue(q, wait); /* 把等待任务加入到等待队列的头部,会最先被唤醒 */

	set_current_state(state); /* 设置进程的状态 */

	spin_unlock_irqrestore(&q->lock, flags);
}

prepare_to_wait()和prepare_to_wait_exclusive()都是用来把等待任务加入到等待队列中,不同之处在于使用prepare_to_wait_exclusive()时,会在等待任务中添加WQ_FLAG_EXCLUSIVE标志,表示一次只能唤醒一个等待任务,目的是为了避免惊群现象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
	unsigned long flags;

	/* 这个标志表示一次只唤醒一个等待任务,避免惊群现象 */
	wait->flags |= WQ_FLAG_EXCLUSIVE;

	spin_lock_irqsave(&q->lock, flags);

	if (list_empty(&wait->task_list))
		__add_wait_queue_tail(q, wait); /* 把此等待任务加入到等待队列尾部 */

	set_current_state(state); /* 设置当前进程的状态 */

	spin_unlock_irqrestore(&q->lock, flags);
}

static inline void __add_wait_queue_tail(wait_queue_head_t *head, wait_queue_t *new)
{
	list_add_tail(&new->task_list, &head->task_list);
}

#define set_current_state(state_value)    \
	set_mb(current->state, (state_value))
(5) 删除等待任务

从等待队列中删除等待任务,同时把等待进程的状态置为可运行状态,即TASK_RUNNING。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * finish_wait - clean up after waiting in a queue
 * @q: waitqueue waited on,等待队列头
 * @wait: wait descriptor,等待任务
 *
 * Sets current thread back to running state and removes the wait
 * descriptor from the given waitqueue if still queued.
 */
void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
	unsigned long flags;
	__set_current_state(TASK_RUNNING);

	if (! list_empty_careful(&wait->task_list)) {
		spin_lock_irqsave(&q->lock, flags);

		list_del_init(&wait->task_list); /* 从等待队列中删除 */

		spin_unlock_irqrestore(&q->lock, flags);
	}
}

connect等待

(1) 睡眠

connect()的超时时间为sk->sk_sndtimeo,在sock_init_data()中初始化为MAX_SCHEDULE_TIMEOUT,表示无限等待,可以通过SO_SNDTIMEO选项来修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static long inet_wait_for_connect(struct sock *sk, long timeo, int writebias)
{
	DEFINE_WAIT(wait);  /* 初始化等待任务 */

	/* 把等待任务加入到socket的等待队列头部,把进程的状态设为TASK_INTERRUPTIBLE */
	prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
	sk->sk_write_pending += writebias;

	/* Basic assumption: if someone sets sk->sk_err, he _must_ change state of the socket
	 * from TCP_SYN_*. Connect() does not allow to get error notifications without closing
	 * the socket.
	 */

	/* 完成三次握手后,状态就会变为TCP_ESTABLISHED,从而退出循环 */
	while ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
		release_sock(sk); /* 等下要睡觉了,先释放锁 */

		/* 进入睡眠,直到超时或收到信号,或者被I/O事件处理函数唤醒。
		 * 1. 如果是收到信号退出的,timeo为剩余的jiffies。
		 * 2. 如果使用了SO_SNDTIMEO选项,超时退出后,timeo为0。
		 * 3. 如果没有使用SO_SNDTIMEO选项,timeo为无穷大,即MAX_SCHEDULE_TIMEOUT,
		 *      那么返回值也是这个,而超时时间不定。为了无限阻塞,需要上面的while循环。
		 */
		timeo = schedule_timeout(timeo);

		lock_sock(sk); /* 被唤醒后重新上锁 */

		/* 如果进程有待处理的信号,或者睡眠超时了,退出循环,之后会返回错误码 */
		if (signal_pending(current) || !timeo)
			break;

		/* 继续睡眠吧 */
		prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
	}

	/* 等待结束时,把等待进程从等待队列中删除,把当前进程的状态设为TASK_RUNNING */
	finish_wait(sk_sleep(sk), &wait);
	sk->sk_write_pending -= writebias;
	return timeo;
}
(2) 唤醒

三次握手中,当客户端收到SYNACK、发出ACK后,连接就成功建立了。此时连接的状态从TCP_SYN_SENT或TCP_SYN_RECV变为TCP_ESTABLISHED,sock的状态发生变化,会调用sock_def_wakeup()来处理连接状态变化事件,唤醒进程,connect()就能成功返回了。

sock_def_wakeup()的函数调用路径如下:

1
2
3
4
5
6
7
8
9
tcp_v4_rcv
	tcp_v4_do_rcv
		tcp_rcv_state_process
			tcp_rcv_synsent_state_process
				tcp_finish_connect
					sock_def_wakeup
						wake_up_interruptible_all
							__wake_up
								__wake_up_common
1
2
3
4
5
6
7
8
9
10
void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
	...
	tcp_set_state(sk, TCP_ESTABLISHED); /* 在这里设置为连接已建立的状态 */
	...
	if (! sock_flag(sk, SOCK_DEAD)) {
		sk->sk_state_change(sk); /* 指向sock_def_wakeup,会唤醒调用connect()的进程,完成连接的建立 */
		sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT); /* 如果使用了异步通知,则发送SIGIO通知进程可写 */
	}
}

accept等待

(1) 睡眠

accept()超时时间为sk->sk_rcvtimeo,在sock_init_data()中初始化为MAX_SCHEDULE_TIMEOUT,表示无限等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/* Wait for an incoming connection, avoid race conditions.
 * This must be called with the socket locked.
 */
static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	DEFINE_WAIT(wait); /* 初始化等待任务 */
	int err;

	for (; ;) {
		/* 把等待任务加入到socket的等待队列中,把进程状态设置为TASK_INTERRUPTIBLE */
		prepare_to_wait_exclusive(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);

		release_sock(sk); /* 等下可能要睡觉了,先释放 */

		if (reqsk_queue_empty(&icsk->icsk_accept_queue)) /* 如果全连接队列为空 */
			timeo = schedule_timeout(timeo); /* 进入睡眠直到超时或收到信号,或被IO事件处理函数唤醒 */

		lock_sock(sk); /* 醒来后重新上锁 */
		err = 0;
		/* 全连接队列不为空时,说明有新的连接建立了,成功返回 */
		if (! reqsk_queue_empty(&icsk->icsk_accept_queue))
			break;

		err = -EINVAL;
		if (sk->sk_state != TCP_LISTEN) /* 如果sock不处于监听状态了,退出,返回错误码 */
			break;

		err = sock_intr_errno(timeo);

		/* 如果进程有待处理的信号,退出,返回错误码。
		 * 因为timeo默认为MAX_SCHEDULE_TIMEOUT,所以err默认为-ERESTARTSYS。
		 * 接下来会重新调用此函数,所以accept()依然阻塞。
		 */
		if (signal_pending(current))
			break;

		err = -EAGAIN;
		if (! timeo) /* 如果等待超时,即超过用户设置的sk->sk_rcvtimeo,退出 */
			break;
	}

	/* 从等待队列中删除等待任务,把等待进程的状态设为TASK_RUNNING */
	finish_wait(sk_sleep(sk), &wait);
	return err;
}
(2) 唤醒

三次握手中,当服务器端接收到ACK完成连接建立的时候,会把新的连接链入全连接队列中,然后唤醒监听socket上的等待进程,accept()就能成功返回了。

三次握手时,当收到客户端的ACK后,经过如下调用:

1
2
3
4
5
6
7
tcp_v4_rcv
	tcp_v4_do_rcv
		tcp_child_process
			sock_def_readable
				wake_up_interruptible_sync_poll
					__wake_up_sync_key
						__wake_up_common

最终调用我们给等待任务注册的唤醒函数。

我们来看下accept()是如何避免惊群现象的。

1
2
3
4
5
6
7
8
9
10
11
12
13
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode, int nr_exclusive,
							 int wake_flags, void *key)
{
	wait_queue_t *curr, *next;

	list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
		unsigned flags = curr->flags;

		if (curr->func(curr, mode, wake_flags, key) && (flags & WQ_FLAG_EXCLUSIVE)
			!--nr_exclusive)
			break;
	}
}

初始化等待任务时,flags |= WQ_FLAG_EXCLUSIVE。传入的nr_exclusive为1,表示只允许唤醒一个等待任务。

所以这里只会唤醒一个等待的进程,不会导致惊群现象。

Socket层实现系列 — 信号驱动的异步等待

http://blog.csdn.net/zhangskd/article/details/45932775

主要内容:Socket的异步通知机制。

内核版本:3.15.2

概述

socket上定义了几个IO事件:状态改变事件、有数据可读事件、有发送缓存可写事件、有IO错误事件。对于这些事件,socket中分别定义了相应的事件处理函数,也称回调函数。

Socket I/O事件的处理过程中,要使用到sock上的两个队列:等待队列和异步通知队列,这两个队列中都保存着等待该Socket I/O事件的进程。

Q:为什么要使用两个队列,等待队列和异步通知队列有什么区别呢?
A:等待队列上的进程会睡眠,直到Socket I/O事件的发生,然后在事件处理函数中被唤醒。异步通知队列上的进程则不需要睡眠,Socket I/O事件发时,事件处理函数会给它们发送到信号,这些进程事先注册的信号处理函数就能够被执行。

异步通知队列

Socket层使用异步通知队列来实现异步等待,此时等待Socket I/O事件的进程不用睡眠。

1
2
3
4
5
6
7
8
9
10
11
12
struct sock {
	...
	struct socket_wq __rcu *sk_wq; /* socket的等待队列和异步通知队列 */
	...
}

struct socket_wq {
	/* Note: wait MUST be first field of socket_wq */
	wait_queue_head_t wait; /* 等待队列头 */
	struct fasync_struct *fasync_list; /* 异步通知队列 */
	struct rcu_head *rcu;
};
1
2
3
4
5
6
7
8
struct fasync_struct {
	spinlock_t fa_lock;
	int magic;
	int fa_fd; /* 文件描述符 */
	struct fasync_struct *fa_next; /* 用于链入单向链表 */
	struct file *fa_file; /* fa_file->f_owner记录接收信号的进程 */
	struct rcu_head fa_rcu;
};

通过之前的blog《linux的异步通知机制》,我们知道为了能处理协议栈发出的SIGIO信号,

用户程序需要做的事情有:
1. 通过signal()指定SIGIO的处理函数。
2. 设置sockfd的拥有者为本进程,如此一来本进程才能收到协议栈发出的SIGIO信号。
3. 设置sockfd支持异步通知,即设置O_ASYNC标志。

对应的用户程序函数调用大概如下:

1
2
3
4
signal(SIGIO, my_handler); /* set new SIGIO handler */
fcntl(sockfd, F_SETOWN, getpid()); /* set sockfd's owner process */
oflags = fcntl(sockfd, F_GETFL); /* get old sockfd flags */
fcntl(sockfd, F_SETFL, oflags | O_ASYNC); /* set new sockfd flags */

下文关注的是内核层面的一些工作:
1. 如何把进程加入Socket的异步通知队列,或者把进程从Socket的异步通知队列中删除。
2. 协议栈何时发送信号给Socket异步通知队列上的进程。

插入和删除

首先来看下fcntl()的系统调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
	struct fd f = fdget_raw(fd);
	long err = -EBADF; /* Bad file number */

	if (! f.file)
		goto out;

	/* File is opened with O_PATH, almost nothing can be done with it */
	if (unlikely(f.file->f_mode & FMODE_PATH)) {
		if (! check_fcntl_cmd(cmd))
			goto out1;
	}

	err = security_file_fcntl(f.file, cmd, arg);
	if (! err)
		err = do_fcntl(fd, cmd, arg, f.file); /* 实际的处理函数 */

out1:
	fdput(f);
out:
	return err;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static long do_fcntl(int fd, unsigned int cmd, unsigned long arg, struct fil *filp)
{
	long err = -EINVAL;

	switch(cmd) {
	...
	case F_SETFL: /* 在这里设置O_ASYNC标志 */
		err = setfl(fd, filp, arg);
		break;
	...
	case F_SETOWN: /* 在这里设置所有者进程 */
		err = f_setown(filp, arg, 1);
		break;
	....
	}

	return err;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int setfl(int fd, struct file *filp, unsigned long arg)
{
	...
	/* ->fasync() is responsible for setting the FASYNC bit. */
	if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) {
		error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);

		if (error < 0)
			goto out;
		if (error > 0)
			error = 0;
	}
	...
}

Socket文件的操作函数集为socket_file_ops。

1
2
3
4
5
static const struct file_operations socket_file_ops = {
	...
	.fasync = sock_fasync,
	...
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* Update the socket async list. */
static int sock_fasync(int fd, struct file *filp, int on)
{
	struct socket *sock = filp->private_data;
	struct sock *sk = sock->sk;
	struct socket_wq *wq; /* Socket的等待队列和异步通知队列 */

	if (sk == NULL)
		return -EINVAL;

	lock_sock(sk);
	wq = rcu_dereference_protected(sock->wq, sock_owned_by_user(sk));

	fasync_helper(fd, filp, on, &wq->fasync_list); /* 使用此函数来插入或删除 */

	/* 设置或取消SOCK_FASYNC标志 */
	if (! wq->fasync_list)
		sock_reset_flag(sk, SOCK_FASYNC);
	else
		sock_set_flag(sk, SOCK_FASYNC);

	release_sock(sk);

	return 0;
}

和设备驱动一样,最终调用fasync_helper()来把进程插入异步通知队列,或者把进程从异步通知队列中删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
 * fasync_helper() is used by almost all character device drivers to set up the fasync
 * queue, and for regular files by the file lease code. It returns negative on error, 0 if
 * it did no changes and positive if it added / deleted the entry.
 */

int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp)
{
	if (! on)
		return fasync_remove_entry(filp, fapp); /* 插入 */

	return fasync_add_entry(fd, filp, fapp); /* 删除 */
}

发送信号

当Socket I/O事件触发时,协议栈会调用sk_wake_async()来进行异步通知。

函数的处理方式:

1
2
3
4
5
6
enum {
	SOCK_WAKE_IO, /* 直接发送SIGIO信号 */
	SOCK_WAKE_WAITD, /* 检测应用程序是否通过recv()类调用来等待接收数据,如果没有才发送SIGIO信号 */
	SOCK_WAKE_SPACE, /* 检测sock的发送队列是否曾经到达上限,如果有的话发送SIGIO信号 */
	SOCK_WAKE_URG, /* 直接发送SIGURG信号 */
};

通告的IO类型,常用的有:

1
2
3
4
5
6
7
#define __SI_POLL 0
#define POLL_IN (__SI_POLL | 1) /* data input available, 有接收数据可读 */
#define POLL_OUT (__SI_POLL | 2) /* output buffers available, 有输出缓存可写 */
#define POLL_MSG (__SI_POLL | 3) /* input message available, 有输入消息可读 */
#define POLL_ERR (__SI_POLL | 4) /* i/0 error, I/O错误 */
#define POLL_PRI (__SI_POLL | 5) /* high priority input available, 有紧急数据可读 */
#define POLL_HUP (__SI_POLL | 6) /* device disconnected, 设备关闭或文件关闭,无法继续读写 */

how为函数的处理方式,band为通告的IO类型。

1
2
3
4
5
static inline void sk_wake_async(struct sock *sk, int how, int band)
{
	if (sock_flag(sk, SOCK_FASYNC)) /* sock需要支持异步通知 */
		sock_wake_async(sk->sk_socket, how, band);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int sock_wake_async(struct socket *sock, int how, int band)
{
	struct socket_wq *wq;

	if (! sock)
		return -1;

	rcu_read_lock();
	wq = rcu_dereference(sock->wq); /* socket的等待队列和异步通知队列 */

	if (! wq || !wq->fasync_list) { /* 如果有队列没有实例 */
		rcu_read_unlock();
		return -1;
	}

	switch(how) {
	/* 检测应用程序是否通过recv()类调用来等待接收数据,如果没有才发送SIGIO信号 */
	case SOCK_WAKE_WAITD:
		if (test_bit(SOCK_ASYNC_WAITDATA, &sock->flags))
			break;
		goto call_kill;

	/* 检测sock的发送队列是否曾经到达上限,如果有的话发送SIGIO信号 */
	case SOCK_WAKE_SPACE:
		if (! test_and_clear_bit(SOCK_ASYNC_NOSPACE, &sock->flags))
			break;
	/* fall_through */

	case SOCK_WAKE_IO: /* 直接发送SIGIO信号 */
call_kill:
			/* 发送SIGIO信号给异步通知队列上的进程,告知IO消息 */
			kill_fasync(&wq->fasync_list, SIGIO, band);
			break;

	case SOCK_WAKE_URG:
			/* 发送SIGURG信号给异步通知队列上的进程 */
			kill_fasync(&wq->fasync_list, SIGURG, band);
	}

	rcu_read_unlock();
	return 0;
}

和设备驱动一样,最终调用kill_fasync()来发送信号给用户进程。

1
2
3
4
5
6
7
8
9
void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
	/* First a quick test without locking: usually the list is empty. */
	if (*f) {
		rcu_read_lock();
		kill_fasync_rcu(rcu_dereference(*fp), sig, band);
		rcu_read_unlock();
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band)
{
	while (fa) {
		struct fown_struct *fown;
		unsigned long flags;

		if (fa->magic != FASYNC_MAGIC) {
			printk(KERN_ERR "kill_fasync: bad magic number in fasync_struct!\n");
			return;
		}

		spin_lock_irqsave(&fa->fa_lock, flags);
		if (fa->fa_file) {
			fown = &fa->file->f_owner; /* 持有文件的进程 */

			/* Don't send SIGURG to processes which have not set a queued signum:
			 * SIGURG has its own default signalling mechanism. */

			if (! (sig == SIGURG && fown->signum == 0))
				send_sigio(fown, fa->fa_fd, band); /* 发送信号给持有文件的进程 */
		}
		spin_unlock_irqrestore(&fa->fa_lock, flags);

		fa = rcu_dereference(fa->fa_next); /* 指向下一个异步通知结构体 */
	}
}

socket和sock的一些分析

http://blog.csdn.net/wolongzhumeng/article/details/8900414

1、每一个打开的文件、socket等等都用一个file数据结构代表,这样文件和socket就通过inode->u(union)中的各个成员来区别:

1
2
3
4
5
6
7
8
9
struct inode {
	.....................
	union {
		struct ext2_inode_info ext2_i;
		struct ext3_inode_info ext3_i;
		struct socket socket_i;
		.....................
	} u;
};

2、每个socket数据结构都有一个sock数据结构成员,sock是对socket的扩充,两者一一对应,socket->sk指向对应的sock,sock->socket 指向对应的socket;

3、socket和sock是同一事物的两个侧面,为什么不把两个数据结构合并成一个呢?这是因为socket是inode结构中的一部分,即把inode结 构内部的一个union用作socket结构。由于插口操作的特殊性,这个数据结构中需要有大量的结构成分,如果把这些成分全部放到socket 结构中,则inode结构中的这个union就会变得很大,从而inode结构也会变得很大,而对于其他文件系统这个union是不需要这么大的, 所以会造成巨大浪费,系统中使用inode结构的数量要远远超过使用socket的数量,故解决的办法就是把插口分成两部分,把与文件系 统关系密切的放在socket结构中,把与通信关系密切的放在另一个单独结构sock中;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
struct socket
{
	socket_state state;      // 该state用来表明该socket的当前状态
	typedef enum {
		SS_FREE = 0,         /* not allocated */
		SS_UNCONNECTED,      /* unconnected to any socket */
		SS_CONNECTING,       /* in process of connecting */
		SS_CONNECTED,        /* connected to socket */
		SS_DISCONNECTING     /* in process of disconnecting */
	} socket_state;
	unsigned long flags;     //该成员可能的值如下,该标志用来设置socket是否正在忙碌
	#define SOCK_ASYNC_NOSPACE 0
	#define SOCK_ASYNC_WAITDATA 1
	#define SOCK_NOSPACE 2
	struct proto_ops *ops;   //依据协议邦定到该socket上的特定的协议族的操作函数指针,例如IPv4 TCP就是inet_stream_ops
	struct inode *inode;     //表明该socket所属的inode
	struct fasync_struct *fasync_list; //异步唤醒队列
	struct file *file;       //file回指指针
	struct sock *sk;         //sock指针
	wait_queue_head_t wait;  //sock的等待队列,在TCP需要等待时就sleep在这个队列上
	short type;              //表示该socket在特定协议族下的类型例如SOCK_STREAM,
	unsigned char passcred;  //在TCP分析中无须考虑
};

struct sock {
	/* socket用来对进入的包进行匹配的5大因素 */
	__u32 daddr;        // dip,Foreign IPv4 addr
	__u32 rcv_saddr;    // 记录套接字所绑定的地址 Bound local IPv4 addr
	__u16 dport;        // dport
	unsigned short num; /* 套接字所在的端口号, 端口号小于1024的为特权端口, 只有特权用户才能绑定,当用户指定的端
						 * 口号为零时, 系统将提供一个未分配的用户端口,如果对于raw socket的话,该num又可以用来
						 * 保存socket(int family, int type, int protocol)中的protocol,而不是端口号了;在bind时候,会首先
						 * 将邦定的源端口号赋予该成员,最终sport成员从该成员出获取源端口号__tcp_v4_hash主要就
						 * 是利用了该成员来hash从而排出hash链
						 */
	int bound_dev_if;   // Bound device index if != 0

	/* 主hash链,系统已分配的端口用tcp_hashinfo.__tcp_bhash来索引, 索引槽结构为tcp_bind_hashbucket, 端口绑定结构用tcp_bind_bucket描述,
	它包含指向绑定到该端口套接字的指针(owners), 套接字的sk->prev指针指向该绑定结构 */
	struct sock *next;
	struct sock **pprev;
	/* sk->bind_next和sk->bind_pprev用来描述绑定到同一端口的套接字,例如http服务器 */
	struct sock *bind_next;
	struct sock **bind_pprev;
	struct sock *prev;

	volatile unsigned char state, zapped; // Connection state,zapped在TCP分析中无须考虑
	__u16 sport;                   // 源端口,见num

	unsigned short family;         // 协议族,例如PF_INET
	unsigned char reuse;           // 地址是否可重用,只有RAW才使用
	unsigned char shutdown;        // 判断该socket连接在某方向或者双向方向上都已经关闭
	#define SHUTDOWN_MASK 3
	#define RCV_SHUTDOWN 1
	#define SEND_SHUTDOWN 2
	atomic_t refcnt;               // 引用计数
	socket_lock_t lock;            // 锁标志, 每个socket都有一个自旋锁,该锁在用户上下文和软中断处理时提供了同步机制
	typedef struct {
		spinlock_t slock;
		unsigned int users;
		wait_queue_head_t wq;
	} socket_lock_t;
	wait_queue_head_t *sleep;      // Sock所属线程的自身休眠等待队列
	struct dst_entry *dst_cache;   // 目的地的路由缓存
	rwlock_t dst_lock;             // 为该socket赋dst_entry值时的锁

	/* sock的收发都是要占用内存的,即发送缓冲区和接收缓冲区。 系统对这些内存的使用是有限制的。 通常,每个sock都会从配额里
		预先分配一些,这就是forward_alloc, 具体分配时:
		1)比如收到一个skb,则要计算到rmem_alloc中,并从forward_alloc中扣除。 接收处理完成后(如用户态读取),则释放skb,并利
			用tcp_rfree()把该skb的内存反还给forward_alloc。
		2)发送一个skb,也要暂时放到发送缓冲区,这也要计算到wmem_queued中,并从forward_alloc中扣除。真正发送完成后,也释放
			skb,并反还forward_alloc。 当从forward_alloc中扣除的时候,有可能forward_alloc不够,此时就要调用tcp_mem_schedule()来增
			加forward_alloc,当然,不是随便想加就可以加的,系统对整个TCP的内存使用有总的限制,即sysctl_tcp_mem[3]。也对每个sock
			的内存使用分别有限制,即sysctl_tcp_rmem[3]和sysctl_tcp_wmem[3]。只有满足这些限制(有一定的灵活性),forward_alloc才
			能增加。 当发现内存紧张的时候,还会调用tcp_mem_reclaim()来回收forward_alloc预先分配的配额。
	*/
	int rcvbuf;                    // 接受缓冲区的大小(按字节)
	int sndbuf;                    // 发送缓冲区的大小(按字节)
	atomic_t rmem_alloc;           // 接受队列中存放的数据的字节数
	atomic_t wmem_alloc;           // 发送队列中存放的数据的字节数
	int wmem_queued;               // 所有已经发送的数据的总字节数
	int forward_alloc;             // 预分配剩余字节数

	struct sk_buff_head receive_queue; // 接受队列
	struct sk_buff_head write_queue;   // 发送队列
	atomic_t omem_alloc;               // 在TCP分析中无须考虑 * "o" is "option" or "other" */

	__u32 saddr; /* 指真正的发送地址,这里需要注意的是,rcv_saddr是记录套接字所绑定的地址,其可能是广播或者
					多播,对于我们要发送的包来说,只能使用接口的IP地址,而不能使用广播或者多播地址 */
	unsigned int allocation;       // 分配该sock之skb时选择的模式,GFP_ATOMIC还是GFP_KERNEL等等

	volatile char dead,            // tcp_close.tcp_listen_stop.inet_sock_release调用sock_orphan将该值置1,表示该socket已经和进程分开,变成孤儿
				done,              // 用于判断该socket是否已经收到 fin,如果收到则将该成员置1
				urginline,         // 如果该值被设置为1,表示将紧急数据放于普通数据流中一起处理,而不在另外处理
				keepopen,          // 是否启动保活定时器
				linger,            // lingertime一起,指明了close()后保留的时间
				destroy,           // 在TCP分析中无须考虑
				no_check,          // 是否对发出的skb做校验和,仅对UDP有效
				broadcast,         // 是否允许广播,仅对UPD有效
				bsdism;            // 在TCP分析中无须考虑
	unsigned char debug;           // 在TCP分析中无须考虑
	unsigned char rcvtstamp;       // 是否将收到skb的时间戳发送给app
	unsigned char use_write_queue; // 在init中该值被初始化为1,该值一直没有变化
	unsigned char userlocks;       // 包括如下几种值的组合,从而改变收包等操作的执行顺序
	#define SOCK_SNDBUF_LOCK 1
	#define SOCK_RCVBUF_LOCK 2
	#define SOCK_BINDADDR_LOCK 4
	#define SOCK_BINDPORT_LOCK 8
	int route_caps;                // 指示本sock用到的路由的信息
	int proc;                      // 保存用户线程的pid
	unsigned long lingertime;      // lingertime一起,指明了close()后保留的时间
	int hashent;                   // 存放4元的hash值
	struct sock *pair;             // 在TCP分析中无须考虑

	/* 一个进程也许会锁住socket导致该socket不能被改变。特别是这点意味着其甚至不能被驱动中断所改变,例如,
		到达的报会被堵塞,导致我们无法获取新的数据或者任何的状态改变。所以在这里,当socket被锁住的时候,中
		断处理可以将包往下面的backlog中添加*/
	struct {
		struct sk_buff *head;
		struct sk_buff *tail;
	} backlog;

	rwlock_t callback_lock;          // sock相关函数内部操作的保护锁
	struct sk_buff_head error_queue; // 错误报文的队列,很少使用
	struct proto *prot;              // 例如指向tcp_prot

	union {       // 私有TCP相关数据保存
		struct tcp_opt af_tcp;
		.............
	} tp_pinfo;

	int err,      // 保存各种错误,例如ECONNRESET Connection reset by peer,从而会影响到后续流程的处理
		err_soft; // 保存各种软错误,例如EPROTO Protocol error,从而会影响到后续流程的处理
	unsigned short ack_backlog;       // 当前已经accept的数目
	unsigned short max_ack_backlog;   // 当前listen sock能保留多少个待处理TCP连接.
	__u32 priority;                   /* Packet queueing priority,Used to set the TOS field. Packets with a higher priority may be processed first, depending on the device’s queueing discipline. See SO_PRIORITY */
	unsigned short type;              // 例如SOCK_STREAM,SOCK_DGRAM或者SOCK_RAW等
	unsigned char localroute;         // Route locally only if set – set by SO_DONTROUTE option.
	unsigned char protocol;           // socket(int family, int type, int protocol)中的protocol
	struct ucred peercred;            // 在TCP分析中无须考虑
	int rcvlowat;                     /* 声明在开始发送 数据 (SO_SNDLOWAT) 或正在接收数据的用户 (SO_RCVLOWAT) 传递数据之
	前缓冲区内的最小字节数. 在 Linux 中这两个值是不可改变的, 固定为 1 字节. */
	long rcvtimeo;                    // 接收时的超时设定, 并在超时时报错
	long sndtimeo;                    // 发送时的超时设定, 并在超时时报错

	union {       // 私有inet相关数据保存
		struct inet_opt af_inet;
		.................
	} protinfo;

	/* the timer is used for SO_KEEPALIVE (i.e. sending occasional keepalive probes to a remote site – by default, set to 2 hours in
	stamp is simply the time that the last packet was received. */
	struct timer_list timer;
	struct timeval stamp;
	struct socket *socket; // 对应的socket
	void *user_data;       // 私有数据,在TCP分析中无须考虑

	/* The state_change operation is called whenever the status of the socket is changed. Similarly, data_ready is called
		when data have been received, write_space when free memory available for writing has increased and error_report
		when an error occurs, backlog_rcv when socket locked, putting skb to backlog, destruct for release this sock*/
	void (*state_change)(struct sock *sk);
	void (*data_ready)(struct sock *sk,int bytes);
	void (*write_space)(struct sock *sk);
	void (*error_report)(struct sock *sk);
	int (*backlog_rcv) (struct sock *sk, struct sk_buff *skb);
	void (*destruct)(struct sock *sk);
};


struct inet_opt
{
	int ttl;                    // IP的TTL设置
	int tos;                    // IP的TOS设置
	unsigned cmsg_flags;        // 该标志用来决定是否向应用层打印相关信息,包括如下可能的值
	#define IP_CMSG_PKTINFO 1
	#define IP_CMSG_TTL 2
	#define IP_CMSG_TOS 4
	#define IP_CMSG_RECVOPTS 8
	#define IP_CMSG_RETOPTS 16
	struct ip_options *opt;     // IP选项,包括安全和处理限制、记录路径、时间戳、宽松的源站选路、严格的源站选路
	unsigned char hdrincl;      // 用于RAW
	__u8 mc_ttl;                // 多播TTL
	__u8 mc_loop;               // 多播回环
	unsigned recverr : 1,       // 是否允许传递扩展的可靠的错误信息.
	freebind : 1;               // 是否允许socket被绑定
	__u16 id;                   // 用于禁止分片的IP包的ID计数
	__u8 pmtudisc;              // 路径MTU发现
	int mc_index;               // 多播设备索引
	__u32 mc_addr;              // 自己的多播地址
	struct ip_mc_socklist *mc_list; // 多播组
};

struct tcp_opt {
	int tcp_header_len;         // tcp首部长度(包括选项)
	__u32 pred_flags; /* 首部预测标志,在syn_rcv、syn_sent、更新窗口或其他恰当的时候,设置pred_flags(主要
						是创建出不符合快速路径的条件,一般值为0x??10 << 16 + snd_wnd)?所对应的值不确定,
						在连接完毕之后,根据pred_flags以及其他因素来确定是否走快速路径。*/

	__u32 rcv_nxt;              // 期望接受到的下一个tcp包的seq
	__u32 snd_nxt;              // 要发送的下一个tcp包的seq
	__u32 snd_una;              // 表示最近一个尚未确认的但是已经发送过的报文的seq
	__u32 snd_sml;              // 最近发送的小包的最后一个字节数,主要用于Nagle算法
	__u32 rcv_tstamp;           // 最近收到的ACK的时间,用于保活
	__u32 lsndtime;             // 最近发送的数据包的时间,用于窗口restart

	/* 经受时延的确认的控制 */
	struct {
		__u8 pending;           /* 正处于ACK延时状态,包括如下几种状态 ACK is pending */
		enum tcp_ack_state_t
		{
			TCP_ACK_SCHED = 1,
			TCP_ACK_TIMER = 2,
			TCP_ACK_PUSHED= 4
		};
		__u8 quick;            /* 快速恢复算法时,用于决定是否需要重传的收到的重复ACK的最大数目 Scheduled number of quick acks */
		__u8 pingpong;         /* 当前该TCP会话处于交互状态(非延时ACK状态)The session is interactive */
		__u8 blocked;          /* 当前socket被锁住了,这时候延时的ACK不再等待,而是立即发送 Delayed ACK was blocked by socket lock*/]
		/*Adaptive Time-Out (ATO) is the time that must elapse before an acknowledgment is considered lost. RFC 2637*/
		__u32 ato;             /* 软件时钟的预测嘀嗒数目 Predicted tick of soft clock */
		unsigned long timeout; /* 当前延时确认的定时器时间 Currently scheduled timeout */
		__u32 lrcvtime;        /* 最后收到的数据报的时间戳 timestamp of last received data packet*/
		__u16 last_seg_size;   /* 最后收到的数据报的大小 Size of last incoming segment */

		/*
		1. tp->advmss:The MSS advertised by the host. This is initialised in the function tcp_advertise_mss, from the routing table's destination cache(dst->advmss).
Given that the cached entry is calculated from the MTU (maximum transfer unit) of the next hop, this will have a value of 1460 over Ethernet.

		2. tp->ack.rcv_mss:A lower-bound estimate of the peer's MSS. This is initiated in tcp_initialize_rcv mss, and updated whenever a segment is received by
tcp measure rcv mss.

		3. tp->mss_cache:The current effective sending MSS, which is calculated in the function tcp_sync_mss. When the socket is created, it is initialised to 536 by
tcp_v4_init_sock. Note that these are the only functions that alter the value of tp->mss cache.

		4. tp->mss clamp:An upper-bound value of the MSS of the connection. This is negotiated at connect(), such that it is the minimum of the MSS values advertised
by the two hosts.We will never see a segment larger than this.
*/
		__u16 rcv_mss;    /* 属于点到点的mss,用于延时确认 MSS used for delayed ACK decisions */
	} ack;

	__u16 mss_cache;      // 当前提供的有效mss, /* Cached effective mss, not including SACKS */
	__u16 mss_clamp;      // 最大mss,连接建立时协商的mss或者用户通过ioctl指定的mss的两者之中最大值
	/* Maximal mss, negotiated at connection setup */
	__u16 advmss;         /* MTU包括路径MTU,这里的advmss是本机告知周围网关的我自身的mss */

	/* 用于直接拷贝给应用层的数据,当用户正在读取该套接字时, TCP包将被排入套接字的预备队列(tcp_prequeue ()),将其
	传递到该用户线程上下文中进行处理. */
	struct {
		struct sk_buff_head prequeue; // 当前预备队列
		struct task_struct *task;     // 当前线程
		struct iovec *iov;            // 用户空间接受数据的地址
		int memory;                   // 当前预备队列中的包总字节数目
		int len;                      // 用户进程从预备队列中读取的数据字节数
	} ucopy;

	__u32 snd_wl1;        // 收到对方返回的skb,记下该包的seq号,用于判断窗口是否需要更新 /* Sequence for window update */
	__u32 snd_wnd;        // 记录对方提供的窗口大小 /* The window we expect to receive */
	__u32 max_window;     // 对方曾经提供的最大窗口 /* Maximal window ever seen from peer */
	__u32 pmtu_cookie;    // 将发送mss和当前的pmtu/exthdr设置同步 /* Last pmtu seen by socket */
	__u16 ext_header_len; // 网络层协议选项长度 /* Network protocol overhead (IP/IPv6 options) */
	__u8 ca_state;        // 快速重传状态机 /* State of fast-retransmit machine */
	enum tcp_ca_state
	{
		TCP_CA_Open = 0,
		TCP_CA_Disorder = 1,
		TCP_CA_CWR = 2,
		TCP_CA_Recovery = 3,
		TCP_CA_Loss = 4
	};
	/* RFC 1122指出,TCP实现必须包括Karn和Jacobson实现计算重传超时(retransmission timeout:RTO)的算法 */
	__u8 retransmits; // 某个还没有被确认的发送TCP包重传的次数 /* Number of unrecovered RTO timeouts. */

	/* 当收到下面数量的重复ack时,快速重传开始,而无需等待重传定时器超时 */
	__u8 reordering; /* Packet reordering metric. */

	/* 当我们发出一个tcp包之后,并不立刻释放掉该包,而是等待其对应的ack到来,如果这时候ack来了,那么我们将从
	write_queue队列中释放掉该包,同时将该事件的标志记录在tp->queue_shrunk中,如果原来进程由于write_queue中没
	有足够的空间继续发送数据而休眠的话,那么此时将会唤醒其对应的sock,从而进程可以继续发送数据 */
	__u8 queue_shrunk; /* Write queue has been shrunk recently.*/
	__u8 defer_accept; // 请参考附录1 /* User waits for some data after accept() */

	/* 往返时间测量 RTT,有关RTT的侧量这里不再详细讨论measurement :Round-Trip Time (RTT) is the estimated round-trip time for an Acknowledgment to be received for a
given transmitted packet. When the network link is a local network, this delay will be minimal (if not zero). When the network link is
the Internet, this delay could be substantial and vary widely. RTT is adaptive. */
	__u8 backoff;         /* backoff */
	__u32 srtt;           /* smothed round trip time << 3 */
	__u32 mdev;           /* medium deviation */
	__u32 mdev_max;       /* maximal mdev for the last rtt period */
	__u32 rttvar;         /* smoothed mdev_max */
	__u32 rtt_seq;        /* sequence number to update rttvar */
	__u32 rto;            /* 重传超时时间 retransmit timeout */

	__u32 packets_out;    /* 已经发出去的数目 Packets which are "in flight" */
	__u32 left_out;       /* 发出去已经被确认的数目 Packets which leaved network */
	__u32 retrans_out;    /* 重传的发出去的包数目 Retransmitted packets out */

	// 慢启动和拥塞控制 Slow start and congestion control (see also Nagle, and Karn & Partridge)
	__u32 snd_ssthresh;   // 拥塞控制时的慢启动门限 /* Slow start size threshold */
	__u32 snd_cwnd;       // 当前采用的拥塞窗口 /* Sending congestion window */
	__u16 snd_cwnd_cnt;   // 线形增加的拥塞窗口计数器 /* Linear increase counter */
	__u16 snd_cwnd_clamp; // 拥塞窗口的最大值(一般为对方通告的窗口大小) /* Do not allow snd_cwnd to grow above this */
	__u32 snd_cwnd_used;  // 慢启动,每发出去一个包,snd_cwnd_used++
	__u32 snd_cwnd_stamp; // 该参数可以保证在重传模式下不会改变拥塞窗口的大小 */

	/* 重传定时器和延时确认定时器 Two commonly used timers in both sender and receiver paths. */
	unsigned long timeout;// 用于重传
	struct timer_list retransmit_timer;     /* Resend (no ack) */
	struct timer_list delack_timer;         /* Ack delay */
	struct sk_buff_head out_of_order_queue; // 乱序的TCP报都存放在该队列中 /* Out of order segments go here */

	struct tcp_func *af_specific;           // ipv4/ipv6 相关特定处理函数 /* Operations which are AF_INET{4,6} specific */
	struct tcp_func ipv4_specific = {
		ip_queue_xmit,
		tcp_v4_send_check,
		tcp_v4_rebuild_header,
		tcp_v4_conn_request,
		tcp_v4_syn_recv_sock,
		tcp_v4_remember_stamp,
		sizeof(struct iphdr),

		ip_setsockopt,
		ip_getsockopt,
		v4_addr2sockaddr,
		sizeof(struct sockaddr_in)
	};
	struct sk_buff *send_head;  // 最先要发送的TCP报文 /* Front of stuff to transmit */
	struct page *sndmsg_page;   // sendmsg所使用的缓冲内存页面 /* Cached page for sendmsg */
	u32 sndmsg_off;             // sendmsg所使用的缓冲偏移 /* Cached offset for sendmsg */

	__u32 rcv_wnd;              // 当前接受窗口 /* Current receiver window */
	__u32 rcv_wup;              // 对方窗口最后一次更新时的rcv_nxt /* rcv_nxt on last window update sent */
	__u32 write_seq;            // tcp发送总数据字节量+1 /* Tail(+1) of data held in tcp send buffer */
	__u32 pushed_seq;           // 上次发送带PSH标志的TCP包的seq /* Last pushed seq, required to talk to windows */
	__u32 copied_seq;           // 尚未读取的数据第一个字节位置 /* Head of yet unread data */

	// Options received (usually on last packet, some only on SYN packets).
	char tstamp_ok,        /* syn包上的时间戳 TIMESTAMP seen on SYN packet */
	wscale_ok,             /* SACK选项处理Kind=5不再详细叙说 Wscale seen on SYN packet */
	sack_ok;               /* SACK选项处理Kind=5不再详细叙说 SACK seen on SYN packet */
	char saw_tstamp;       /* 最后一个TCP包的时间戳 Saw TIMESTAMP on last packet */
	__u8 snd_wscale;       /* 接受窗口扩大因子 Window scaling received from sender */
	__u8 rcv_wscale;       /* 发送窗口扩大因子 Window scaling to send to receiver */
	__u8 nonagle;          /* 是否允许Nagle算法 Disable Nagle algorithm? */
	__u8 keepalive_probes; /* 保活探测的数量 num of allowed keep alive probes */

	/* PAWS:防止回绕的序号,不再详细叙说 PAWS/RTTM data */
	__u32 rcv_tsval;       /* Time stamp value */
	__u32 rcv_tsecr;       /* Time stamp echo reply */
	__u32 ts_recent;       /* Time stamp to echo next */
	long ts_recent_stamp;  /* Time we stored ts_recent (for aging) */

	/* SACK选项处理Kind=5不再详细叙说 SACKs data1 */
	__u16 user_mss;        /* 用户通过ioctl指定的mssmss requested by user in ioctl */
	__u8 dsack;            /* D-SACK is scheduled */
	__u8 eff_sacks;        /* Size of SACK array to send with next packet */
	struct tcp_sack_block duplicate_sack[1]; /* D-SACK block */
	struct tcp_sack_block selective_acks[4]; /* The SACKS themselves*/

	/* 通告窗口(advertised window,tp->tcv_wnd),window_clamp是最大的通告窗口,说白了就是
	应用程序的缓冲区真实大小。rcv_ssthresh是更为严格的window_clamp,主要用于慢启动期间
	预测连接的行为 */
	__u32 window_clamp;   /* Maximal window to advertise */
	__u32 rcv_ssthresh;   /* Current window clamp */

	__u8 probes_out;      /* 用于零窗口探测 unanswered 0 window probes */
	__u8 num_sacks;       /* Number of SACK blocks */

	__u8 syn_retries;     /* syn重试次数 num of allowed syn retries */
	__u8 ecn_flags;       /* 显式拥塞通知状态位,不再详叙 ECN status bits. */
	__u16 prior_ssthresh; /* 在经过重传后恢复时的ssthresh保存值 ssthresh saved at recovery start */

	/* SACK选项处理Kind=5不再详细叙说 SACKs data2 */
	__u32 lost_out;       /* Lost packets */
	__u32 sacked_out;     /* SACK'd packets */
	__u32 fackets_out;    /* FACK'd packets */
	__u32 high_seq;       /* snd_nxt at onset of congestion */

	__u32 retrans_stamp;  // 上次重传的时间,其也会记住第一个syn的时间戳
	__u32 undo_marker;    /* 开始跟踪重传的标示符 tracking retrans started here. */
	int undo_retrans;     /* 用于Undo冗余的重传 number of undoable retransmissions. */
	__u32 urg_seq;        /* 紧急指针的seq Seq of received urgent pointer */
	__u16 urg_data;       /* 紧急指针的相关控制标志保存 Saved octet of OOB data and control flags */
	__u8 pending;         /* 确定定时器的事件 Scheduled timer event,包括如下四种情况 */
	#define TCP_TIME_RETRANS 1  /* Retransmit timer */
	#define TCP_TIME_DACK 2     /* Delayed ack timer */
	#define TCP_TIME_PROBE0 3   /* Zero window probe timer */
	#define TCP_TIME_KEEPOPEN 4 /* Keepalive timer */

	__u8 urg_mode;        /* 是否处于紧急模式 In urgent mode */
	__u32 snd_up;         /* 紧急指针位置 Urgent pointer */

	/* The syn_wait_lock is necessary only to avoid tcp_get_info having to grab the main lock sock while browsing the listening hash
	 * (otherwise it's deadlock prone). This lock is acquired in read mode only from tcp_get_info() and it's acquired in write mode _only_ from
	 * code that is actively changing the syn_wait_queue. All readers that are holding the master sock lock don't need to grab this lock in read
	 * mode too as the syn_wait_queue writes are always protected from the main sock lock.
	 */
	rwlock_t syn_wait_lock;
	struct tcp_listen_opt *listen_opt;

	/* 服务器段listening socket的已经建立的子socket FIFO队列 FIFO of established children */
	struct open_request *accept_queue;
	struct open_request *accept_queue_tail;

	int write_pending;             /* 是否有对socket的写请求 A write to socket waits to start. */
	unsigned int keepalive_time;   /* 保活定时器启动的时间阀值 time before keep alive takes place */
	unsigned int keepalive_intvl;  /* 保活探测时间间隔 time interval between keep alive probes */
	int linger2;                   // lingertime一起,指明了close()后保留的时间
	int frto_counter;              /* 开始重传后的新的ack数目 Number of new acks after RTO */
	__u32 frto_highmark;           /* 重传发生时的要发送的下一个tcp包的seq snd_nxt when RTO occurred */

	unsigned long last_synq_overflow; // 用于syn_cookie处理
};

/* 附录1: The first option we’ll consider is TCP_DEFER_ACCEPT. (This is what it’s called in Linux; other OSs offer the same option but use different names.) To understand the idea of the TCP_DEFER_ACCEPT option, it is necessary to picture a typical process of the HTTP client-server interaction. Consider how the TCP establishes a connection with the goal of transferring data. On a network, information travels in discrete units called IP packets (or IP datagrams). A packet always has a header that carries service information, used for internal protocol handling, and it may also carry payload data. A typical example of service information is a set of so-called flags, which mark the packets as having special meaning to a TCP/IP stack, such as acknowledgement of successful packet receiving. Often, it’s possible to carry payload in the “marked” packet, but sometimes, internal logic forces a TCP/IP stack to send out packets with just a header. These packets often introduce unwanted delays and increased overhead and result in overall performance degradation.

The server has now created a socket and is waiting for a connection. The connection procedure in TCP/IP is a so-called “three-way handshake.” First, a client sends a TCP packet with a SYN flag set and no payload (a SYN packet). The server replies by sending a packet with SYN/ACK flags set (a SYN/ACK packet) to acknowledge receipt of the initial packet. The client then sends an ACK packet to acknowledge receipt of the second packet and to finalize the connection procedure. After receiving the SYN/ACK, the packet server wakes up a receiver process while waiting for data. When the three-way handshake is completed, the client starts to send “useful” data to be transferred to the server. Usually, an HTTP request is quite small and fits into a single packet. But in this case, at least four packets will be sent in both directions, adding considerable delay times. Note also that the receiver has already been waiting for the information—since before the data was ever sent.

To alleviate these problems, Linux (along with some other OSs) includes a TCP_DEFER_ACCEPT option in its TCP implementation. Set on a server-side listening socket, it instructs the kernel not to wait for the final ACK packet and not to initiate the process until the first packet of real data has arrived. After sending the SYN/ACK, the server will then wait for a data packet from a client. Now, only three packets will be sent over the network, and the connection establishment delay will be significantly reduced, which is typical for HTTP.

This feature, called an “accept filter” , is used in different ways, although in all cases, the effect is the same as TCP_DEFER_ACCEPT—the server will not wait for the final ACK packet, waiting only for a packet carrying a payload. More information about this option and its significance for a high-performance Web server is available in the Apache documentation. */