kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

Linux TCP数据包接收处理tcp_rcv_established

http://www.cppblog.com/fwxjj/archive/2013/02/18/197906.aspx

tcp_rcv_established函数的工作原理是把数据包的处理分为2类:fast path和slow path,其含义显而易见。这样分类的目的当然是加快数据包的处理,因为在正常情况下,数据包是按顺序到达的,网络状况也是稳定的,这时可以按照fast path直接把数据包存放到receive queue了。而在其他的情况下则需要走slow path流程了。

在协议栈中,是用头部预测来实现的,每个tcp sock有个pred_flags成员,它就是判别的依据。

1
2
3
4
5
6
static inline void __tcp_fast_path_on(struct tcp_sock *tp, u32 snd_wnd)
{
	tp->pred_flags = htonl((tp->tcp_header_len << 26) |
				   ntohl(TCP_FLAG_ACK) |
				   snd_wnd);
}

可以看出头部预测依赖的是头部长度字段和通告窗口。也就是说标志位除了ACK和PSH外,如果其他的存在的话,就不能用

fast path处理,其揭示的含义如下:

1 Either the data transaction is taking place in only one direction (which means that we are the receiver and not transmitting any data) or in the case where we are sending out data also, the window advertised from the other end is constant. The latter means that we have not transmitted any data from our side for quite some time but are receiving data from the other end. The receive window advertised by the other end is constant.

  1. Other than PSH|ACK flags in the TCP header, no other flag is set (ACK is set for each TCP segment).
    This means that if any other flag is set such as URG, FIN, SYN, ECN, RST, and CWR, we know that something important is there to be attended and we need to move into the SLOW path.

  2. The header length has unchanged. If the TCP header length remains unchanged, we have not added/reduced any TCP option and we can safely assume that there is nothing important to be attended, if the above two conditions are TRUE.

fast path工作的条件
1
2
3
4
5
6
7
8
9
10
static inline void tcp_fast_path_check(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (skb_queue_empty(&tp->out_of_order_queue) &&
		tp->rcv_wnd &&
		atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf &&
		!tp->urg_data)
		tcp_fast_path_on(tp);
}

1 没有乱序数据包
2 接收窗口不为0
3 还有接收缓存空间
4 没有紧急数据

反之,则进入slow path处理;另外当连接新建立时处于slow path。

从fast path进入slow path的触发条件(进入slow path 后pred_flags清除为0):

1 在tcp_data_queue中接收到乱序数据包
2 在tcp_prune_queue中用完缓存并且开始丢弃数据包
3 在tcp_urgent_check中遇到紧急指针
4 在tcp_select_window中发送的通告窗口下降到0.

从slow_path进入fast_path的触发条件:

1 When we have read past an urgent byte in tcp_recvmsg() . Wehave gotten an urgent byte and we remain in the slow path mode until we receive the urgent byte because it is handled in the slow path in tcp_rcv_established().
2 当在tcp_data_queue中乱序队列由于gap被填充而处理完毕时,运行tcp_fast_path_check。
3 tcp_ack_update_window()中更新了通告窗口。

fast path处理流程

A 判断能否进入fast path

1
2
if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&
		TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {

TCP_HP_BITS的作用就是排除flag中的PSH标志位。只有在头部预测满足并且数据包以正确的顺序(该数据包的第一个序号就是下个要接收的序号)到达时才进入fast path。

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
int tcp_header_len = tp->tcp_header_len;

/* Timestamp header prediction: tcp_header_len
 * is automatically equal to th->doff*4 due to pred_flags
 * match.
 */

/* Check timestamp */
//相等说明tcp timestamp option被打开。
if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {
	/* No? Slow path! */
	//这里主要是parse timestamp选项,如果返回0则表明pase出错,此时我们进入slow_path
	if (!tcp_parse_aligned_timestamp(tp, th))
		goto slow_path;

	/* If PAWS failed, check it more carefully in slow path */
	//如果上面pase成功,则tp对应的rx_opt域已经被正确赋值,此时如果rcv_tsval(新的接收的数据段的时间戳)比ts_recent(对端发送过来的数据(也就是上一次)的最新的一个时间戳)小,则我们要进入slow path 处理paws。
	if ((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0)
		goto slow_path;

	/* DO NOT update ts_recent here, if checksum fails
	 * and timestamp was corrupted part, it will result
	 * in a hung connection since we will drop all
	 * future packets due to the PAWS test.
	 */
}

该代码段是依据时戳选项来检查PAWS(Protect Against Wrapped Sequence numbers)。 如果发送来的仅是一个TCP头的话(没有捎带数据或者接收端检测到有乱序数据这些情况时都会发送一个纯粹的ACK包)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* Bulk data transfer: sender */
if (len == tcp_header_len) {
	/* Predicted packet is in window by definition.
	 * seq == rcv_nxt and rcv_wup <= rcv_nxt.
	 * Hence, check seq<=rcv_wup reduces to:
	 */
	if (tcp_header_len ==
		(sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
		tp->rcv_nxt == tp->rcv_wup)
		tcp_store_ts_recent(tp);

	/* We know that such packets are checksummed
	 * on entry.
	 */
	tcp_ack(sk, skb, 0);
	__kfree_skb(skb);
	tcp_data_snd_check(sk);
	return 0;
} else { /* Header too small */
	TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
	goto discard;
}

主要的工作如下:
1 保存对方的最近时戳 tcp_store_ts_recent。通过前面的if判断可以看出tcp总是回显2次时戳回显直接最先到达的数据包的时戳,
rcv_wup只在发送数据(这时回显时戳)时重置为rcv_nxt,所以接收到前一次回显后第一个数据包后,rcv_nxt增加了,但是
rcv_wup没有更新,所以后面的数据包处理时不会调用该函数来保存时戳。
2 ACK处理。这个函数非常复杂,包含了拥塞控制机制,确认处理等等。
3 检查是否有数据待发送 tcp_data_snd_check。

如果该数据包中包含了数据的话

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
		} else {
			int eaten = 0;
			int copied_early = 0;
			/* 此数据包刚好是下一个读取的数据,并且用户空间可存放下该数据包*/
			if (tp->copied_seq == tp->rcv_nxt &&
				len - tcp_header_len <= tp->ucopy.len) {
#ifdef CONFIG_NET_DMA
				if (tcp_dma_try_early_copy(sk, skb, tcp_header_len)) {
					copied_early = 1;
					eaten = 1;
				}
#endif          /* 如果该函数在进程上下文中调用并且sock被用户占用的话*/
				if (tp->ucopy.task == current &&
					sock_owned_by_user(sk) && !copied_early) {
					/* 进程有可能被设置为TASK_INTERRUPTIBLE */
					__set_current_state(TASK_RUNNING);
					/* 直接copy数据到用户空间*/
					if (!tcp_copy_to_iovec(sk, skb, tcp_header_len))
						eaten = 1;
				}
				if (eaten) {
					/* Predicted packet is in window by definition.
					 * seq == rcv_nxt and rcv_wup <= rcv_nxt.
					 * Hence, check seq<=rcv_wup reduces to:
					 */
					if (tcp_header_len ==
						(sizeof(struct tcphdr) +
						 TCPOLEN_TSTAMP_ALIGNED) &&
						tp->rcv_nxt == tp->rcv_wup)
						tcp_store_ts_recent(tp);
					/* 更新RCV RTT,Dynamic Right-Sizing算法*/
					tcp_rcv_rtt_measure_ts(sk, skb);

					__skb_pull(skb, tcp_header_len);
					tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
					NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITSTOUSER);
				}
				if (copied_early)
					tcp_cleanup_rbuf(sk, skb->len);
			}
			if (!eaten) { /* 没有直接读到用户空间*/
				if (tcp_checksum_complete_user(sk, skb))
					goto csum_error;

				/* Predicted packet is in window by definition.
				 * seq == rcv_nxt and rcv_wup <= rcv_nxt.
				 * Hence, check seq<=rcv_wup reduces to:
				 */
				if (tcp_header_len ==
					(sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
					tp->rcv_nxt == tp->rcv_wup)
					tcp_store_ts_recent(tp);

				tcp_rcv_rtt_measure_ts(sk, skb);

				if ((int)skb->truesize > sk->sk_forward_alloc)
					goto step5;

				NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITS);

				/* Bulk data transfer: receiver */
				__skb_pull(skb, tcp_header_len);
				/* 进入receive queue 排队,以待tcp_recvmsg读取*/
				__skb_queue_tail(&sk->sk_receive_queue, skb);
				skb_set_owner_r(skb, sk);
				tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
			}
			/* 数据包接收后续处理*/
			tcp_event_data_recv(sk, skb);
			/* ACK 处理*/
			if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
				/* Well, only one small jumplet in fast path... */
				tcp_ack(sk, skb, FLAG_DATA);
				tcp_data_snd_check(sk);
				if (!inet_csk_ack_scheduled(sk))
					goto no_ack;
			}
			/* ACK发送处理*/
			if (!copied_early || tp->rcv_nxt != tp->rcv_wup)
				__tcp_ack_snd_check(sk, 0);
no_ack:
#ifdef CONFIG_NET_DMA
			if (copied_early)
				__skb_queue_tail(&sk->sk_async_wait_queue, skb);
			else
#endif
			/* eaten为1,表示数据直接copy到了用户空间,这时无需提醒用户进程数据的到达,否则需调用sk_data_ready来通知,因为此时数据到达了receive queue*/
			if (eaten)
				__kfree_skb(skb);
			else
				sk->sk_data_ready(sk, 0);
			return 0;
		}

