kk Blog —— 通用基础


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

tcp重传数据包 tcp_retransmit_skb 函数

http://blog.csdn.net/shanshanpt/article/details/22202999

基于CentOS6.5 2.6.32-504.16.2.el6.x86_64

tcp_retransmit_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
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
/* This retransmits one SKB.  Policy decisions and retransmit queue
 * state updates are done by the caller.  Returns non-zero if an
 * error occurred which prevented the send.
 */ // 如果消耗很多的内存做其他事,那么就没有多余的来做队列的处理了
int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);
	unsigned int cur_mss;
	int err;

	/* Inconslusive MTU probe */
	if (icsk->icsk_mtup.probe_size) {
		icsk->icsk_mtup.probe_size = 0;
	}

	/* Do not sent more than we queued. 1/4 is reserved for possible
	 * copying overhead: fragmentation, tunneling, mangling etc.
	 */
	if (atomic_read(&sk->sk_wmem_alloc) >                                    // sk_wmem_alloc:传输队列大小
		min(sk->sk_wmem_queued + (sk->sk_wmem_queued >> 2), sk->sk_sndbuf))  // sk_wmem_queud:固定的队列大小
		return -EAGAIN;

	if (before(TCP_SKB_CB(skb)->seq, tp->snd_una)) {         // 若这样,说明是有一部分数据才需要重传,形如:seq---snd_una---end_seq,前面一半已收到ACK
		if (before(TCP_SKB_CB(skb)->end_seq, tp->snd_una))   // 若这样,说明全部ACK,无需重传,BUG
			BUG();
		if (tcp_trim_head(sk, skb, tp->snd_una - TCP_SKB_CB(skb)->seq))      // 将无须重传的部分去掉
			return -ENOMEM;
	}

	if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
		return -EHOSTUNREACH; /* Routing failure or similar. */

	cur_mss = tcp_current_mss(sk);

	/* If receiver has shrunk his window, and skb is out of
	 * new window, do not retransmit it. The exception is the
	 * case, when window is shrunk to zero. In this case
	 * our retransmit serves as a zero window probe.
	 */
	if (!before(TCP_SKB_CB(skb)->seq, tcp_wnd_end(tp))       // 如果数据在窗口后面,不会发送
		&& TCP_SKB_CB(skb)->seq != tp->snd_una)
		return -EAGAIN;
	if (skb->len > cur_mss) {                                // 如果skb长度 > MSS
		if (tcp_fragment(sk, skb, cur_mss, cur_mss))         // 先分片,并调整packet_out等统计值。再传送
			return -ENOMEM; /* We'll try again later. */
	} else {
		int oldpcount = tcp_skb_pcount(skb);

		if (unlikely(oldpcount > 1)) {
			tcp_init_tso_segs(sk, skb, cur_mss);             // 按当前mss重置skb->gso_XXX
			tcp_adjust_pcount(sk, skb, oldpcount - tcp_skb_pcount(skb)); // 调整packet_out等统计值
		}
	}

	tcp_retrans_try_collapse(sk, skb, cur_mss);              // 尝试和后几个包合并后一起重传出去,加快速度

	/* Some Solaris stacks overoptimize and ignore the FIN on a
	 * retransmit when old data is attached.  So strip it off
	 * since it is cheap to do so and saves bytes on the network.
	 */ //Solaris系统的协议栈有时候会忽略重传SKB上带有的FIN标志的payload,将payload全部剥离掉,节省网络流量
	if (skb->len > 0 &&
		(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) &&
		tp->snd_una == (TCP_SKB_CB(skb)->end_seq - 1)) {
		if (!pskb_trim(skb, 0)) {
			/* Reuse, even though it does some unnecessary work */
			tcp_init_nondata_skb(skb, TCP_SKB_CB(skb)->end_seq - 1,
						 TCP_SKB_CB(skb)->tcp_flags);
			skb->ip_summed = CHECKSUM_NONE;
		}
	}

	/* Make a copy, if the first transmission SKB clone we made
	 * is still in somebody's hands, else make a clone.
	 */
	TCP_SKB_CB(skb)->when = tcp_time_stamp;

	/* make sure skb->data is aligned on arches that require it
	 * and check if ack-trimming & collapsing extended the headroom
	 * beyond what csum_start can cover.
	 */
	if (unlikely((NET_IP_ALIGN && ((unsigned long)skb->data & 3)) ||
			 skb_headroom(skb) >= 0xFFFF)) {
		struct sk_buff *nskb = __pskb_copy(skb, MAX_TCP_HEADER,
						   GFP_ATOMIC);
		err = nskb ? tcp_transmit_skb(sk, nskb, 0, GFP_ATOMIC) :
				 -ENOBUFS;
	} else {
		err = tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);     // 这个才是正在的传输函数
	}

	if (err == 0) {                                         // 发送成功,那么就需要更新TCP统计信息
		/* Update global TCP statistics. */
		TCP_INC_STATS(sock_net(sk), TCP_MIB_RETRANSSEGS);

		tp->total_retrans++;                                // 整体重传数量++

#if FASTRETRANS_DEBUG > 0
		if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_RETRANS) {
			if (net_ratelimit())
				printk(KERN_DEBUG "retrans_out leaked.\n");
		}
#endif
		if (!tp->retrans_out)
			tp->lost_retrans_low = tp->snd_nxt;
		TCP_SKB_CB(skb)->sacked |= TCPCB_RETRANS;
		tp->retrans_out += tcp_skb_pcount(skb);             // 重传出去的数量+=。。。

		/* Save stamp of the first retransmit. */
		if (!tp->retrans_stamp)
			tp->retrans_stamp = TCP_SKB_CB(skb)->when;      // 第一次重传时间戳

		tp->undo_retrans += tcp_skb_pcount(skb);

		/* snd_nxt is stored to detect loss of retransmitted segment,
		 * see tcp_input.c tcp_sacktag_write_queue().
		 */
		TCP_SKB_CB(skb)->ack_seq = tp->snd_nxt;
	}
	return err;
}

