这种重点描述从应用层下发的数据,是在TCP中如何被管理以及被传输的。主要会涉及到MTU,各类TCP层的算法(如Nagle)以及核心的拥塞控制算法和flow control算法(滑动窗口机制)。
另外需要注意的是,这块我看的内核3.10与书中的内核版本实现存在较大不同,看代码的时候需要引起注意。
7.1 TCP segmentation unit for sending data
当应用层调用send发送数据后,内核中对应的函数就是tcp_sendmsg()。因此我们从这个函数切入来理解TCP的发送流程。
SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len, unsigned int, flags) // net/socket.c
=> sys_sendto(fd, buff, len, flags, NULL, 0) // send a datagram to a given address, net/socket.c line 1754
=> sock = sockfd_lookup_light() // 通过fd找到sock
=> err = sock_sendmsg(sock, &msg, len)
=> sock->ops->sendmsg() == tcp_sendmsg()
tcp_sendmsg()
=> mss_now = tcp_send_mss() // 获得current mss
=> sg = !!(sk->sk_route_caps * NETIF_F_SG) // 检查硬件是否支持scatter-gather
=> 两个循环,第一层遍历所有的buffer块,第二层遍历某一个buffer的所有数据
/* 获取sk->sk_write_queue的最后一个skb,用于检查是否用满。
* 用满了就新建一个skb放新数据,否则将新数据拼接到这最后一个skb中
*/
=> skb = tcp_write_queue_tail(sk)
=> if (copy <= 0) // 需要new a segment
=> sk_stream_memory_free(sk) // 检查send buffer的配额是否超过上限, 超过了要跳转到wait_for_sndbuf
=> return sk->sk_wmem_queued < sk->sk_sndbuf
=> skb = sk_stream_alloc_skb() // 为新数据新分配一个skb
=> skb_entail(sk, skb) // 将新生成的skb挂到sk->sk_write_queue的尾部
=> skb_can_coalesce() // 判断最后一页能否合并更多数据
=> forced_push(tp) // 解释见接下来的note
=> tcp_mark_push(tp, skb) // 解释见接下来的note
/* push out any pending frames which were held back due to TCP_CORK
* or attempt at coalescing tiny packets
*/
=> __tcp_push_pending_frame() // 如果是设置了PSH flag,会调用该函数尽快的将数据发送出去
=> if (copied) tcp_push() //发送数据
=> check sk->sk_send_head is NULL or not // 不为空表示有数据待发送
=> __tcp_push_pending_frames() // 大部分数据应该是走这条流程被发送出去的
=> tcp_write_xmit() // this rountine write packets to the network
/* 只要有数据pending在write queue里面就继续发送,
* 当然循环内部有各种条件判断是否应该终止循环
*/
=> while (skb = tcp_send_head(sk))
=> cwnd_quota = tcp_cwnd_test(tp, skb) // 根据cwnd与packet in flight的差得到配额
=> if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)) break; // 判断是否受限于rwnd
=> if (unlikely(!tcp_nagle_test()) break; // return true if allow by Nagle
=> if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)) break; // 发送一个skb,不成功则break。参数1表示传递一份clone
=> 细节见后续章节
=> tcp_event_new_data_sent(sk, skb) // 更新sk->sk_send_head,tp->snd_nxt, tp->packet_out等
=> if (push_one) break; // 如果之前只允许发送一个,则break
tcp_mark_push()为sk_buff设置PSH flag。当满足下面某一个条件时,PSH会被设置:
1. we have written more than half of the so far maximum window size from the last byte marked as pushed
2. we have one full-sized TCP segment ready for transmission.
forced_push()就是用来判断上面条件一是否成立的
tcp_transmit_skb
源代码函数上方的几句注释摘录一下,应该基本知道该函数的功能了。
This routine actually transmits TCP packets queued in by tcp_do_sendmsg().
This is used by both the initial transmission and possible later retransmissions.
All SKB's seen here are completely headerless.
话多不说,直接读代码
tcp_transmit_skb()
=> skb = skb_clone(skb, gfp_mask) // 复制一份skb,新的skb不属于任何一个socket
=> if (tcp_packets_in_flight(tp) == 0) tcp_ca_event(sk, CA_EVENT_TX_START); // 根据packet inflight判断传输的开始
=> skb_push(skb, tcp_head_size) // 根据计算得到的TCP header长度调整skb
=> th = tcp_hdr(skb) // 获得TCP header对应位置,并开始构建TCP header
=> tcp_options_write() // write previously computed TCP options to the packet
=> icsk->icsk_af_ops->send_check(sk, skb) == tcp_v4_send_check() // compute an IPv4 TCP checksum
=> if (skb->len != tcp_header_size) tcp_event_data_send(tp, sk) // congestion state accounting after a packet has been sent
=> err = icsk->icsk_af_ops->queue_xmit() == ip_queue_xmit() // 目前不关心ip层及以下的实现
TODO
sock结构体中有一个sk_forward_alloc,需要理解一下这个变量