tcp_event_data_recv函数

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
static void tcp_event_data_recv(struct sock *sk, struct sk_buff *skb)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);
	u32 now;
	/* 接收到了数据,设置ACK需调度标志*/
	inet_csk_schedule_ack(sk);

	tcp_measure_rcv_mss(sk, skb);

	tcp_rcv_rtt_measure(tp);

	now = tcp_time_stamp;
	/* 以下为根据接收间隔更新icsk_ack.ato,该值主要用于判断pingpong模式见函数tcp_event_data_sent */
	if (!icsk->icsk_ack.ato) {
		/* The _first_ data packet received, initialize
		 * delayed ACK engine.
		 */
		tcp_incr_quickack(sk);
		icsk->icsk_ack.ato = TCP_ATO_MIN;
	} else {
		int m = now - icsk->icsk_ack.lrcvtime;

		if (m <= TCP_ATO_MIN / 2) {
			/* The fastest case is the first. */
			icsk->icsk_ack.ato = (icsk->icsk_ack.ato >> 1) + TCP_ATO_MIN / 2;
		} else if (m < icsk->icsk_ack.ato) {
			icsk->icsk_ack.ato = (icsk->icsk_ack.ato >> 1) + m;
			if (icsk->icsk_ack.ato > icsk->icsk_rto)
				icsk->icsk_ack.ato = icsk->icsk_rto;
		} else if (m > icsk->icsk_rto) {
			/* Too long gap. Apparently sender failed to
			 * restart window, so that we send ACKs quickly.
			 */
			tcp_incr_quickack(sk);
			sk_mem_reclaim(sk);
		}
	}
	icsk->icsk_ack.lrcvtime = now;

	TCP_ECN_check_ce(tp, skb);
	/* 每次接收到来自对方的一个TCP数据报,且数据报长度大于128字节时,我们需要调用tcp_grow_window,增加rcv_ssthresh的值,一般每次为rcv_ssthresh增长两倍的mss,增加的条件是rcv_ssthresh小于window_clamp,并且 rcv_ssthresh小于接收缓存剩余空间的3/4,同时tcp_memory_pressure没有被置位(即接收缓存中的数据量没有太大)。 tcp_grow_window中对新收到的skb的长度还有一些限制,并不总是增长rcv_ssthresh的值*/
	if (skb->len >= 128)
		tcp_grow_window(sk, skb);
}

rcv_ssthresh是当前的接收窗口大小的一个阀值,其初始值就置为rcv_wnd。它跟rcv_wnd配合工作,当本地socket收到数据报,并满足一定条件时,增长rcv_ssthresh的值,在下一次发送数据报组建TCP首部时,需要通告对方当前的接收窗口大小,这时需要更新rcv_wnd,此时rcv_wnd的取值不能超过rcv_ssthresh的值。两者配合,达到一个滑动窗口大小缓慢增长的效果。

__tcp_ack_snd_check用来判断ACK的发送方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
 * Check if sending an ack is needed.
 */
static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible)
{
	struct tcp_sock *tp = tcp_sk(sk);

	/* More than one full frame received... */
	if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss
		 /* ... and right edge of window advances far enough.
		  * (tcp_recvmsg() will send ACK otherwise). Or...
		  */
		 && __tcp_select_window(sk) >= tp->rcv_wnd) ||
		/* We ACK each frame or... */
		tcp_in_quickack_mode(sk) ||
		/* We have out of order data. */
		(ofo_possible && skb_peek(&tp->out_of_order_queue))) {
		/* Then ack it now */
		tcp_send_ack(sk);
	} else {
		/* Else, send delayed ack. */
		tcp_send_delayed_ack(sk);
	}
}

这里有个疑问,就是当ucopy应用读到需要读取到的数据包后,也即在一次处理中