tcp_retrans_try_collapse 重传时尝试和后几个包合并后传出去

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
// 只做简单合并,所以条件设置严格
/* Check if coalescing SKBs is legal. */
static int tcp_can_collapse(struct sock *sk, struct sk_buff *skb)
{
	if (tcp_skb_pcount(skb) > 1)         // skb只包含一个数据包,没有TSO分包
		return 0;
	/* TODO: SACK collapsing could be used to remove this condition */
	if (skb_shinfo(skb)->nr_frags != 0)  // 数据都在线性空间,非线性空间中没有数据
		return 0;
	if (skb_cloned(skb))                 // 不是clone
		return 0;
	if (skb == tcp_send_head(sk))
		return 0;
	/* Some heurestics for collapsing over SACK'd could be invented */
	if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED)  // 已经被sack的当然不用重传
		return 0;

	return 1;
}

/* Collapse packets in the retransmit queue to make to create
 * less packets on the wire. This is only done on retransmission.
 */
static void tcp_retrans_try_collapse(struct sock *sk, struct sk_buff *to,
					 int space)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb = to, *tmp;
	int first = 1;

	if (!sysctl_tcp_retrans_collapse)
		return;
	if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_SYN)  // SYN包不合并
		return;

	tcp_for_write_queue_from_safe(skb, tmp, sk) {
		if (!tcp_can_collapse(sk, skb))           // 要和并的包判断是否符合条件
			break;

		space -= skb->len;

		if (first) {
			first = 0;
			continue;
		}

		if (space < 0)
			break;
		/* Punt if not enough space exists in the first SKB for
		 * the data in the second
		 */
		if (skb->len > skb_tailroom(to))          // 第一个包的tailroom空间足够容下该包
			break;

		if (after(TCP_SKB_CB(skb)->end_seq, tcp_wnd_end(tp))) // 大于窗口不合并
			break;

		tcp_collapse_retrans(sk, to);             // 进行两个包的合并
	}
}

tcp_collapse_retrans 重传合并

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
/* Collapses two adjacent SKB's during retransmission. */
static void tcp_collapse_retrans(struct sock *sk, struct sk_buff *skb)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *next_skb = tcp_write_queue_next(sk, skb);
	int skb_size, next_skb_size;

	skb_size = skb->len;
	next_skb_size = next_skb->len;

	BUG_ON(tcp_skb_pcount(skb) != 1 || tcp_skb_pcount(next_skb) != 1);

	tcp_highest_sack_combine(sk, next_skb, skb);

	tcp_unlink_write_queue(next_skb, sk);    // 将要合并的包从队列中删掉

	skb_copy_from_linear_data(next_skb, skb_put(skb, next_skb_size),
				  next_skb_size);            // 将数据copy到前一个包上,调整前一个的len,tail

	if (next_skb->ip_summed == CHECKSUM_PARTIAL)
		skb->ip_summed = CHECKSUM_PARTIAL;

	if (skb->ip_summed != CHECKSUM_PARTIAL)
		skb->csum = csum_block_add(skb->csum, next_skb->csum, skb_size);

	/* Update sequence range on original skb. */
	TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(next_skb)->end_seq;  // end_seq 等于后一个包的end_seq,所以如果skb->end_seq > next_skb->seq,就会合并出一个len>end_seq-seq的异常数据(内核保证了sk_write_queue不会出现这情况)

	/* Merge over control information. This moves PSH/FIN etc. over */
	TCP_SKB_CB(skb)->tcp_flags |= TCP_SKB_CB(next_skb)->tcp_flags;

	/* All done, get rid of second SKB and account for it so
	 * packet counting does not break.
	 */
	TCP_SKB_CB(skb)->sacked |= TCP_SKB_CB(next_skb)->sacked & TCPCB_EVER_RETRANS;

	/* changed transmit queue under us so clear hints */
	tcp_clear_retrans_hints_partial(tp);
	if (next_skb == tp->retransmit_skb_hint)
		tp->retransmit_skb_hint = skb;

	tcp_adjust_pcount(sk, next_skb, tcp_skb_pcount(next_skb)); // 调整pcount

	sk_wmem_free_skb(sk, next_skb);        // 合并到了前一个包上,所以释放这个包
}

拥塞避免处理函数 tcp_reno_cong_avoid

http://blog.csdn.net/shanshanpt/article/details/22201847

慢启动和快速重传拥塞避免算法,函数tcp_reno_cong_avoid
在“慢开始”阶段,每收到一个ACK,cwnd++一次,那么一个RTT之后,cwnd就会加倍
拥塞避免阶段,其实就是在一个RTT时间内将cwnd++一次( 注意在不丢包的情况下 )

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
/*
 * TCP Reno congestion control
 * This is special case used for fallback as well.
 */
/* This is Jacobson's slow start and congestion avoidance.
 * SIGCOMM '88, p. 328.
 */
