第七章 tcp发送(传输层)--基于Linux3.10

由第五章可知,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.c

1754 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发送函数调用流程


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shichaog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值