1
2
if (tp->copied_seq == tp->rcv_nxt &&
		len - tcp_header_len <= tp->ucopy.len) {

的第二个条件的等号为真 len - tcp_header_len == tp->ucopy.len,然后执行流程到后面eaten为1,所以函数以释放skb结束,没有调用sk_data_ready函数。假设这个处理调用流程如下:
tcp_recvmsg-> sk_wait_data -> sk_wait_event -> release_sock -> __release_sock-> sk_backlog_rcv-> tcp_rcv_established那么即使此时用户得到了所需的数据,但是在tcp_rcv_established返回前没有提示数据已得到,

1
2
3
4
5
6
7
8
9
10
11
#define sk_wait_event(__sk, __timeo, __condition)       /
	({  int __rc;                                       /
		release_sock(__sk);                             /
		__rc = __condition;                             /
		if (!__rc) {                                    /
			*(__timeo) = schedule_timeout(*(__timeo));  /
		}                                               /
		lock_sock(__sk);                                /
		__rc = __condition;                             /
		__rc;                                           /
	})

但是在回到sk_wait_event后,由于__condition为 !skb_queue_empty(&sk->sk_receive_queue),所以还是会调用schedule_timeout来等待。这点显然是浪费时间,所以这个condition应该考虑下这个数据已经读满的情况,而不能光靠观察receive queue来判断是否等待。

接下来分析slow path

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
slow_path:
	if (len < (th->doff << 2) || tcp_checksum_complete_user(sk, skb))
		goto csum_error;

	/*
	 *  Standard slow path.
	 */
	/* 检查到达的数据包 */
	res = tcp_validate_incoming(sk, skb, th, 1);
	if (res <= 0)
		return -res;

step5:  /* 如果设置了ACK,则调用tcp_ack处理,后面再分析该函数*/
	if (th->ack)
		tcp_ack(sk, skb, FLAG_SLOWPATH);

	tcp_rcv_rtt_measure_ts(sk, skb);

	/* Process urgent data. */
	tcp_urg(sk, skb, th);

	/* step 7: process the segment text */
	tcp_data_queue(sk, skb);

	tcp_data_snd_check(sk);
	tcp_ack_snd_check(sk);
	return 0;

先看看tcp_validate_incoming函数,在slow path处理前检查输入数据包的合法性。

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
/* Does PAWS and seqno based validation of an incoming segment, flags will
 * play significant role here.
 */
static int tcp_validate_incoming(struct sock *sk, struct sk_buff *skb,
				  struct tcphdr *th, int syn_inerr)
{
	struct tcp_sock *tp = tcp_sk(sk);

	/* RFC1323: H1. Apply PAWS check first. */
	if (tcp_fast_parse_options(skb, th, tp) && tp->rx_opt.saw_tstamp &&
		tcp_paws_discard(sk, skb)) {
		if (!th->rst) {
			NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSESTABREJECTED);
			tcp_send_dupack(sk, skb);
			goto discard;
		}
		/* Reset is accepted even if it did not pass PAWS. */
	}

	/* Step 1: check sequence number */
	if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
		/* RFC793, page 37: "In all states except SYN-SENT, all reset
		 * (RST) segments are validated by checking their SEQ-fields."
		 * And page 69: "If an incoming segment is not acceptable,
		 * an acknowledgment should be sent in reply (unless the RST
		 * bit is set, if so drop the segment and return)".
		 */
		if (!th->rst)
			tcp_send_dupack(sk, skb);
		goto discard;
	}

	/* Step 2: check RST bit */
	if (th->rst) {
		tcp_reset(sk);
		goto discard;
	}

	/* ts_recent update must be made after we are sure that the packet
	 * is in window.
	 */
	tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq);

	/* step 3: check security and precedence [ignored] */

	/* step 4: Check for a SYN in window. */
	if (th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
		if (syn_inerr)
			TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
		NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONSYN);
		tcp_reset(sk);
		return -1;
	}

	return 1;

discard:
	__kfree_skb(skb);
	return 0;
}

第一步:检查PAWS tcp_paws_discard

1
2
3
4
5
6
7
8
static inline int tcp_paws_discard(const struct sock *sk,
				   const struct sk_buff *skb)
{
	const struct tcp_sock *tp = tcp_sk(sk);
	return ((s32)(tp->rx_opt.ts_recent - tp->rx_opt.rcv_tsval) > TCP_PAWS_WINDOW &&
		get_seconds() < tp->rx_opt.ts_recent_stamp + TCP_PAWS_24DAYS &&
		!tcp_disordered_ack(sk, skb));
}

PAWS丢弃数据包要满足以下条件

1 The difference between the timestamp value obtained in the current segmentand last seen timestamp on the incoming TCP segment should be more than TCP_PAWS_WINDOW (= 1), which means that if the segment that was transmitted 1 clock tick before the segment that reached here earlier TCP seq should be acceptable. It may be because of reordering of the segments that the latter reached earlier. 2 the 24 days have not elapsed since last time timestamp was stored, 3 tcp_disordered_ack返回0.

以下转载自CU论坛http://linux.chinaunix.net/bbs/viewthread.php?tid=1130308

在实际进行PAWS预防时,Linux是通过如下代码调用来完成的

1
2
3
4
5
tcp_rcv_established
  |
  |-->tcp_paws_discard
        |
        |-->tcp_disordered_ack

其中关键是local方通过tcp_disordered_ack函数对一个刚收到的数据分段进行判断,下面我们对该函数的判断逻辑进行下总结:
大前提:该收到分段的TS值表明有回绕现象发生
a)若该分段不是一个纯ACK,则丢弃。因为显然这个分段所携带的数据是一个老数据了,不是local方目前希望接收的(参见PAWS的处理依据一节)
b)若该分段不是local所希望接收的,则丢弃。这个原因很显然
c)若该分段是一个纯ACK,但该ACK并不是一个重复ACK(由local方后续数据正确到达所引发的),则丢弃。因为显然该ACK是一个老的ACK,并不是由于为了加快local方重发而在每收到一个丢失分段后的分段而发出的ACK。
d)若该分段是一个ACK,且为重复ACK,并且该ACK的TS值超过了local方那个丢失分段后的重发rto,则丢弃。因为显然此时local方已经重发了那个导致此重复ACK产生的分段,因此再收到此重复ACK就可以直接丢弃。
e)若该分段是一个ACK,且为重复ACK,但是没有超过一个rto的时间,则不能丢弃,因为这正代表peer方收到了local方发出的丢失分段后的分段,local方要对此ACK进行处理(例如立刻重传)

这里有一个重要概念需要理解,即在出现TS问题后,纯ACK和带ACK的数据分段二者是显著不同的,对于后者,可以立刻丢弃掉,因为从一个窗口的某个seq到下一个窗口的同一个seq过程中,一定有窗口变化曾经发生过,从而TS记录值ts_recent也一定更新过,此时一定可以通过PAWS进行丢弃处理。但是对于前者,一个纯ACK,就不能简单丢弃了,因为有这样一个现象是合理的,即假定local方的接收缓存很大,并且peer方在发送时很快就回绕了,于是在local方的某个分段丢失后,peer方需要在每收到的后续分段时发送重复ACK,而此时该重发ACK的ack_seq就是这个丢失分段的序号,而该重发ACK的seq已经是回绕后的重复序号了,尽管此时到底是回绕后的那个重复ACK还是之前的那个同样序号seq的重复ACK,对于local方来都需要处理(立刻启动重发动作),而不能简单丢弃掉。


第2步 检查数据包的序号是否正确,该判断失败后调用tcp_send_dupack发送一个duplicate acknowledge(未设置RST标志位时)。