void tcp_reno_cong_avoid(struct sock *sk, u32 ack, u32 in_flight)
{
	struct tcp_sock *tp = tcp_sk(sk);         // 获取tcp_sock
	// 函数返回1说明拥塞窗口被限制,我们需要增加拥塞窗口,否则的话,就不需要增加拥塞窗口。
	if (!tcp_is_cwnd_limited(sk, in_flight))  // 是否已经达到拥塞窗口的限制值(1)
		return;

	/* In "safe" area, increase. */
	if (tp->snd_cwnd <= tp->snd_ssthresh)     // 如果发送窗口大小还 比 慢开始门限小,那么还是慢开始处理
		tcp_slow_start(tp);                   // 下面进入慢开始处理 (2)
	/* In dangerous area, increase slowly. */
	else if (sysctl_tcp_abc) {                // 否则进入拥塞避免阶段!!每个RTT时间就加1
		/* RFC3465: Appropriate Byte Count
		 * increase once for each full cwnd acked              // 基本思想就是:经过一个RTT时间就将snd_cwnd增加一个单位!
		 */                                                    // 一个RTT时间可以认为是当前拥塞窗口发送出去的数据的所有ACK都被接收到
		if (tp->bytes_acked >= tp->snd_cwnd*tp->mss_cache) {   // 当前的拥塞窗口的所有段都被ack了,窗口才被允许增加。
			tp->bytes_acked -= tp->snd_cwnd*tp->mss_cache;     // ACK处理过的及删除去了
			if (tp->snd_cwnd < tp->snd_cwnd_clamp)             // 不允许发送窗口大小超过snd_cwnd_clamp值
				tp->snd_cwnd++;
		}
	} else {                                       // 每接收到一个ACK,窗口增大(1/snd_cwnd),使用cnt计数
		/* In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd */
		if (tp->snd_cwnd_cnt >= tp->snd_cwnd) {    // 线性增长计数器 >= 阈值
			if (tp->snd_cwnd < tp->snd_cwnd_clamp) // 如果窗口还没有达到阈值
				tp->snd_cwnd++;                    // 那么++增大窗口
			tp->snd_cwnd_cnt = 0;
		} else
			tp->snd_cwnd_cnt++;                    // 否则仅仅是增大线性递增计数器
	}
}

下面看一下“慢开始”算法:

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
void tcp_slow_start(struct tcp_sock *tp)           // 每到达一个ACK,snd_cwnd就加1。这意味着每个RTT,拥塞窗口就会翻倍。
{
	int cnt; /* increase in packets */

	/* RFC3465: ABC Slow start
	 * Increase only after a full MSS of bytes is acked
	 *
	 * TCP sender SHOULD increase cwnd by the number of
	 * previously unacknowledged bytes ACKed by each incoming
	 * acknowledgment, provided the increase is not more than L
	 */
	if (sysctl_tcp_abc && tp->bytes_acked < tp->mss_cache)                     // 如果ack确认的数据少于一个MSS大小,不需要增大窗口
		return;
	// 限制cnt的值
	if (sysctl_tcp_max_ssthresh > 0 && tp->snd_cwnd > sysctl_tcp_max_ssthresh) // 发送窗口超过最大门限值
		cnt = sysctl_tcp_max_ssthresh >> 1;     /* limited slow start */       // 窗口减半~~~~~
	else
		cnt = tp->snd_cwnd;          /* exponential increase */                // 否则还是原来的窗口

	/* RFC3465: ABC
	 * We MAY increase by 2 if discovered delayed ack
	 */
	if (sysctl_tcp_abc > 1 && tp->bytes_acked >= 2*tp->mss_cache) // 如果启动了延迟确认,那么当接收到的ACK大于等于两个MSS的时候才加倍窗口大小
		cnt <<= 1;
	tp->bytes_acked = 0;  // 清空

	tp->snd_cwnd_cnt += cnt;
	while (tp->snd_cwnd_cnt >= tp->snd_cwnd) {  // 这里snd_cwnd_cnt是snd_cwnd的几倍,拥塞窗口就增加几。
		tp->snd_cwnd_cnt -= tp->snd_cwnd;       // ok
		if (tp->snd_cwnd < tp->snd_cwnd_clamp)  // 判断窗口大小
			tp->snd_cwnd++;  // + +
	}
}

最后看一下这个函数:tcp_is_cwnd_limited,基本的意思就是判断需不需要增大拥塞窗口。

关于gso:主要功能就是尽量的延迟数据包的传输,以便与在最恰当的时机传输数据包。如果支持gso,就有可能是tso 延迟了数据包,因此这里会进行几个相关的判断,来看需不需要增加拥塞窗口。

关于burst:主要用来控制网络流量的突发性增大,也就是说当left数据(还能发送的数据段数)大于burst值的时候,我们需要暂时停止增加窗口,因为此时有可能我们这边数据发送过快。其实就是一个平衡权值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int tcp_is_cwnd_limited(const struct sock *sk, u32 in_flight)  // 第二个参数是正在网络中传输,还没有收到确认的报数量
{
	const struct tcp_sock *tp = tcp_sk(sk);
	u32 left;

	if (in_flight >= tp->snd_cwnd)    // 比较发送未确认和发送拥塞窗口的大小
		return 1;                     // 如果未确认的大,那么需要增大拥塞窗口

	if (!sk_can_gso(sk))              // 如果没有gso延时处理所有包,不需要增大窗口
		return 0;

	left = tp->snd_cwnd - in_flight;  // 得到还能发送的数据包的数量
	if (sysctl_tcp_tso_win_divisor)
		return left * sysctl_tcp_tso_win_divisor < tp->snd_cwnd;
	else
		return left <= tcp_max_burst(tp); // 如果还可以发送的数量>burst,说明发送太快,不需要增大窗口。
}

清理重传队列中函数 tcp_clean_rtx_queue

http://blog.csdn.net/shanshanpt/article/details/22194029

如果重传队列中的一些数据已经被确认,那么, 需要从重传队列中清除出去,需要使用这个函数:tcp_clean_rtx_queue

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
/* Remove acknowledged frames from the retransmission queue. If our packet
 * is before the ack sequence we can discard it as it's confirmed to have
 * arrived at the other end.
 */
