由第五章可知,sock_recvmsg和tcp_sendmsg用于tcp层和应用层的接口,由第四章可知,tcp_v4_rcv和tcp_tarnsmit_skb是传输层和网络层之间的接口,现在来看看tcp_sendmsg是如何到tcp_tarnsmit_skb,tcp_v4_rcv又是如何到sock_recvmsg的。
图7.1 套接字发送
sys_send的参数意义如下,fd是套接字ID,buff是要发送的内容,len是要发送的长度。
net/socket.c
SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len, unsigned int, flags)
{
return sys_sendto(fd, buff, len, flags, NULL, 0);
}
flags的参数意义如表7-1:
flags | 说明 | recv | send |
MSG_DONTROUTE | 绕过路由表查找 |
| • |
MSG_DONTWAIT | 仅本操作非阻塞 | • | • |
MSG_OOB | 发送或接收带外数据 | • | • |
MSG_PEEK | 窥看外来消息 | • |
|
MSG_WAITALL | 等待所有数据 | • |
|
char snd_buff[BUFFER_SIZE] = "tcp/ip protocal stack" ;
send(sock_fd,snd_buff,BUFFER_SIZE,0);
sys_sendto的参数fd是sock_fd,之前的应用程序中创建的,buff对应的是snd_buff,len对应的是BUFFER_SIZE,flags对应就是0,addr是NULL,addr_len是0。
这里的buff是用户空间的地址,内核空间和用户空间的交互需要使用copy_from_user和copy_from_user接口。
net/socket.c1754 SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
1755 unsigned int, flags, struct sockaddr __user *, addr,
1756 int, addr_len)
1757 {
1758 struct socket *sock;
...
1761 struct msghdr msg;
1762 struct iovec iov;
//根据套接字ID,查找应用程序socket()调用第6章的sys_sock()创建的套接字。
1767 sock = sockfd_lookup_light(fd, &err, &fput_needed);...
1771 iov.iov_base = buff;//用户空间的地址,被记录于这个字段了。
1772 iov.iov_len = len; //此用于记录用户空间需要传递数据的字节数,对应这里就是BUFFER_SIZE。
1773 msg.msg_name = NULL; //如果查看之一文章,message在四层模型中位于应用层和传输层之间。后面的发送将使用msg结构体。
1774 msg.msg_iov = &iov; //将地址信息存入msg结构体中。
1775 msg.msg_iovlen = 1;
1776 msg.msg_control = NULL;
1777 msg.msg_controllen = 0;
1778 msg.msg_namelen = 0;
...
1788 msg.msg_flags = flags;
1789 err = sock_sendmsg(sock, &msg, len);
...
1794 return err;
1795 }
sys_sendto将有价值的信息都保存了msg中,接着调用了sock_sendmsg函数,该函数的第一个参数是创建的套接字,第二个参数包含了要发送数据的一些信息,最后一个参数是待发送数据的长度,对于32为机型,该长度总是小于等于32位无符号数所能表示的最大长度。
msg
的各字段:
struct msghdr {
void *msg_name;/* Socket name,上例中其赋值为了NULL*/
int msg_namelen;/* Length of name ,长度被赋值为0了*/
struct iovec *msg_iov;/* Data blocks,这里存放了用户空间待发送数据的首地址和发送数据的字节数*/
__kernel_size_tmsg_iovlen;/* Number of blocks,发送的block数,只有一个,这里赋值等于1*/
void *msg_control;/* Per protocol magic (eg BSD file descriptor passing),协议相关控制信息,NULL */
__kernel_size_tmsg_controllen;/* Length of cmsg list ,长度同样为NULL*/
unsigned int msg_flags; /*这个flag使用的是socket传递进来的参数 0*/
};
sock_sendmsg()是对__sock_sendmsg_nosec()函数的封装,
//net/socket.c
616 static inline int __sock_sendmsg_nosec(struct kiocb *iocb, struct socket *sock,
617 struct msghdr *msg, size_t size)
618 {
619 struct sock_iocb *si = kiocb_to_siocb(iocb);
620
621 si->sock = sock;
622 si->scm = NULL;
623 si->msg = msg;
624 si->size = size;
625
626 return sock->ops->sendmsg(iocb, sock, msg, size);
627 }
628
629 static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock,
630 struct msghdr *msg, size_t size)
631 {
632 int err = security_socket_sendmsg(sock, msg, size);
633
634 return err ?: __sock_sendmsg_nosec(iocb, sock, msg, size);
635 }
636
637 int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
638 {
639 struct kiocb iocb; //io控制块,每个IO请求都会对应一个该结构体。
640 struct sock_iocb siocb; //套接字的io控制块,每个套接字IO请求会对应一个该结构体。
641 int ret;
642
643 init_sync_kiocb(&iocb, NULL); //该函数的工作见下文
644 iocb.private = &siocb; //将套接字IO控制块,存放在私有字段。
//调用上面629行的函数,632行安全检查,目前只是个框架,无实质内容,检查符合安全后调用634行的__sock_sendmsg_nosec进行发送。
645 ret = __sock_sendmsg(&iocb, sock, msg, size);
646 if (-EIOCBQUEUED == ret)
647 ret = wait_on_sync_kiocb(&iocb);
648 return ret;
649 }
__sock_sendmsg_nosec函数参数如下:
iocb:io控制块,在sock_sendmsg()中定义;
sock:对应根源是应用程序创建的套接字;
msg:存放的是用户空间待发送的数据地址和以字节计数的长度。
size:待发送数据的字节数。
626的函数具体设置在第五章中提过了,是inet_sendmsg()函数。
//include/linux/aio.h
74 static inline void init_sync_kiocb(struct kiocb *kiocb, struct file *filp)
75 {
76 *kiocb = (struct kiocb) {
77 .ki_users = ATOMIC_INIT(1), //引用计数设置成一。
78 .ki_ctx = NULL, //设置成0,表示是同步IO操作。
79 .ki_filp = filp, //filp传递的参数是NULL。
80 .ki_obj.tsk = current, //描述当前进程的结构体
81 };
82 }
//net/ipv4/af_inet.c,该函数的参数意义参考上面__sock_sendmsg_nosec()。
758 int inet_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
759 size_t size)
760 {
763 sock_rps_record_flow(sk); //软中断均衡(多核绑定)
770 return sk->sk_prot->sendmsg(iocb, sk, msg, size);
771 }
770行调用的函数在第五章提到过该函数,对于tcp协议调用的是tcp_sendmsg()函数。
net/ipv4/tcp.c
1016 int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
1017 size_t size)
1018 {
1019 struct iovec *iov;
1020 struct tcp_sock *tp = tcp_sk(sk);
1021 struct sk_buff *skb;
1022 int iovlen, flags, err, copied = 0;
1023 int mss_now = 0, size_goal, copied_syn = 0, offset = 0;
1024 bool sg;
1025 long timeo;
1026
1027 lock_sock(sk);
1028
1029 flags = msg->msg_flags;
//如果设置了fastopen标志,则在发送SYN同步包时,也会发送一部分数据。
1030 if (flags & MSG_FASTOPEN) {
1031 err = tcp_sendmsg_fastopen(sk, msg, &copied_syn);
1032 if (err == -EINPROGRESS && copied_syn > 0)
1033 goto out;
1034 else if (err)
1035 goto out_err;
1036 offset = copied_syn;
1037 }
//该值是发送等待超时值,如果设置了MSG_DONTWAIT标志,则是非阻塞IO,发送完立即返回,否则则会等待一个超时值。
1039 timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
1040
/* 等待tcp建立连接,TCPF_ESTABLISHED 和TCPF_CLOSE_WAIT 均位于tcp已建立连接(connect函数会完成
*三次握手)状态,对于TCP Fast Open模式,可以在连接完全建立前前发送数据包
* 等待的时长是1039行获得的参数。 */
1045 if (((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) &&
1046 !tcp_passive_fastopen(sk)) {
//不处在可发送情况下,则需要等待timeo时间,以便建立连接
1047 if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
1048 goto do_error;
1049 }
//tp->repair是指tcp连接收到了破坏,需要进行修复,这种情况在虚拟机或者LXC被切到新的物理设备上,可能使用的NICs
//改变,这就需要对tcp的收发队列进行处理,以使tcp连接不会因为切换而断开。
1051 if (unlikely(tp->repair)) {
1052 if (tp->repair_queue == TCP_RECV_QUEUE) {
1053 copied = tcp_send_rcvq(sk, msg, size);
1054 goto out;
1055 }
1057 err = -EINVAL;
1058 if (tp->repair_queue == TCP_NO_QUEUE) //如果设置了需要repair,但是队列又什么也没有,则出错
1059 goto out_err;
1062 }
1063
1064 /* This should be in poll */
1065 clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
1066
/***获取mss值,根据pmtu和接收方通告窗口大小设置mss值,另外还要考虑自身NICs是否支持GSO,如果支持,则tcp
**向IP发送的数据包文大小将是MSS的整数倍,最大64K。
****/
1067 mss_now = tcp_send_mss(sk, &size_goal, flags);
1068
1069 /* Ok commence sending. */
1070 iovlen = msg->msg_iovlen; //用户空间传输数据的字节长度
1071 iov = msg->msg_iov; //用户空间待传递数据的用户空间地址。
1072 copied = 0; //已经拷贝的数据字节长度
1073
1074 err = -EPIPE;
1075 if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
1076 goto out_err;
//向量IO( scatter/gather) ,可以从一个stream向多个buffer写,或者从多个bufer向一个stream中写。
1078 sg = !!(sk->sk_route_caps & NETIF_F_SG);
//判断应用程序要发送的数据是否全部发送了,如果没有则执行while循环体内的代码。
1080 while (--iovlen >= 0) {
1081 size_t seglen = iov->iov_len;
1082 unsigned char __user *from = iov->iov_base;
1083
1084 iov++;
1085 if (unlikely(offset > 0)) { /* Skip bytes copied in SYN */
1086 if (offset >= seglen) {
1087 offset -= seglen;
1088 continue;
1089 }
1090 seglen -= offset;
1091 from += offset;
1092 offset = 0;
1093 }
1094
1095 while (seglen > 0) {
/*max=size_goal=tp->xmit_size_goal,表示发送数据报到达网络设备时数据段的最大长度,该长度用来分割数据,TCP发送报文时,每个SKB的大小不能超过该值。在不支持GSO或TSO情况下,xmit_size_goal就等于MSS;而如果支持GSO,则xmit_size_goal会是MSS的整数倍。数据报发送到网络设备后再由NICs根据MSS进行分割。*/
1096 int copy = 0;
1097 int max = size_goal;
//获取传输控制块发送队列的尾部的那个SKB,因为只有队尾的那个SKB才有可能存在剩余空间的
1099 skb = tcp_write_queue_tail(sk);
//判断sk_send_head节点上待发送的sk_buff是否为空,sk_buff在第二章中有过叙述。
1100 if (tcp_send_head(sk)) {
//CHECKSUM_NONE表示csum值无意义,通常需要tcp层自己校验,但多数网卡有硬件校验功能。
1101 if (skb->ip_summed == CHECKSUM_NONE)
1102 max = mss_now;
1103 copy = max - skb->len; //获得有效载荷(payload)
1104 }
//如果copy小于零说明,传输的数据长度超出了tcp允许的长度,这时需要进行分片操作。
1106 if (copy <= 0) {
1107 new_segment:
1108 /* Allocate new segment. If the interface is SG,
1109 * allocate skb fitting to single page.
1110 */
1111 if (!sk_stream_memory_free(sk)) //判断发送缓冲区剩余空闲有没有
1112 goto wait_for_sndbuf;
1113
1114 skb = sk_stream_alloc_skb(sk,
1115 select_size(sk, sg),
1116 sk->sk_allocation);
1117 if (!skb)
1118 goto wait_for_memory;
1119
1120 /*
1121 * Check whether we can use HW checksum.
1122 */
1123 if (sk->sk_route_caps & NETIF_F_ALL_CSUM)
1124 skb->ip_summed = CHECKSUM_PARTIAL;
1125
1126 skb_entail(sk, skb);
1127 copy = size_goal;
1128 max = size_goal;
1129 }
1130
1131 /* Try to append data to the end of skb,预留填充以符合包长度规定 */
1132 if (copy > seglen)
1133 copy = seglen;
1134
1135 /* Where to copy to? */
//返回由sk_stream_alloc()在sk_buff末尾分配的空间。即sk_buff的线性存储区底部是否还有空间
1136 if (skb_availroom(skb) > 0) {
1137 /* We have some space in skb head. Superb! */
1138 copy = min_t(int, copy, skb_availroom(skb));
1139 err = skb_add_data_nocache(sk, skb, from, copy); /如果还有,则将数据由用户空间进行复制到skb中。
1140 if (err)
1141 goto do_fault;
1142 } else { //如果没有空间,则将数据复制到scatter/gather IO类型的页中。
1143 bool merge = true; //标识最后一个页中是否有数据。
1144 int i = skb_shinfo(skb)->nr_frags; //获取分片的数量
1145 struct page_frag *pfrag = sk_page_frag(sk); //获得cache中的分片页
/*在分片列表(frags)中使用原有分片(返回相应分片的指针)或分配新页来存放数据,如果不成功则等待存储空间*/
1147 if (!sk_page_frag_refill(sk, pfrag))
1148 goto wait_for_memory;
/*
* 如果传输控制块(sock)中的缓存页pfrag,不是当前skb->shared_info中的最后一个分片(分散聚集IO页面)所在的页面,则直接使用该页面,
* 将其添加 到分片列表(分散聚集IO页面数组)中,否则说明传输控制块(sock)中的缓存页pfrag就是分散聚集IO页面的最后一个页面, 则直接向其中拷贝数据即可。
1150 if (!skb_can_coalesce(skb, i, pfrag->page,
1151 pfrag->offset)) {
1152 if (i == MAX_SKB_FRAGS || !sg) {
1153 tcp_mark_push(tp, skb);
1154 goto new_segment;
1155 }
1156 merge = false;
1157 }
1158
1159 copy = min_t(int, copy, pfrag->size - pfrag->offset);
/*拷贝数据至skb中非线性区分片(分散聚集IO页面)中*/
1161 if (!sk_wmem_schedule(sk, copy))
1162 goto wait_for_memory;
1163
1164 err = skb_copy_to_page_nocache(sk, from, skb,
1165 pfrag->page,
1166 pfrag->offset,
1167 copy);
1168 if (err)
1169 goto do_error;
1170
1171 /* Update the skb. */
1172 if (merge) {
/*增加分片大小*/
1173 skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
1174 } else {
/*如果是复制到一个全新的页面分段中,则需要更新的有关分段信息就会多一些,如分段数据的长度、页内偏移、分段数量等。调用skb_fill_page_desc()来完成。如果标识最近一次分配页面的sk_sndmsg_page不为空,则增加对该页面的引用。否则说明复制了数据的页面是新分配的,且没有使用完,在增加对该页面的引用的同时,还需更新sk_sndmsg_page的值。如果新分配的页面已使用完,就无须更新sk_sndmsg_page的值了,因为如果SKB未超过段上限,那么下次必定还会分配新的页面,因此在此处就省去了对off+copy=PAGE_SIZE这条分支的处理。*/
1175 skb_fill_page_desc(skb, i, pfrag->page,
1176 pfrag->offset, copy);
1177 get_page(pfrag->page);
1178 }
1179 pfrag->offset += copy;
1180 }
/*如果复制的数据长度为零,则取消TCPHDR_PSH标志,将意味着数据不会被发送*/
1182 if (!copied)
1183 TCP_SKB_CB(skb)->tcp_flags &= ~TCPHDR_PSH;
/*更新发送队列中的最后一个序号write_seq,以及数据包的最后一个序列end_seq,初始化gso分段数gso_segs。*/
1185 tp->write_seq += copy;
1186 TCP_SKB_CB(skb)->end_seq += copy;
1187 skb_shinfo(skb)->gso_segs = 0;
/*更新指向数据源的指针和已复制字节数。*/
1189 from += copy;
1190 copied += copy;
/*如果所有数据已全部复制到SKB中,则跳转到out处理。*/
1191 if ((seglen -= copy) == 0 && iovlen == 0)
1192 goto out;
/*如果当前SKB中的数据小于max,说明还可以往里填充数据,或者发送的是带外数据(MSG_OOB,紧急),则跳过以下发送过程,继续复制数据到SKB*/
1194 if (skb->len < max || (flags & MSG_OOB) || unlikely(tp->repair))
1195 continue;
/*检查是否必须立即发送,即检查自上次发送后产生的数据是否已超过对方曾经通告过的最大窗口值的一半。如果必须立即发送,则设置TCPHDR_PSH标志后调用__tcp_push_pending_frames(),在发送队列上从sk_send_head开始把SKB发送出去。*/
1197 if (forced_push(tp)) {
1198 tcp_mark_push(tp, skb);
1199 __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
1200 } else if (skb == tcp_send_head(sk))
/*如果没有必要立即发送,且发送队列上只存在这个段,则调用tcp_push_one()只发送当前段。*/
1201 tcp_push_one(sk, mss_now);
1202 continue;
/*套接口的发送缓存是有大小限制的,当发送队列中的数据段总长度超过发送缓冲区的长度上限时,就不能再分配SKB了,只能等待。设置SOCK_NOSPACE标志,表示套接口发送缓冲区已满。*/
1204 wait_for_sndbuf:
1205 set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
/*跳到这里,意味着内存分配失败*/
1206 wait_for_memory:
/*虽然分配SKB失败,但是如果之前有数据从用户空间复制过来,则调用tcp_push()将其发送出去。
其中第三个参数中去掉MSG_MORE标志,表示本次发送没有更多的数据了。
因为分配SKB失败,因此可以加上TCPHDR_PSH标志,第五个参数使用nagle算法,可能会推迟发送。*/
1207 if (copied)
1208 tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);
/*调用sk_stream_wait_memory()进入睡眠,等待内存空闲的信号,如果在超时时间内没有得到该信号,则跳转到do_error处执行。*/
1210 if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
1211 goto do_error;
/*等待内存未超时,有空闲内存可用。睡眠后,MSS有可能发生了变化,所以重新获取当前的MSS和TSO分段段长,然后继续循环复制数据。*/
1213 mss_now = tcp_send_mss(sk, &size_goal, flags);
1214 }
1215 }
/*发送过程中正常的退出。*/
1217 out:
1218 if (copied)
/*如果已有复制的数据,则调用tcp_push()将其发送出去,是否立即发送取决于nagle算法。*/
1219 tcp_push(sk, flags, mss_now, tp->nonagle);
1220 release_sock(sk);
1221 return copied + copied_syn;
/*在复制数据异常时进入到这里。*/
1223 do_fault:
1224 if (!skb->len) {
1225 tcp_unlink_write_queue(skb, sk);
1226 /* It is the one place in all of TCP, except connection
1227 * reset, where we can be unlinking the send_head.
1228 */
1229 tcp_check_send_head(sk, skb);
1230 sk_wmem_free_skb(sk, skb);
1231 }
1232 /*如果已复制了部分数据,那么即使发生了错误,也可以发送数据包,因此跳转到out处*/
1233 do_error:
1234 if (copied + copied_syn)
1235 goto out;
/*如果没有复制数据,则调用sk_stream_error()来获取错误码。然后对传输层控制块解锁后返回错误码。*/
1236 out_err:
1237 err = sk_stream_error(sk, flags, err);
1238 release_sock(sk);
1239 return err;
1240 }
第1220行和1222行都会调用tcp_write_xmit()发送数据。这里走1222行的路线
void tcp_push_one(struct sock *sk, unsigned int mss_now)
{
struct sk_buff *skb = tcp_send_head(sk);
tcp_write_xmit(sk, mss_now, TCP_NAGLE_PUSH, 1, sk->sk_allocation);
}
tcp_write_xmit的参数意义如下:
sk:源于应用程序,其使用AF_INET创建的socket套接字
mss_now:maximumsegment size,参考tso和gso后给出的值。
TCP_NAGLE_PUSH:nagle算法标志,起初用于解决拥塞;该标志表示当一个数据段不足MSS时,其会推迟这个数据段的发送,直到数据填充完一个MSS。
push_one:标志要发送的packet数,当其>0时,会确保至少发送一个packet。
gfp:分配内存时的标志1811 static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
1812 int push_one, gfp_t gfp)
1813 {
1814 struct tcp_sock *tp = tcp_sk(sk);
1815 struct sk_buff *skb;
1816 unsigned int tso_segs, sent_pkts;
1817 int cwnd_quota;
1818 int result;
1819
1820 sent_pkts = 0; //记录发送的数据包的个数,初始值为0
1821 //push_one用于记录要发送报文的个数,对于只发送一个报文则跳过if语句。
1822 if (!push_one) {
1823 /* Do MTU probing. */
1824 result = tcp_mtu_probe(sk); //要发送多个的话,会检查MTU值,这个MTU值会影响分片操作。
1825 if (!result) {
1826 return false;
1827 } else if (result > 0) {
1828 sent_pkts = 1;
1829 }
1830 }
1831
1832 while ((skb = tcp_send_head(sk))) { //while循环用于检测发送队列头部是否有sk_buffer需要发送。
1833 unsigned int limit;
1834
1835 //tso/gso字段用于在数据传递到IP层之前进行分片。
1836 tso_segs = tcp_init_tso_segs(sk, skb, mss_now);
1837 BUG_ON(!tso_segs);
1838
1839 if (unlikely(tp->repair) && tp->repair_queue == TCP_SEND_QUEUE)
1840 goto repair; /* Skip network transmission */
//cwnd_quota是拥塞控制窗口中可以发送的segment数。如果cwnd_quota=0表示在发送的segment已经填满了拥塞控制窗//口,不能在发送了。
1842 cwnd_quota = tcp_cwnd_test(tp, skb);
1843 if (!cwnd_quota) { //如果不能在发送了
1844 if (push_one == 2)
1845 /* Force out a loss probe pkt. */
1846 cwnd_quota = 1; //如果设置push_one == 2,那么强制cwnd_quota =1,后面会发送至少一个数据包。
1847 else
1848 break; //跳出循环,不发送数据了。
1849 }
1850
1851 if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now))) //检测是否SKB至少第一个segment位于发送窗口中,否,则跳出while循环。
1852 break;
1853
1854 if (tso_segs == 1) { //对于无tso/gso情况,即未分片。
1855 if (unlikely(!tcp_nagle_test(tp, skb, mss_now, //unlikely预示着tcp_nagle_test返回值是true,true的返回值意味着Nagle拥塞控制算法允许发送该数据。
1856 (tcp_skb_is_last(sk, skb) ?
1857 nonagle : TCP_NAGLE_PUSH))))
1858 break;
1859 } else {
1860 if (!push_one && tcp_tso_should_defer(sk, skb)) //多个skb时,需要计算是否延迟发送
1861 break;
1862 }
1863
1864 /* TSQ : sk_wmem_alloc accounts skb truesize,
1865 * including skb overhead. But thats OK.
1866 */
1867 if (atomic_read(&sk->sk_wmem_alloc) >= sysctl_tcp_limit_output_bytes) { //发送数据是否大于用户使用sysctl设置/proc输出允许的最大字节数?
1868 set_bit(TSQ_THROTTLED, &tp->tsq_flags);
1869 break;
1870 }
1871 limit = mss_now;
1872 if (tso_segs > 1 && !tcp_urg_mode(tp)) //存在分片,且非urgent模式,使用MSS计算发送数据的限制。
1873 limit = tcp_mss_split_point(sk, skb, mss_now,
1874 min_t(unsigned int,
1875 cwnd_quota,
1876 sk->sk_gso_max_segs));
1877
1878 if (skb->len > limit &&
1879 unlikely(tso_fragment(sk, skb, limit, mss_now, gfp))) //如果发送数据大于上面计算的limit值,其启用tso_fragment()进行分片操作。
1880 break;
/* 以上6行:根据条件,可能需要对SKB中的报文进行分段处理,分段的报文包括两种:
* 一种是普通的用MSS分段的报文,另一种则是TSO分段的报文。
能否发送报文主要取决于两个条件:一是报文需完全在发送窗口中,而是拥塞窗口未满。
第一种报文,应该不会再分段了,因为在tcp_sendmsg()中创建报文的SKB时已经根据MSS处理了,而第二种报文,则一般情况下都会大于MSS,因为通过TSO分段的段有可能大于拥塞窗口的剩余空间,如果是这样,就需要以发送窗口和拥塞窗口的最小值作为段长对报文再次分段。
1881
1882 TCP_SKB_CB(skb)->when = tcp_time_stamp; //时间戳跟新。
1883
1884 if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp))) //发送数据
1885 break;
1886
1887 repair:
1888 /* Advance the send_head. This one is sent out.
1889 * This call will increment packets_out.
1890 */
1891 tcp_event_new_data_sent(sk, skb); //更新统计,启动重传定时器。
1892
1893 tcp_minshall_update(tp, mss_now, skb);//更新struct tcp_sock中的snd_sml字段,用于表示是否启用Nagle算法。
1894 sent_pkts += tcp_skb_pcount(skb);
...
1911 }
第1884行注释的比较简单,该函数将构建数据的tcp头,并将其由tcp传递到Ip层,函数定义同样位于tcp_output.c文件中。该函数参数的意义;
sk:关联到应用程序创建的套接字。
sk_buff:存放的是发送数据信息
clone_it:是否clone传递进来的数据报。上面设置了1,为传递。
gfp_mask:申请内存的标志
828 static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
829 gfp_t gfp_mask)
830 {
831 const struct inet_connection_sock *icsk = inet_csk(sk);
832 struct inet_sock *inet;
833 struct tcp_sock *tp;
834 struct tcp_skb_cb *tcb;
835 struct tcp_out_options opts;
836 unsigned int tcp_options_size, tcp_header_size;
837 struct tcp_md5sig_key *md5;
838 struct tcphdr *th;
...
843 /* 如果拥塞控制需要时间戳,则必须在复制前获得时间戳;
844 *并不是所有拥塞算法都会用到时间戳,TCP_CONG_RTT_STAMP,高精度RTT,Round-Trip Time,传输延迟
845 */
846 if (icsk->icsk_ca_ops->flags & TCP_CONG_RTT_STAMP)
847 __net_timestamp(skb);
848
849 if (likely(clone_it)) { //传递进来的参数指定是否需要复制报文
850 const struct sk_buff *fclone = skb + 1;
851
852 if (unlikely(skb->fclone == SKB_FCLONE_ORIG &&
853 fclone->fclone == SKB_FCLONE_CLONE))
854 NET_INC_STATS_BH(sock_net(sk),
855 LINUX_MIB_TCPSPURIOUS_RTX_HOSTQUEUES);
856
857 if (unlikely(skb_cloned(skb)))
858 skb = pskb_copy(skb, gfp_mask); //执行copy操作
859 else
860 skb = skb_clone(skb, gfp_mask); //执行clone操作
861 if (unlikely(!skb))
862 return -ENOBUFS;
863 }
864 /*获取INET层和TCP层的传输控制块、skb中的TCP私有数据块。*/
865 inet = inet_sk(sk);
866 tp = tcp_sk(sk);
867 tcb = TCP_SKB_CB(skb);
868 memset(&opts, 0, sizeof(opts));
869
/*根据TCP选项重新调整TCP首部的长度。*/
/*判断当前TCP报文是否是SYN段,因为有些选项只能出现在SYN报文中,需做特别处理。*/
870 if (unlikely(tcb->tcp_flags & TCPHDR_SYN))
871 tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
872 else
873 tcp_options_size = tcp_established_options(sk, skb, &opts,
874 &md5);
/*tcp首部的总长度等于可选长度加上struct tcphdr。*/
875 tcp_header_size = tcp_options_size + sizeof(struct tcphdr);
876
/*如果已发出但未确认的数据包数目为零,则只初始化拥塞控制,并开始跟踪该连接的RTT。*/
877 if (tcp_packets_in_flight(tp) == 0)
878 tcp_ca_event(sk, CA_EVENT_TX_START);
879
880 /* if no packet is in qdisc/device queue, then allow XPS to select
881 * another queue.
882 */
883 skb->ooo_okay = sk_wmem_alloc_get(sk) == 0;
884 /*调用skb_push()在数据部分的头部添加TCP首部,长度即为之前计算得到的那个tcp_header_size,实际上是把data指针往上移。*/
885 skb_push(skb, tcp_header_size);
886 skb_reset_transport_header(skb);
887
888 skb_orphan(skb); //孤儿一个Buffer,如果skb有解析函数,则会调用它自带的解析函数,孤儿并不意味着buffer不存在,意味着不再为先前所有者所有,意味着可以修改一些字段//
//更新sk_buff 类型的skb的相关字段
889 skb->sk = sk;
890 skb->destructor = (sysctl_tcp_limit_output_bytes > 0) ?
891 tcp_wfree : sock_wfree;
892 atomic_add(skb->truesize, &sk->sk_wmem_alloc);
893
894 /* Build TCP header and checksum it.构建tcp头并做校验 */
895 th = tcp_hdr(skb);
896 th->source = inet->inet_sport;
897 th->dest = inet->inet_dport;
898 th->seq = htonl(tcb->seq);
899 th->ack_seq = htonl(tp->rcv_nxt);
900 *(((__be16 *)th) + 6) = htons(((tcp_header_size >> 2) << 12) |
901 tcb->tcp_flags);
902 /*分两种情况设置TCP首部的接收窗口的大小*/
903 if (unlikely(tcb->tcp_flags & TCPHDR_SYN)) {
904 /* RFC1323: The window in SYN & SYN/ACK segments
905 * is never scaled.
906 */
/*如果是SYN段,则设置接收窗口初始值为rcv_wnd*/
907 th->window = htons(min(tp->rcv_wnd, 65535U));
908 } else {
/*如果是其他的报文,则调用tcp_select_window()计算当前接收窗口的大小。*/
909 th->window = htons(tcp_select_window(sk));
910 }
/*初始化TCP首部的校验码和紧急指针,可选字段,具体请参考TCP协议中的首部定义。*/
911 th->check = 0;
912 th->urg_ptr = 0;
913
914 /* The urg_mode check is necessary during a below snd_una win probe,紧急指针 */
915 if (unlikely(tcp_urg_mode(tp) && before(tcb->seq, tp->snd_up))) {
916 if (before(tp->snd_up, tcb->seq + 0x10000)) {
917 th->urg_ptr = htons(tp->snd_up - tcb->seq);
918 th->urg = 1;
919 } else if (after(tcb->seq + 0xFFFF, tp->snd_nxt)) {
920 th->urg_ptr = htons(0xFFFF);
921 th->urg = 1;
922 }
923 }
924
925 tcp_options_write((__be32 *)(th + 1), tp, &opts); //可选字段
926 if (likely((tcb->tcp_flags & TCPHDR_SYN) == 0))
/*ENC(explicit congestion notification),基于显示反馈的拥塞控制协议,此外还有DCA(delay-based congestion avoidance),基于路径延迟,LCA(loss-based congestion avoidance),基于丢包反馈。
TCP_ECN_send:为已建立连接的套接字的即将发送的packet设置该ECN标志
*/
927 TCP_ECN_send(sk, skb, tcp_header_size);
928
/*MD5(Message Digest Algorithm)算法,计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。
*/
929 #ifdef CONFIG_TCP_MD5SIG
930 /* Calculate the MD5 hash, as we have all we need now */
931 if (md5) {
932 sk_nocaps_add(sk, NETIF_F_GSO_MASK);
933 tp->af_specific->calc_md5_hash(opts.hash_location,
934 md5, sk, NULL, skb);
935 }
936 #endif
937
938 icsk->icsk_af_ops->send_check(sk, skb);
939
940 if (likely(tcb->tcp_flags & TCPHDR_ACK))
941 tcp_event_ack_sent(sk, tcp_skb_pcount(skb));
942
943 if (skb->len != tcp_header_size)
944 tcp_event_data_sent(tp, sk);
945
946 if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq)
947 TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS,
948 tcp_skb_pcount(skb));
949
/*调用发送接口queue_xmit发送报文,进入到ip层,如果失败返回错误码。在TCP中该接口实现函数为ip_queue_xmit()*/
950 err = icsk->icsk_af_ops->queue_xmit(skb, &inet->cork.fl);
951 if (likely(err <= 0))
952 return err;
953
954 tcp_enter_cwr(sk, 1);
955
956 return net_xmit_eval(err);
957 }
ip_queue_xmit参考网络层发送侧程序。最后将程序的流梳理一遍~!
图7.2 tcp发送函数调用流程