1
2
3
4
5
static inline int tcp_sequence(struct tcp_sock *tp, u32 seq, u32 end_seq)
{
	return  !before(end_seq, tp->rcv_wup) &&
		!after(seq, tp->rcv_nxt + tcp_receive_window(tp));
}

由rcv_wup的更新时机(发送ACK时的tcp_select_window)可知位于序号rcv_wup前面的数据都已确认,所以待检查数据包的结束序号至少要大于该值;同时开始序号要落在接收窗口内。

第3步 如果设置了RST,则调用tcp_reset处理

第4步 更新ts_recent,

第5步 检查SYN,因为重发的SYN和原来的SYN之间不会发送数据,所以这2个SYN的序号是相同的,如果不满足则reset连接。

skb 申请释放

http://book.51cto.com/art/201206/345040.htm


一、SKB的缓存池

网络模块中,有两个用来分配SKB描述符的高速缓存,在SKB模块初始函数skb_init()中被创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2048 void __init skb_init(void)  
2049 {  
2050     skbuff_head_cache = kmem_cache_create("skbuff_head_cache",  
2051                           sizeof(struct sk_buff),  
2052                           0,  
2053                           SLAB_HWCACHE_ALIGN|SLAB_PANIC,  
2054                           NULL, NULL);  
2055     skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache",  
2056                         (2*sizeof(struct sk_buff)) +  
2057                         sizeof(atomic_t),  
2058                         0,  
2059                         SLAB_HWCACHE_ALIGN|SLAB_PANIC,  
2060                         NULL, NULL);  
2061 }

2050-2054 创建skbuff_head_cache高速缓存,一般情况下,SKB都是从该高速缓存中分配的。

2055-2060 创建每次以两倍SKB描述符长度来分配空间的skbuff_fclone_cache高速缓存。如果在分配SKB时就知道可能被克隆,那么应该从这个高速缓存中分配空间,因为在这个高速缓存中分配SKB时,会同时分配一个后备的SKB,以便将来用于克隆,这样在克隆时就不用再次分配SKB了,直接使用后备的SKB即可,这样做的目的主要是提高效率。

两个高速缓存的区别在于创建时指定的单位内存区域大小不同,skbuff_head_cache的单位内存区域长度是sizeof(struct sk_buff),而skbuff_fclone_cache的单位内存区域长度是2*sizeof(struct sk_buff)+sizeof(atomic_t),即一对SKB和一个引用计数,可以说这一对SKB是"父子"关系,指向同一个数据缓存区,引用计数值为0,1或2,用来表示这一对SKB中有几个已被使用,如图3-12所示。


二、分配SKB

1. alloc_skb()

alloc_skb()用来分配SKB。数据缓存区和SKB描述符是两个不同的实体,这就意味着,在分配一个SKB时,需要分配两块内存,一块是数据缓存区,一块是SKB描述符。__alloc_skb()调用kmem_cache_alloc_node()从高速缓存中获取一个sk_buff结构的空间,然后调用kmalloc_node_track_caller()分配数据缓存区。参数说明如下:

size,待分配SKB的线性存储区的长度。

gfp_mask,分配内存的方式,见表25-3。

fclone,预测是否会克隆,用于确定从哪个高速缓存中分配。

node,当支持NUMA(非均匀质存储结构)时,用于确定何种区域中分配SKB。NUMA参见相关资料。

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
144 struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,  
145                 int fclone, int node)  
146 {  
147     struct kmem_cache *cache;  
148     struct skb_shared_info *shinfo;  
149     struct sk_buff *skb;  
150     u8 *data;  
151  
152     cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;  
153  
154     /* Get the HEAD */  
155     skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);  
156     if (!skb)  
157         goto out;  
158  
159     /* Get the DATA. Size must match skb_add_mtu(). */  
160     size = SKB_DATA_ALIGN(size);  
161     data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info),  
162             gfp_mask, node);  
163     if (!data)  
164         goto nodata;  
165  
166     memset(skb, 0, offsetof(struct sk_buff, truesize));  
167     skb->truesize = size + sizeof(struct sk_buff);  
168     atomic_set(&skb->users, 1);  
169     skb->head = data;  
170     skb->datadata = data;  
171     skb->tail = data;  
172     skb->end  = data + size;  
173     /* make sure we initialize shinfo sequentially */  
174     shinfo = skb_shinfo(skb);  
175     atomic_set(&shinfo->dataref, 1);  
176     shinfo->nr_frags  = 0;  
177     shinfo->gso_size = 0;  
178     shinfo->gso_segs = 0;  
179     shinfo->gso_type = 0;  
180     shinfo->ip6_frag_id = 0;  
181     shinfo->frag_list = NULL;  
182  
183     if (fclone) {  
184         struct sk_buff *child = skb + 1;  
185         atomic_t *fclone_ref = (atomic_t *) (child + 1);  
186  
187         skb->fclone = SKB_FCLONE_ORIG;  
188         atomic_set(fclone_ref, 1);  
189  
190         child->fclone = SKB_FCLONE_UNAVAILABLE;  
191     }  
192 out:  
193     return skb;  
194 nodata:  
195     kmem_cache_free(cache, skb);  
196     skb = NULL;  
197     goto out;  
198 }

152 根据参数fclone确定从哪个高速缓存中分配SKB。

155 调用kmem_cache_alloc_node()从选定的高速缓存中分配一个SKB。在此从分配标志中去除GFP_DMA,是为了不从DMA内存区域中分配SKB描述符,因为DMA内存区域比较小且有特定用途,没有必要用来分配SKB描述符。而后面分配数据缓存区时,就不会去掉GFP_DMA标志,因为很有可能数据缓存区就需要在DMA内存区域中分配,这样硬件可以直接进行DMA操作,参见161~162行。

160 在分配数据缓存区之前,强制对给定的数据缓存区大小size作对齐操作。

161-165 调用kmalloc_node_track_caller()分配数据缓存区,其长度为size和sizeof(struct skb_shared_info)之和,因为在缓存区尾部紧跟着一个skb_shared_info结构。

168-181 初始化新分配SKB描述符和skb_shared_info结构。

183-191 如果是skbuff_fclone_cache高速缓存中分配SKB描述符,则还需置父SKB描述符的fclone为SKB_FCLONE_ORIG,表示可以被克隆;同时将子SKB描述符的fclone成员置为SKB_FCLONE_UNAVAILABLE,表示该SKB还没有被创建出来;最后将引用计数置为1。

最后SKB结构如图3-13所示,在图右边所示的内存块中部,可以看到对齐操作所带来的填充区域。需要说明的是,__alloc_skb()一般不被直接调用,而是被封装函数调用,如__netdev_alloc_skb()、alloc_skb()、alloc_skb_fclone()等函数。

2. dev_alloc_skb()

dev_alloc_skb()也是一个缓存区分配函数,通常被设备驱动用在中断上下文中。这是一个alloc_skb()的封装函数,因为是在中断处理函数中被调用的,因此要求原子操作(GFP_ATOMIC)。