static int tcp_clean_rtx_queue(struct sock *sk, int prior_fackets)
{
	struct tcp_sock *tp = tcp_sk(sk);   // 获得tcp_sock
	const struct inet_connection_sock *icsk = inet_csk(sk); // 获得连接sock
	struct sk_buff *skb;
	u32 now = tcp_time_stamp;           // 当前时间,用于计算RTT
	int fully_acked = 1;                // 表示数据段是否完全被确认
	int flag = 0;
	u32 pkts_acked = 0;
	u32 reord = tp->packets_out;        // 发送出去,还在网络上跑,但是还没有被确认的数据包们
	s32 seq_rtt = -1;
	s32 ca_seq_rtt = -1;
	ktime_t last_ackt = net_invalid_timestamp();    // 把last_ackt设置位0
	// 下面就是遍历sk_write_queue队列,遇到snd_una就停止,如果没有更新过,开始就直接退出了
	while ((skb = tcp_write_queue_head(sk)) && skb != tcp_send_head(sk)) {
		struct tcp_skb_cb *scb = TCP_SKB_CB(skb);   // 获得这个重传队列的一个skb的cb字段
		u32 end_seq;
		u32 acked_pcount;
		u8 sacked = scb->sacked;

		/* Determine how many packets and what bytes were acked, tso and else */
		if (after(scb->end_seq, tp->snd_una)) {     // 注意这个scb是我们发出去的数据的skb中的一个scb哦!,不是接受到的数据!小心
			if (tcp_skb_pcount(skb) == 1 ||         // 这里的意思就是发出去的数据最后一个字节在已经确认的snd_una之后,说明还有没有确认的字节
			!after(tp->snd_una, scb->seq))          // 如果没有设置了TSO 或者 seq不在snd_una之前,即不是 seq---snd_una---end_seq这样情况
				break;                              // 那么说明没有必要把重传元素去掉,(如果是seq---snd_una---end_seq)那么前面半部分的就可以从队列中删除!!!

			acked_pcount = tcp_tso_acked(sk, skb);  // 如果只确认了TSO段中的一部分,则从skb删除已经确认的segs,并统计确认了多少段( 1 )
			if (!acked_pcount)                      // 处理出错
				break;

			fully_acked = 0;                        // 表示TSO只处理了一部分,其他还没处理完
			end_seq = tp->snd_una;
		} else {
			acked_pcount = tcp_skb_pcount(skb);     // 即 !after(scb->end_seq, tp->snd_una),说明已经完全确认OK!
			end_seq = scb->end_seq;
		}

		/* MTU probing checks */
		if (fully_acked && icsk->icsk_mtup.probe_size &&      // 探测mtu,暂时不多说
		!after(tp->mtu_probe.probe_seq_end, scb->end_seq)) {
			tcp_mtup_probe_success(sk, skb);
		}
		// 下面通过sack的信息得到这是一个被重传的过包
		if (sacked & TCPCB_RETRANS) {
			if (sacked & TCPCB_SACKED_RETRANS)      // 如果之前重传过,&& 之前还没收到回复
				tp->retrans_out -= acked_pcount;    // 现在需要更新重传的且没有收到ACK的包
			flag |= FLAG_RETRANS_DATA_ACKED;        // 重传包收到ACK
			ca_seq_rtt = -1;
			seq_rtt = -1;
			if ((flag & FLAG_DATA_ACKED) || (acked_pcount > 1))
				flag |= FLAG_NONHEAD_RETRANS_ACKED;
		} else { // 如果此数据段没有被重传过
			ca_seq_rtt = now - scb->when;           // 通过ACK确认获得RTT值
			last_ackt = skb->tstamp;                // 获得skb的发送时间
			if (seq_rtt < 0) {
				seq_rtt = ca_seq_rtt;
			}
			if (!(sacked & TCPCB_SACKED_ACKED))     // 如果SACK存在一段没有被确认,那么保存其中序号最小号的
				reord = min(pkts_acked, reord);
		}

		if (sacked & TCPCB_SACKED_ACKED)            // 如果是有sack标识
			tp->sacked_out -= acked_pcount;         // 那么更新sack的发出没有接受到确认的数量
		if (sacked & TCPCB_LOST)                    // 如果是丢包标识,那么更新数量
			tp->lost_out -= acked_pcount;

		if (unlikely(tp->urg_mode && !before(end_seq, tp->snd_up)))  // 紧急模式
			tp->urg_mode = 0;

		tp->packets_out -= acked_pcount;            // 发送的包没有确认的数量-=acked_pcount
		pkts_acked += acked_pcount;                 // 接收到确认的包数量+=acked_pcount

		/* Initial outgoing SYN's get put onto the write_queue
		 * just like anything else we transmit.  It is not
		 * true data, and if we misinform our callers that
		 * this ACK acks real data, we will erroneously exit
		 * connection startup slow start one packet too
		 * quickly.  This is severely frowned upon behavior.
		 */
		if (!(scb->flags & TCPCB_FLAG_SYN)) {       // 如果不是SYN握手包
			flag |= FLAG_DATA_ACKED;                // 标识是数据确认
		} else {
			flag |= FLAG_SYN_ACKED;                 // 标识是SYN包标识
			tp->retrans_stamp = 0;                  // 清除重传戳
		}

		if (!fully_acked)                           // 如果TSO段没被完全确认,则到此为止
			break;

		tcp_unlink_write_queue(skb, sk);            // 从发送队列上移除这个skb!!!这个函数其实很简单,其实就是从链表中移除这个skb而已
		sk_wmem_free_skb(sk, skb);                  // 删除skb内存对象
		tcp_clear_all_retrans_hints(tp);
	}                                               // while循环结束

	if (skb && (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED))  // 虚假的SACK
		flag |= FLAG_SACK_RENEGING;

	if (flag & FLAG_ACKED) {                        // 如果ACK更新了数据,是的snd_una更新了
		const struct tcp_congestion_ops *ca_ops
			= inet_csk(sk)->icsk_ca_ops;            // 拥塞信息

		tcp_ack_update_rtt(sk, flag, seq_rtt);      // 更新RTT
		tcp_rearm_rto(sk);                          // 重置超时重传计时器

		if (tcp_is_reno(tp)) {                      // 如果没有SACK处理
			tcp_remove_reno_sacks(sk, pkts_acked);  // 处理乱序的包
		} else {
			/* Non-retransmitted hole got filled? That's reordering */
			if (reord < prior_fackets)
				tcp_update_reordering(sk, tp->fackets_out - reord, 0);  // 更新乱序队列大小
		}

		tp->fackets_out -= min(pkts_acked, tp->fackets_out);    // 更新提前确认算法得出的尚未得到确认的包的数量

		if (ca_ops->pkts_acked) {   // 这是一个钩子函数
			s32 rtt_us = -1;

			/* Is the ACK triggering packet unambiguous? */
			if (!(flag & FLAG_RETRANS_DATA_ACKED)) {            // 如果是确认了非重传的包
				/* High resolution needed and available? */
				if (ca_ops->flags & TCP_CONG_RTT_STAMP &&       // 下面都是测量RTT,精读不同而已
				!ktime_equal(last_ackt,
						 net_invalid_timestamp()))
					rtt_us = ktime_us_delta(ktime_get_real(),
								last_ackt);
				else if (ca_seq_rtt > 0)
					rtt_us = jiffies_to_usecs(ca_seq_rtt);
			}

			ca_ops->pkts_acked(sk, pkts_acked, rtt_us);
		}
	}

#if FASTRETRANS_DEBUG > 0  // 下面用于调试
	BUG_TRAP((int)tp->sacked_out >= 0);
	BUG_TRAP((int)tp->lost_out >= 0);
	BUG_TRAP((int)tp->retrans_out >= 0);
	if (!tp->packets_out && tcp_is_sack(tp)) {
		icsk = inet_csk(sk);
		if (tp->lost_out) {
			printk(KERN_DEBUG "Leak l=%u %d\n",
				tp->lost_out, icsk->icsk_ca_state);
			tp->lost_out = 0;
		}
		if (tp->sacked_out) {
			printk(KERN_DEBUG "Leak s=%u %d\n",
				tp->sacked_out, icsk->icsk_ca_state);
			tp->sacked_out = 0;
		}
		if (tp->retrans_out) {
			printk(KERN_DEBUG "Leak r=%u %d\n",
				tp->retrans_out, icsk->icsk_ca_state);
			 tp->retrans_out = 0;
		}
	}
#endif
	return flag;
}

下面看一下tcp_tso_acked函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* If we get here, the whole TSO packet has not been acked. */
static u32 tcp_tso_acked(struct sock *sk, struct sk_buff *skb)       // TSO 包并没有全部被确认,现在需要统计已经被确认的数量
{
	struct tcp_sock *tp = tcp_sk(sk);                                // 获得tcp_sock
	u32 packets_acked;

	BUG_ON(!after(TCP_SKB_CB(skb)->end_seq, tp->snd_una));           // seq---end_seq---snd_una  这种情况不可能进来

	packets_acked = tcp_skb_pcount(skb);                             // TSO段总共包括几个
	if (tcp_trim_head(sk, skb, tp->snd_una - TCP_SKB_CB(skb)->seq))  // 对于已经确认的部分,更新skb中的信息。例如len之类信息都变了
		return 0;                                                    // 然后重新计算出新的剩余的segs
	packets_acked -= tcp_skb_pcount(skb);                            // 之前总的segs - 现在剩余的segs == 被确认的segs

	if (packets_acked) {
		BUG_ON(tcp_skb_pcount(skb) == 0);
		BUG_ON(!before(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq));
	}

	return packets_acked;                                            // 返回被确认的数量
}

linux TCP/IP协议栈-IP层

linux TCP/IP协议栈 —ip_rcv()
linux TCP/IP协议栈 —ip_rcv_finish()
linux TCP/IP协议栈 —ip_local_deliver()
linux TCP/IP协议栈 —ip_local_deliver_finish()
linux TCP/IP协议栈 —ip_defrag()
linux TCP/IP协议栈 —ip_find()
linux TCP/IP协议栈 —inet_frag_find()