1
2
3
4
5
6
7
8
9
10
11
12
13
1124 static inline struct sk_buff *dev_alloc_skb(unsigned int length)  
1125 {  
1126     return __dev_alloc_skb(length, GFP_ATOMIC);  
1127 }  
... ...  
1103 static inline struct sk_buff *__dev_alloc_skb(unsigned int length,  
1104                           gfp_t gfp_mask)  
1105 {  
1106     struct sk_buff *skb = alloc_skb(length + NET_SKB_PAD, gfp_mask);  
1107     if (likely(skb))  
1108         skb_reserve(skb, NET_SKB_PAD);  
1109     return skb;  
1110 }

1108 调用skb_reserve()在skb->head与skb->data之间预留NET_SKB_PAD个字节。NET_SKB_PAD的定义在skbuff.h中,其值为 16。这部分空间将被填入硬件帧头,如14B的以太网帧头。

1126 以GFP_ATOMIC为内存分配优先级,表示分配过程为原子操作,不能被中断。


三、释放SKB

dev_kfree_skb()和kfree_skb()用来释放SKB,把它返回给高速缓存。kfree_skb()可以直接调用,也可以通过封装函数dev_kfree_skb()来调用。而dev_kfree_skb()只是一个简单调用kfree_skb()的宏,一般为设备驱动使用,与之功能相反的函数是dev_alloc_skb()。这些函数只在skb->users为1的情况下才释放内存,否则只简单地递减skb->users,因此假设SKB有三个引用者,那么只有第三次调用dev_kfree_skb()或kfree_skb()时才释放内存。kfree_skb()的流程如图3-14所示。

图3-14所示的流程显示了释放一个SKB的步骤:

1)kfree_skb()检测sk_buff结构的引用计数users,如果不为1,则说明此次释放后该SKB还将被用户占用,因此递减引用计数users后即返回;否则说明不再有其他用户占用该sk_buff结构,调用__kfree_skb()释放之。

2)SKB描述符中包含一个dst_entry结构的引用,在释放SKB后,会调用dst_release()来递减dst_entry结构的引用计数。

3)如果初始化了SKB的析构函数,则调用相应的函数。

4)一个SKB描述符是与一个存有真正数据的内存块,即数据区相关的。如果存在聚合分散I/O数据,该数据区底部的skb_shared_info结构还会包含指向聚合分散I/O数据的指针,同样需要释放这些分片所占用的内存。最后需把SKB描述符所占内存返回给skbuff_head_cache缓存。释放内存由kfree_skbmem()处理,过程如下:

如果SKB没有被克隆,或者payload没有被单独引用,则释放SKB的数据缓存区,包括存储聚合分散I/O数据的缓存区和SKB描述符。

如果是释放从skbuff_fclone_cache中分配的父SKB描述符,且克隆计数为1,则释放父SKB描述符。

如果是释放从skbuff_fclone_cache中分配的子SKB描述符,设置父SKLB的fclone字段为SKB_FCLONE_UNAVAILABLE,在克隆计数为1的情况下,释放子SKB描述符。


四、数据预留和对齐

数据预留和对齐主要由skb_reserve()、skb_put()、skb_push()以及skb_pull()这几个函数来完成。

1. skb_reserve()

skb_reserve()在数据缓存区头部预留一定的空间,通常被用来在数据缓存区中插入协议首部或者在某个边界上对齐。它并没有把数据移出或移入数据缓存区,而只是简单地更新了数据缓存区的两个指针-分别指向负载起始和结尾的data和tail指针,图3-15 展示了调用skb_reserve()前后这两个指针的变化。

请注意:skb_reserve()只能用于空的SKB,通常会在分配SKB之后就调用该函数,此时data和tail指针还一同指向数据区的起始位置,如图3-15a所示。例如,某个以太网设备驱动的接收函数,在分配SKB之后,向数据缓存区填充数据之前,会有这样的一条语句skb_reserve(skb, 2),这是因为以太网头长度为14B,再加上2B就正好16字节边界对齐,所以大多数以太网设备都会在数据包之前保留2B。

当SKB在协议栈中向下传递时,每一层协议都把skb->data指针向上移动,然后复制本层首部,同时更新skb->len。这些操作都使用图3-15 中所示的函数完成。

2.skb_push()

skb_push()在数据缓存区的前头加入一块数据,与skb_reserve()类似,也并没有真正向数据缓存区中添加数据,而只是移动数据缓存区的头指针data和尾指针tail。数据由其他函数复制到数据缓存区中。

函数执行步骤如下:

1)当TCP发送数据时,会根据一些条件,如TCP最大分段长度MSS、是否支持聚合分散I/O等,分配一个SKB。

2)TCP需在数据缓存区的头部预留足够的空间,用来填充各层首部。MAX_TCP_HEADER是各层首部长度的总和,它考虑了最坏的情况:由于TCP层不知道将要用哪个接口发送包,它为每一层预留了最大的首部长度,甚至还考虑了出现多个IP首部的可能性,因为在内核编译支持IP over IP的情况下,会遇到多个IP首部。

3)把TCP负载复制到数据缓存区。需要注意的是,图3-16 只是一个例子,TCP负载可能会被组织成其他形式,例如分片,在后续章节中将会看到一个分片的数据缓存区是什么样的。

4)TCP层添加TCP首部。

5)SKB传递到IP层,IP层为数据包添加IP首部。

6)SKB传递到链路层,链路层为数据包添加链路层首部。

3.skb_put()

skb_put()修改指向数据区末尾的指针tail,使之往下移len字节,即使数据区向下扩大len字节,并更新数据区长度len。调用skb_put()前后,SKB结构变化如图3-17所示。

4.skb_pull()

skb_pull()通过将data指针往下移动,在数据区首部忽略len字节长度的数据,通常用于接收到数据包后在各层间由下往上传递时,上层忽略下层的首部。调用skb_pull()前后,SKB结构变化如图3-18所示。


五、克隆和复制SKB

1.skb_clone()

如果一个SKB会被不同的用户独立操作,而这些用户可能只是修改SKB描述符中的某些字段值,如h、nh,则内核没有必要为每个用户复制一份完整的SKB描述及其相应的数据缓存区,而会为了提高性能,只作克隆操作。克隆过程只复制SKB描述符,同时增加数据缓存区的引用计数,以免共享数据被提前释放。完成这些功能的是skb_clone()。一个使用包克隆的场景是,一个接收包程序要把该包传递给多个接收者,例如包处理函数或者一个或多个网络模块。原始的及克隆的SKB描述符的cloned值都会被设置为1,克隆SKB描述符的users值置为1,这样在第一次释放时就会释放掉。同时将数据缓存区引用计数dataref递增1,因为又多了一个克隆SKB描述符指向它。
图3-19 演示的是已克隆的SKB。