ip_rcv()

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
/* 主要功能:对IP头部合法性进行严格检查,然后把具体功能交给ip_rcv_finish。*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
	struct iphdr *iph;
	u32 len;
	/* 网络名字空间,忽略 */
	if (dev->nd_net != &init_net)
		goto drop;
	/*
	 *当网卡处于混杂模式时,收到不是发往该主机的数据包,由net_rx_action()设置。
	 *在调用ip_rcv之前,内核会将该数据包交给嗅探器,所以该函数仅丢弃该包。
	 */
	if (skb->pkt_type == PACKET_OTHERHOST)
		goto drop;
	/* SNMP所需要的统计数据,忽略 */
	IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES);

	/*
	 *ip_rcv是由netif_receive_skb函数调用,如果嗅探器或者其他的用户对数据包需要进
	 *进行处理,则在调用ip_rcv之前,netif_receive_skb会增加skb的引用计数,既该引
	 *用计数会大于1。若如此次,则skb_share_check会创建sk_buff的一份拷贝。
	 */
	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
		IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
		goto out;
	}
	/*
	 *pskb_may_pull确保skb->data指向的内存包含的数据至少为IP头部大小,由于每个
	 *IP数据包包括IP分片必须包含一个完整的IP头部。如果小于IP头部大小,则缺失
	 *的部分将从数据分片中拷贝。这些分片保存在skb_shinfo(skb)->frags[]中。
	 */
	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
		goto inhdr_error;
	/* pskb_may_pull可能会调整skb中的指针,所以需要重新定义IP头部*/
	iph = ip_hdr(skb);

	/*
	 *    RFC1122: 3.1.2.2 MUST silently discard any IP frame that fails the checksum.
	 *
	 *    Is the datagram acceptable?
	 *
	 *    1.    Length at least the size of an ip header
	 *    2.    Version of 4
	 *    3.    Checksums correctly. [Speed optimisation for later, skip loopback checksums]
	 *    4.    Doesn't have a bogus length
	 */
	/* 上面说的很清楚了 */
	if (iph->ihl < 5 || iph->version != 4)
		goto inhdr_error;
	/* 确保IP完整的头部包括选项在内存中 */
	if (!pskb_may_pull(skb, iph->ihl*4))
		goto inhdr_error;
	
	iph = ip_hdr(skb);
	/* 验证IP头部的校验和 */
	if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
		goto inhdr_error;
	/* IP头部中指示的IP数据包总长度 */
	len = ntohs(iph->tot_len);
	/*
	 *确保skb的数据长度大于等于IP头部中指示的IP数据包总长度及数据包总长度必须
	 *大于等于IP头部长度。
	 */
	if (skb->len < len) {
		IP_INC_STATS_BH(IPSTATS_MIB_INTRUNCATEDPKTS);
		goto drop;
	} else if (len < (iph->ihl*4))
		goto inhdr_error;

	/* Our transport medium may have padded the buffer out. Now we know it
	 * is IP we can trim to the true length of the frame.
	 * Note this now means skb->len holds ntohs(iph->tot_len).
	 */
	/* 注释说明的很清楚,该函数成功执行完之后,skb->len = ntohs(iph->tot_len). */
	if (pskb_trim_rcsum(skb, len)) {
		IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
		goto drop;
	}

	/* Remove any debris in the socket control block */
	memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
	/* 忽略与netfilter子系统的交互,调用为ip_rcv_finish(skb) */
	return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
		 ip_rcv_finish);

inhdr_error:
	IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
	kfree_skb(skb);
out:
	return NET_RX_DROP;
}

ip_rcv_finish()

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
static int ip_rcv_finish(struct sk_buff *skb)
{
	const struct iphdr *iph = ip_hdr(skb);
	struct rtable *rt;

	/*
	 *    Initialise the virtual path cache for the packet. It describes
	 *    how the packet travels inside Linux networking.
	 */
	/*
	 * 通常从外界接收的数据包,skb->dst不会包含路由信息,暂时还不知道在何处会设置
	 * 这个字段。ip_route_input函数会根据路由表设置路由信息,暂时不考虑路由系统。
	 */
	if (skb->dst == NULL) {
		int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
					 skb->dev);
		if (unlikely(err)) {
			if (err == -EHOSTUNREACH)
				IP_INC_STATS_BH(IPSTATS_MIB_INADDRERRORS);
			else if (err == -ENETUNREACH)
				IP_INC_STATS_BH(IPSTATS_MIB_INNOROUTES);
			goto drop;
		}
	}
/* 更新流量控制所需要的统计数据,忽略 */
#ifdef CONFIG_NET_CLS_ROUTE
	if (unlikely(skb->dst->tclassid)) {
		struct ip_rt_acct *st = ip_rt_acct + 256*smp_processor_id();
		u32 idx = skb->dst->tclassid;
		st[idx&0xFF].o_packets++;
		st[idx&0xFF].o_bytes+=skb->len;
		st[(idx>>16)&0xFF].i_packets++;
		st[(idx>>16)&0xFF].i_bytes+=skb->len;
	}
#endif
	/* 如果IP头部大于20字节,则表示IP头部包含IP选项,需要进行选项处理.暂时忽略,毕竟很少用 */
	if (iph->ihl > 5 && ip_rcv_options(skb))
		goto drop;

	/* skb->dst包含路由信息。根据路由类型更新SNMP统计数据 */
	rt = (struct rtable*)skb->dst;
	if (rt->rt_type == RTN_MULTICAST)
		IP_INC_STATS_BH(IPSTATS_MIB_INMCASTPKTS);
	else if (rt->rt_type == RTN_BROADCAST)
		IP_INC_STATS_BH(IPSTATS_MIB_INBCASTPKTS);
	/*
	 * dst_input实际上会调用skb->dst->input(skb).input函数会根据路由信息设置为合适的
	 * 函数指针,如果是递交到本地的则为ip_local_deliver,若是转发则为ip_forward.
	 * 暂时仅先考虑ip_local_deliver。
	 */
	return dst_input(skb);

drop:
	kfree_skb(skb);
	return NET_RX_DROP;
}

ip_local_deliver()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
 *     Deliver IP Packets to the higher protocol layers.
 */
主要功能:收集IP分片,然后调用ip_local_deliver_finish将一个完整的数据包传送给上层协议。
int ip_local_deliver(struct sk_buff *skb)
{
	/*
	 *    Reassemble IP fragments.
	 */
	/*
	 * 判断该IP数据包是否是一个分片,如果IP_MF置位,则表示该包是分片之一,其
	 * 后还有更多分片,最后一个IP分片未置位IP_MF但是其offset是非0。
	 * 如果是一个IP分片,则调用ip_defrag重新组织IP数据包。
	 */
	if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
		if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
			return 0;
	}
	/* 调用ip_local_deliver_finish(skb) */
	return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
		 ip_local_deliver_finish);
}

ip_local_deliver_finish()

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
/* 如果忽略掉原始套接字和IPSec,则该函数仅仅是根据IP头部中的协议字段选择上层L4协议,并交给它来处理 */
static int ip_local_deliver_finish(struct sk_buff *skb)
{
	/* 跳过IP头部 */
	__skb_pull(skb, ip_hdrlen(skb));

	/* Point into the IP datagram, just past the header. */
	/* 设置传输层头部位置 */
	skb_reset_transport_header(skb);

	rcu_read_lock();
	{
		/* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
		int protocol = ip_hdr(skb)->protocol;
		int hash;
		struct sock *raw_sk;
		struct net_protocol *ipprot;

	resubmit:
	/* 这个hash根本不是哈希值,仅仅只是inet_protos数组中的下表而已 */
		hash = protocol & (MAX_INET_PROTOS - 1);
		raw_sk = sk_head(&raw_v4_htable[hash]);

		/* If there maybe a raw socket we must check - if not we
		 * don't care less
		 */
	/* 原始套接字?? 忽略... */
		if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))
			raw_sk = NULL;
	/* 查找注册的L4层协议处理结构。 */
		if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {
			int ret;
	/* 启用了安全策略,则交给IPSec */
			if (!ipprot->no_policy) {
				if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
					kfree_skb(skb);
					goto out;
				}
				nf_reset(skb);
			}
	/* 调用L4层协议处理函数 */
	/* 通常会是tcp_v4_rcv, udp_rcv, icmp_rcv和igmp_rcv */
	/* 如果注册了其他的L4层协议处理,则会进行相应的调用。 */
			ret = ipprot->handler(skb);
			if (ret < 0) {
				protocol = -ret;
				goto resubmit;
			}
			IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
		} else {
			if (!raw_sk) {    /* 无原始套接字,提交给IPSec */
				if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
					IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
					icmp_send(skb, ICMP_DEST_UNREACH,
						 ICMP_PROT_UNREACH, 0);
				}
			} else
				IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
			kfree_skb(skb);
		}
	}
 out:
	rcu_read_unlock();

	return 0;
}

ip_defrag()

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
/* Process an incoming IP datagram fragment. */
int ip_defrag(struct sk_buff *skb, u32 user)
{
	struct ipq *qp;

	IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);

	/* Start by cleaning up the memory. */
	/*
	 * 首先检查所有IP分片所消耗的内存是否大于系统允许的最高阀值,如果是,则调用
	 * ip_evictor()丢弃未完全到达的IP分片,从最旧的分片开始释放。此举一来是为了节
	 * 约内存,二来是未了防止黑客的恶意攻击。使分片在系统中累计,降低系统性能。
	 */
	if (atomic_read(&ip4_frags.mem) > ip4_frags_ctl.high_thresh)
		ip_evictor();

	/* Lookup (or create) queue header */
	/* 如果该分片是数据报的第一个分片,则ip_find返回一个新的队列来搜集分片,否则
	 * 返回其所属于的分片队列。 */
	if ((qp = ip_find(ip_hdr(skb), user)) != NULL) {
		int ret;

		spin_lock(&qp->q.lock);
	/* 将该分片加入到队列中,重组分片队列,如果所有的包都收到了,则该函数
	 * 负责重组IP包 */
		ret = ip_frag_queue(qp, skb);

		spin_unlock(&qp->q.lock);
		ipq_put(qp);    /* 引用计数减1 */
		return ret;
	}

	IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
	kfree_skb(skb);
	return -ENOMEM;
}

ip_find()

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
/* Find the correct entry in the "incomplete datagrams" queue for
 * this IP datagram, and create new one, if nothing is found.
 */
/* u32 user这个参数有点迷惑,其表示以何种理由需要对数据包进行重组,在ip_local_deliver的调用序列当中,这个值是IP_DEFRAG_LOCAL_DELIVER。*/
static inline struct ipq *ip_find(struct iphdr *iph, u32 user)
{
	struct inet_frag_queue *q;
	struct ip4_create_arg arg;
	unsigned int hash;

	arg.iph = iph;
	arg.user = user;
	/*
	 * hash算法,该算法除了使用所给的这四个参数之外,还使用了一个随机值
	 * ip4_frags.rnd,,其初始化为
	 * (u32) ((num_physpages ^ (num_physpages>>7)) ^ (jiffies ^ (jiffies >> 6)));
	 * 这是为了防止黑客根据固定的hash算法,通过设置ip头部的这些字段,生成同样
	 * HASH值,从而使某一HASH队列长度急剧增大而影响性能。
	 */
	hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);
	/* 若存在该分片所属的分片队列则返回这个队列,否则创建一个新的队列 */
	q = inet_frag_find(&ip4_frags, &arg, hash);
	if (q == NULL)
		goto out_nomem;

	return container_of(q, struct ipq, q);

out_nomem:
	LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");
	return NULL;
}

inet_frag_find()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct inet_frag_queue *inet_frag_find(struct inet_frags *f, void *key,
		unsigned int hash)
{
	struct inet_frag_queue *q;
	struct hlist_node *n;

	/* f->lock是读写锁,先搜索是否存在该IP分段所属的队列 */
	read_lock(&f->lock);
	hlist_for_each_entry(q, n, &f->hash[hash], list) { /* 扫描该HASH槽中所有节点 */
	/* f->match中match字段在ipfrag_init中初始化为ip4_frag_match函数。*/
	/* 对比分片队列中的散列字段和user是否和key相等,key指向的是struct ip4_create_arg
	 * 结构,包含IP头部和user字段。 */
		if (f->match(q, key)) {
			atomic_inc(&q->refcnt);     /* 若找到,则增加该队列引用计数。 */
			read_unlock(&f->lock);
			return q;                /* 返回该队列 */
		}
	}
	read_unlock(&f->lock);
	/* 该分片是第一个IP分片,创建一个新的分片队列并添加到合适的HASH队列 */
	return inet_frag_create(f, key, hash);
}