图3-19 所示是一个存在聚合分散I/O缓存区的例子,这个数据缓存区的一些数据保存在分片结构数组frags中。skb_share_check()用来检查SKB引用计数users,如果该字段表明SKB是被共享的,则克隆一个新的SKB。一个SKB被克隆后,该SKB数据缓存区中的内容就不能再被修改,这也意味着访问数据的函数没有必要加锁。skb_cloned()可以用来测试skb的克隆状态。

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
432 struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)  
433 {  
434     struct sk_buff *n;  
435  
436     n = skb + 1;  
437     if (skb->fclone == SKB_FCLONE_ORIG &&  
438         n->fclone == SKB_FCLONE_UNAVAILABLE) {  
439         atomic_t *fclone_ref = (atomic_t *) (n + 1);  
440         n->fclone = SKB_FCLONE_CLONE;  
441         atomic_inc(fclone_ref);  
442     } else {  
443         n = kmem_cache_alloc(skbuff_head_cache, gfp_mask);  
444         if (!n)  
445             return NULL;  
446         n->fclone = SKB_FCLONE_UNAVAILABLE;  
447     }  
448  
449 #define C(x) n->x = skb->x  
450  
451     n->nnext = n->prev = NULL;  
452     n->sk = NULL;  
453     C(tstamp);  
454     C(dev);  
455     C(h);  
456     C(nh);  
457     C(mac);  
458     C(dst);  
459     dst_clone(skb->dst);  
460     C(sp);  
461 #ifdef CONFIG_INET  
462     secpath_get(skb->sp);  
463 #endif  
464     memcpy(n->cb, skb->cb, sizeof(skb->cb));  
465     C(len);  
466     C(data_len);  
467     C(csum);  
468     C(local_df);  
469     n->cloned = 1;  
470     n->nohdr = 0;  
471     C(pkt_type);  
472     C(ip_summed);  
473     C(priority);  
474 #if defined(CONFIG_IP_VS) || defined(CONFIG_IP_VS_MODULE)  
475     C(ipvs_property);  
476 #endif  
477     C(protocol);  
478     n->destructor = NULL;  
479     C(mark);  
480 #ifdef CONFIG_NETFILTER  
481     C(nfct);  
482     nf_conntrack_get(skb->nfct);  
483     C(nfctinfo);  
484 #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)  
485     C(nfct_reasm);  
486     nf_conntrack_get_reasm(skb->nfct_reasm);  
487 #endif  
488 #ifdef CONFIG_BRIDGE_NETFILTER  
489     C(nf_bridge);  
490     nf_bridge_get(skb->nf_bridge);  
491 #endif  
492 #endif /*CONFIG_NETFILTER*/  
493 #ifdef CONFIG_NET_SCHED  
494     C(tc_index);  
495 #ifdef CONFIG_NET_CLS_ACT  
496     n->tc_verd = SET_TC_VERD(skb->tc_verd,0);  
497     n->tc_verd = CLR_TC_OK2MUNGE(n->tc_verd);  
498     n->tc_verd = CLR_TC_MUNGED(n->tc_verd);  
499     C(input_dev);  
500 #endif  
501     skb_copy_secmark(n, skb);  
502 #endif  
503     C(truesize);  
504     atomic_set(&n->users, 1);  
505     C(head);  
506     C(data);  
507     C(tail);  
508     C(end);  
509  
510     atomic_inc(&(skb_shinfo(skb)->dataref));  
511     skb->cloned = 1;  
512  
513     return n;  
514 }

436-438 由fclone标志来决定从哪个缓冲池中分配SKB描述符。如果紧邻的两个父子SKB描述符,前一个的fclone为SKB_FCLONE_ORIG,后一个的fclone为SKB_FCLONE_ UNAVAILABLE,则说明这两个SKB描述符是从skbuff_fclone_cache缓冲池中分配的,且父SKB描述符还没有被克隆,即子SKB描述符还是空的。否则即从skbuff_head_cache缓冲池中分配一个新的SKB来用于克隆。

451-508 将父SKB描述符各字段值赋给子SKB描述符的对应字段。

504 设置子SKB描述符引用计数users为1。

510 递增父SKB描述符中的数据区引用计数skb_shared_info结构的dataref。

511 设置父SKB描述符的成员cloned为1,表示该SKB已被克隆。

2.pskb_copy()

当一个函数不仅要修改SKB描述符,而且还要修改数据缓存区中的数据时,就需要同时复制数据缓存区。在这种情况下,程序员有两个选择。如果所修改的数据在skb->head和skb->end之间,可使用pskb_copy()来复制这部分数据,如图3-20所示。

3.skb_copy()

如果同时需要修改聚合分散I/O存储区中的数据,就必须使用skb_copy(),如图3-21所示。从前面的章节中看到,skb_shared_info结构中也包含一个SKB链表frag_list。该链表在pskb_copy()和skb_copy()中的处理方式与frags数组处理方式相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
587 struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask)  
588 {  
589     int headerlen = skb->data - skb->head;  
590     /*  
591      *    Allocate the copy buffer  
592      */  
593     struct sk_buff *n = alloc_skb(skb->end - skb->head + skb->data_len,  
594                       gfp_mask);  
595     if (!n)  
596         return NULL;  
597  
598     /* Set the data pointer */  
599     skb_reserve(n, headerlen);  
600     /* Set the tail pointer and length */  
601     skb_put(n, skb->len);  
602     n->csum         = skb->csum;  
603     n->ip_summed = skb->ip_summed;  
604  
605     if (skb_copy_bits(skb, -headerlen, n->head, headerlen + skb->len))  
606         BUG();  
607  
608     copy_skb_header(n, skb);  
609     return n;  
610 }

589-599 分配一个新的SKB,即包括SKB描述符和数据缓存区,然后在指针head和data之间预留源数据缓存区headroom长度的空间。

601 将新SKB的tail指针和数据区长度len设置为与源SKB的一样。

605-608 复制数据。

在讨论本书中不同主题时,有时会强调某个特定函数需要克隆或者复制一个SKB。在决定克隆或复制SKB时,各子系统程序员不能预测其他内核组件是否需要使用SKB中的原始数据。内核是模块化的,其状态变化是不可预测的,每个子系统都不知道其他子系统是如何操作数据缓存区的。因此,内核程序员需要记录各子系统对数据缓存区的修改,并且在修改数据缓存区前,复制一个新的数据缓存区,以免其他子系统需使用数据缓存区原始数据时出现错误。

内核网络设备的注册与初始化(eth0...)

找到eth0…之类的设备的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# crash vmlinux

p init_net
找到:
  dev_base_head = {
	next = 0xffff88003e48b070, 
	prev = 0xffff880037582070
  },
next 就是 struct net_device *dev; 中 dev->dev_list;
算算 dev_list 在 dev 中的偏移为0x50(可能会不同)

struct net_device 0xffff88003e48b020
然后根据 dev中的dev_list.next取下一个net_device
  dev_list = {
	next = 0xffff880037582070, 
	prev = 0xffffffff81b185b0
  },

找到net_device对应的XXX_adapter, 如ixgbe_adapter

ixgbe模块在申请net_device时会把需要预留给ixgbe_adapter的空间大小传给alloc_etherdev

1
2
3
4
5
6
7
8
netdev = alloc_etherdev(sizeof(struct ixgbe_adapter));

adapter = netdev_priv(netdev);

static inline void *netdev_priv(const struct net_device *dev)
{
	return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN);
}

所以ixgbe_adapter在net_device结构按32位对其后面,偏移0x6c0(视内核而定)


http://blog.csdn.net/sfrysh/article/details/5736752

首先来看如何分配内存给一个网络设备。

内核通过alloc_netdev来分配内存给一个指定的网络设备:

1
2
3
4
5
#define alloc_netdev(sizeof_priv, name, setup) /   
	alloc_netdev_mq(sizeof_priv, name, setup, 1)   
  
struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,   
		void (*setup)(struct net_device *), unsigned int queue_count)  

其中alloc_netdev_mq中的第一个元素是每个网络设备的私有数据(主要是包含一些硬件参数,比如中断之类的)的大小,也就是net_device结构中的priv的大小。第二个参数是设备名,我们传递进来一般都是一个待format的字符串,比如"eth%d",到时多个相同类型网卡设备就会依次为eth0,1(内核会通过dev_alloc_name来进行设置)… 第三个参数setup是一个初始化net_device结构的回调函数。

可是一般我们不需要直接调用alloc_netdev的,内核提供了一些包装好的函数:

这里我们只看alloc_etherdev:

1
2
3
4
5
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)   
struct net_device *alloc_etherdev_mq(int sizeof_priv, unsigned int queue_count)   
{   
	return alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count);   
}  

这里实际是根据网卡的类型进行包装,也就类似于oo中的基类,ether_setup初始化一些所有相同类型的网络设备的一些相同配置的域:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void ether_setup(struct net_device *dev)   
{   
	dev->header_ops      = ð_header_ops;   
  
	dev->change_mtu      = eth_change_mtu;   
	dev->set_mac_address     = eth_mac_addr;   
	dev->validate_addr   = eth_validate_addr;   
  
	dev->type        = ARPHRD_ETHER;   
	dev->hard_header_len     = ETH_HLEN;   
	dev->mtu     = ETH_DATA_LEN;   
	dev->addr_len        = ETH_ALEN;   
	dev->tx_queue_len    = 1000; /* Ethernet wants good queues */  
	dev->flags       = IFF_BROADCAST|IFF_MULTICAST;   
  
	memset(dev->broadcast, 0xFF, ETH_ALEN);   
  
}  

接下来我们来看注册网络设备的一些细节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int register_netdev(struct net_device *dev)   
{   
	int err;   
  
	rtnl_lock();   
  
	/*  
	 * If the name is a format string the caller wants us to do a  
	 * name allocation.  
	 */  
	if (strchr(dev->name, '%')) {   
		// 这里通过dev_alloc_name函数来对设备名进行设置。   
		err = dev_alloc_name(dev, dev->name);   
		if (err < 0)   
			goto out;   
	}   
	// 注册当前的网络设备到全局的网络设备链表中.下面会详细看这个函数.   
	err = register_netdevice(dev);   
out:   
	rtnl_unlock();   
	return err;   
}  

整个网络设备就是一个链表,他需要很方便的遍历所有设备,以及很快的定位某个指定的设备。为此net_device包含了下面3个链表(有关内核中数据结构的介绍,可以去自己google下):

1
2
3
4
5
6
// 可以根据index来定位设备   
struct hlist_node   index_hlist;   
// 可以根据name来定位设备   
struct hlist_node   name_hlist;   
// 通过dev_list,将此设备插入到全局的dev_base_head中,我们下面会介绍这个。   
struct list_head    dev_list;  

当设备注册成功后,还需要通知内核的其他组件,这里通过netdev_chain类型的notifier chain来通知其他组件。事件是NETDEV_REGISTER..其他设备通过register_netdevice_notifier来注册自己感兴趣的事件到此notifier chain上。

网络设备(比如打开或关闭一个设备),与用户空间的通信通过rtmsg_ifinfo函数,也就是RTMGRP_LINK的netlink。

每个设备还包含两个状态,一个是state字段,表示排队策略状态(用位图表示),一个是注册状态。