Linux slab 分配器,注意RCU

Linux Slab分配器(一)-概述
Linux Slab分配器(二)-初始化
Linux Slab分配器(三)-创建缓存
Linux Slab分配器(四)-分配对象
Linux Slab分配器(五)–释放对象
Linux Slab分配器(六)–创建slab和销毁slab
Linux Slab分配器(七)–销毁缓存

一、概述

slab分配器是Linux内存管理中非常重要和复杂的一部分,其工作是针对一些经常分配并释放的对象,如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。而slab分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统。slab分配对象时,会使用最近释放的对象内存块,因此其驻留在CPU高速缓存的概率较高。

用于描述和管理cache的数据结构是struct kmem_cache
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
struct kmem_cache {
/* 1) per-cpu data, touched during every alloc/free */
	/*per-CPU数据,记录了本地高速缓存的信息,也用于跟踪最近释放的对象,每次分配和释放都要直接访问它*/
	struct array_cache *array[NR_CPUS];
/* 2) Cache tunables. Protected by cache_chain_mutex */
	unsigned int batchcount;  /*本地高速缓存转入或转出的大批对象数量*/
	unsigned int limit;       /*本地高速缓存中空闲对象的最大数目*/
	unsigned int shared;

	unsigned int buffer_size;/*管理对象的大小*/
	u32 reciprocal_buffer_size;/*buffer_size的倒数值*/
/* 3) touched by every alloc & free from the backend */

	unsigned int flags;          /* 高速缓存的永久标识*/
	unsigned int num;         /* 一个slab所包含的对象数目 */

/* 4) cache_grow/shrink */
	/* order of pgs per slab (2^n) */
	unsigned int gfporder;   /*一个slab包含的连续页框数的对数*/

	/* force GFP flags, e.g. GFP_DMA */
	gfp_t gfpflags;          /*与伙伴系统交互时所提供的分配标识*/

	size_t colour;         /* 颜色的个数*/
	unsigned int colour_off; /* 着色的偏移量 */

	/*如果将slab描述符存储在外部,该指针指向存储slab描述符的cache,
	  否则为NULL*/
	struct kmem_cache *slabp_cache;
	unsigned int slab_size;  /*slab管理区的大小*/
	unsigned int dflags;     /*动态标识*/

	/* constructor func */
	void (*ctor)(void *obj); /*创建高速缓存时的构造函数指针*/

/* 5) cache creation/removal */
	const char *name;         /*高速缓存名*/
	struct list_head next;    /*用于将高速缓存链入cache chain*/

/* 6) statistics */
#ifdef CONFIG_DEBUG_SLAB /*一些用于调试用的变量*/
	unsigned long num_active;
	unsigned long num_allocations;
	unsigned long high_mark;
	unsigned long grown;
	unsigned long reaped;
	unsigned long errors;
	unsigned long max_freeable;
	unsigned long node_allocs;
	unsigned long node_frees;
	unsigned long node_overflow;
	atomic_t allochit;
	atomic_t allocmiss;
	atomic_t freehit;
	atomic_t freemiss;

	/*
	 * If debugging is enabled, then the allocator can add additional
	 * fields and/or padding to every object. buffer_size contains the total
	 * object size including these internal fields, the following two
	 * variables contain the offset to the user object and its size.
	 */
	int obj_offset;
	int obj_size;
#endif /* CONFIG_DEBUG_SLAB */

	/*
	 * We put nodelists[] at the end of kmem_cache, because we want to size
	 * this array to nr_node_ids slots instead of MAX_NUMNODES
	 * (see kmem_cache_init())
	 * We still use [MAX_NUMNODES] and not [1] or [0] because cache_cache
	 * is statically defined, so we reserve the max number of nodes.
	 */
	 /*struct kmem_list3用于组织该高速缓存中的slab*/
	struct kmem_list3 *nodelists[MAX_NUMNODES];
	/*
	 * Do not add fields after nodelists[]
	 */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
struct kmem_list3 {
	struct list_head slabs_partial;/*slab链表,包含空闲对象和已分配对象的slab描述符*/
	struct list_head slabs_full;   /*slab链表,只包含非空闲的slab描述符*/
	struct list_head slabs_free;   /*slab链表,只包含空闲的slab描述符*/
	unsigned long free_objects;    /*高速缓存中空闲对象的个数*/
	unsigned int free_limit;       /*空闲对象的上限*/
	unsigned int colour_next;       /*下一个slab使用的颜色*/
	spinlock_t list_lock;
	struct array_cache *shared; /* shared per node */
	struct array_cache **alien; /* on other nodes */
	unsigned long next_reap;    /* updated without locking */
	int free_touched;       /* updated without locking */
};
描述和管理单个slab的结构是struct slab
1
2
3
4
5
6
7
8
struct slab {
	struct list_head list;  /*用于将slab链入kmem_list3的链表*/
	unsigned long colouroff;/*该slab的着色偏移*/
	void *s_mem;            /*指向slab中的第一个对象*/
	unsigned int inuse;     /*已分配出去的对象*/
	kmem_bufctl_t free;     /*下一个空闲对象的下标*/
	unsigned short nodeid;  /*节点标识号*/
};