包的排队策略也就是qos了。。

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
int register_netdevice(struct net_device *dev)   
{   
	struct hlist_head *head;   
	struct hlist_node *p;   
	int ret;   
	struct net *net;   
  
	BUG_ON(dev_boot_phase);   
	ASSERT_RTNL();   
  
	might_sleep();   
  
	/* When net_device's are persistent, this will be fatal. */  
	BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);   
	BUG_ON(!dev_net(dev));   
	net = dev_net(dev);   
  
	// 初始化相关的锁   
	spin_lock_init(&dev->addr_list_lock);   
	netdev_set_addr_lockdep_class(dev);   
	netdev_init_queue_locks(dev);   
  
	dev->iflink = -1;   
  
	/* Init, if this function is available */  
	if (dev->init) {   
		ret = dev->init(dev);   
		if (ret) {   
			if (ret > 0)   
				ret = -EIO;   
			goto out;   
		}   
	}   
  
	if (!dev_valid_name(dev->name)) {   
		ret = -EINVAL;   
		goto err_uninit;   
	}   
	// 给设备分配一个唯一的identifier.   
	dev->ifindex = dev_new_index(net);   
	if (dev->iflink == -1)   
		dev->iflink = dev->ifindex;   
  
	// 在全局的链表中检测是否有重复的名字   
	head = dev_name_hash(net, dev->name);   
	hlist_for_each(p, head) {   
		struct net_device *d   
			= hlist_entry(p, struct net_device, name_hlist);   
		if (!strncmp(d->name, dev->name, IFNAMSIZ)) {   
			ret = -EEXIST;   
			goto err_uninit;   
		}   
	}   
	// 下面是检测一些特性的组合是否合法。   
	/* Fix illegal checksum combinations */  
	if ((dev->features & NETIF_F_HW_CSUM) &&   
		(dev->features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {   
		printk(KERN_NOTICE "%s: mixed HW and IP checksum settings./n",   
			   dev->name);   
		dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM);   
	}   
  
	if ((dev->features & NETIF_F_NO_CSUM) &&   
		(dev->features & (NETIF_F_HW_CSUM|NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {   
		printk(KERN_NOTICE "%s: mixed no checksumming and other settings./n",   
			   dev->name);   
		dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM|NETIF_F_HW_CSUM);   
	}   
  
  
	/* Fix illegal SG+CSUM combinations. */  
	if ((dev->features & NETIF_F_SG) &&   
		!(dev->features & NETIF_F_ALL_CSUM)) {   
		printk(KERN_NOTICE "%s: Dropping NETIF_F_SG since no checksum feature./n",   
			   dev->name);   
		dev->features &= ~NETIF_F_SG;   
	}   
  
	/* TSO requires that SG is present as well. */  
	if ((dev->features & NETIF_F_TSO) &&   
		!(dev->features & NETIF_F_SG)) {   
		printk(KERN_NOTICE "%s: Dropping NETIF_F_TSO since no SG feature./n",   
			   dev->name);   
		dev->features &= ~NETIF_F_TSO;   
	}   
	if (dev->features & NETIF_F_UFO) {   
		if (!(dev->features & NETIF_F_HW_CSUM)) {   
			printk(KERN_ERR "%s: Dropping NETIF_F_UFO since no "  
					"NETIF_F_HW_CSUM feature./n",   
							dev->name);   
			dev->features &= ~NETIF_F_UFO;   
		}   
		if (!(dev->features & NETIF_F_SG)) {   
			printk(KERN_ERR "%s: Dropping NETIF_F_UFO since no "  
					"NETIF_F_SG feature./n",   
					dev->name);   
			dev->features &= ~NETIF_F_UFO;   
		}   
	}   
  
	/* Enable software GSO if SG is supported. */  
	if (dev->features & NETIF_F_SG)   
		dev->features |= NETIF_F_GSO;   
  
	// 初始化设备驱动的kobject并创建相关的sysfs   
	netdev_initialize_kobject(dev);   
	ret = netdev_register_kobject(dev);   
	if (ret)   
		goto err_uninit;   
	// 设置注册状态。   
	dev->reg_state = NETREG_REGISTERED;   
  
	/*  
	 *  Default initial state at registry is that the  
	 *  device is present.  
	 */  
  
	// 设置排队策略状态。   
	set_bit(__LINK_STATE_PRESENT, &dev->state);   
	// 初始化排队规则   
	dev_init_scheduler(dev);   
	dev_hold(dev);   
	// 将相应的链表插入到全局的链表中。紧接着会介绍这个函数   
	list_netdevice(dev);   
  
	/* Notify protocols, that a new device appeared. */  
	// 调用netdev_chain通知内核其他子系统。   
	ret = call_netdevice_notifiers(NETDEV_REGISTER, dev);   
	ret = notifier_to_errno(ret);   
	if (ret) {   
		rollback_registered(dev);   
		dev->reg_state = NETREG_UNREGISTERED;   
	}   
  
out:   
	return ret;   
  
err_uninit:   
	if (dev->uninit)   
		dev->uninit(dev);   
	goto out;   
}  

这里要注意有一个全局的struct net init_net;变量,这个变量保存了全局的name,index hlist以及全局的网络设备链表。

net结构我们这里所需要的也就三个链表:

1
2
3
4
5
6
// 设备链表   
struct list_head    dev_base_head;   
// 名字为索引的hlist   
struct hlist_head   *dev_name_head;   
// index为索引的hlist   
struct hlist_head   *dev_index_head;  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static int list_netdevice(struct net_device *dev)   
{   
	struct net *net = dev_net(dev);   
  
	ASSERT_RTNL();   
  
	write_lock_bh(&dev_base_lock);   
	// 插入全局的list   
	list_add_tail(&dev->dev_list, &net->dev_base_head);   
	// 插入全局的name_list以及index_hlist   
	hlist_add_head(&dev->name_hlist, dev_name_hash(net, dev->name));   
	hlist_add_head(&dev->index_hlist, dev_index_hash(net, dev->ifindex));   
	write_unlock_bh(&dev_base_lock);   
	return 0;   
}  

最终执行完之后,注册函数将会执行rtnl_unlock函数,而此函数则会执行netdev_run_todo方法。也就是完成最终的注册。(要注意,当取消注册这个设备时也会调用这个函数来完成最终的取消注册)

这里有一个全局的net_todo_list的链表:

1
static LIST_HEAD(net_todo_list);  

而在取消注册的函数中会调用这个函数:

1
2
3
4
static void net_set_todo(struct net_device *dev)   
{   
	list_add_tail(&dev->todo_list, &net_todo_list);   
}  

也就是把当前将要取消注册的函数加入到todo_list链表中。

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
void netdev_run_todo(void)   
{   
	struct list_head list;   
  
	/* Snapshot list, allow later requests */  
	// replace掉net_todo_list用list代替。   
	list_replace_init(&net_todo_list, &list);   
  
	__rtnl_unlock();   
	// 当注册设备时没有调用net_set_todo函数来设置net_todo_list,因此list为空,所以就会直接跳过。   
	while (!list_empty(&list)) {   
		// 通过todo_list得到当前的device对象。   
		struct net_device *dev   
			= list_entry(list.next, struct net_device, todo_list);   
		// 删除此todo_list;   
		list_del(&dev->todo_list);   
  
  
		if (unlikely(dev->reg_state != NETREG_UNREGISTERING)) {   
			printk(KERN_ERR "network todo '%s' but state %d/n",   
				   dev->name, dev->reg_state);   
			dump_stack();   
			continue;   
		}   
		// 设置注册状态为NETREG_UNREGISTERED.   
		dev->reg_state = NETREG_UNREGISTERED;   
		// 在每个cpu上调用刷新函数。   
		on_each_cpu(flush_backlog, dev, 1);   
  
		// 等待引用此设备的所有系统释放资源,也就是引用计数清0.   
		netdev_wait_allrefs(dev);   
  
		/* paranoia */  
		BUG_ON(atomic_read(&dev->refcnt));   
		WARN_ON(dev->ip_ptr);   
		WARN_ON(dev->ip6_ptr);   
		WARN_ON(dev->dn_ptr);   
  
		if (dev->destructor)   
			dev->destructor(dev);   
  
		/* Free network device */  
		kobject_put(&dev->dev.kobj);   
	}   
}  

下面来看netdev_wait_allrefs函数,我们先看它的调用流程:

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 void netdev_wait_allrefs(struct net_device *dev)   
{   
	unsigned long rebroadcast_time, warning_time;   
  
	rebroadcast_time = warning_time = jiffies;   
	while (atomic_read(&dev->refcnt) != 0) {   
		if (time_after(jiffies, rebroadcast_time + 1 * HZ)) {   
			rtnl_lock();   
  
			// 给netdev_chain发送NETDEV_UNREGISTER事件,通知各个子模块释放资源   
			/* Rebroadcast unregister notification */  
			call_netdevice_notifiers(NETDEV_UNREGISTER, dev);   
  
			if (test_bit(__LINK_STATE_LINKWATCH_PENDING,   
					 &dev->state)) {   
				/* We must not have linkwatch events  
				 * pending on unregister. If this  
				 * happens, we simply run the queue  
				 * unscheduled, resulting in a noop  
				 * for this device.  
				 */  
				linkwatch_run_queue();   
			}   
  
			__rtnl_unlock();   
  
			rebroadcast_time = jiffies;   
		}   
  
		msleep(250);   
  
		if (time_after(jiffies, warning_time + 10 * HZ)) {   
			printk(KERN_EMERG "unregister_netdevice: "  
				   "waiting for %s to become free. Usage "  
				   "count = %d/n",   
				   dev->name, atomic_read(&dev->refcnt));   
			warning_time = jiffies;   
		}   
	}   
}