tcp_sendmsg函数实现分析

来源:互联网 发布:广联达2013软件下载 编辑:程序博客网 时间:2024/05/16 23:50

Tcp_sendmsg主要负责把数据从用户空间拷贝到内核空间,然后根据MSS分段,放入到SKB中,调用tcp_push发送出去。大部分代码都是在找应该把数据复制到哪里去。

int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,size_t size){struct iovec *iov;struct tcp_sock *tp = tcp_sk(sk);struct sk_buff *skb;int iovlen, flags;int mss_now, size_goal;int err, copied;long timeo;lock_sock(sk);TCP_CHECK_TIMER(sk);flags = msg->msg_flags;timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT); /* 如果send_msg是阻塞操作的话,获取阻塞的时间 *//* Wait for a connection to finish. */if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))/* 发送用户数据应该处于ESTABLISHED状态或者是CLOSE_WAIT状态, 如果不在这两种状态则调用sk_stream_wait_connnect 等连接建立完成,如果超时的话就跳转到out_err*/if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)goto out_err;/* This should be in poll */clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);/* 获取当前的MSS, 并将MSG_OOB清零,因为OOB带外数据不支持GSO*/mss_now = tcp_current_mss(sk, !(flags&MSG_OOB)); /* 获取SKB的最大长度,这个表示SKB到达网络设备上时的最大长度*/size_goal = tp->xmit_size_goal;/* Ok commence sending. */iovlen = msg->msg_iovlen; /* 待发数据块的块数*/iov = msg->msg_iov;  /*待发数据指针,起始地址*/copied = 0; /* copied表示有多少个数据块已经从用户空间复制到内核空间,先清零*/err = -EPIPE;/* 先把错误谁-EPIPE,EPIPE表示本地已经关闭socket连接了*/if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN)) /* 如果本地socket有错误或者不允许发送数据,那么跳到do_error去处理*/goto do_error;while (--iovlen >= 0) {/* 如果还有待拷贝的数据块,这个循环用于控制拷贝所有的用户数据块到内核空间*/int seglen = iov->iov_len;  /*当前要复制的这个数据块的长度*/unsigned char __user *from = iov->iov_base; /*当前要复制的这个数据块的起始地址*/iov++; /*指向下一个数据块*/while (seglen > 0) {/*这个数据块是不是全部都拷贝完了,用于控制每一个数据块的拷贝*/int copy;skb = sk->sk_write_queue.prev;/*发送队列的最末尾的一个skb, sk_write_queue指向发送队列的头结点,发送队列是一个双向环链表,所以这里是链表的尾节点 */if (!sk->sk_send_head ||    (copy = size_goal - skb->len) <= 0) { /* 如果sk_send_head == NULL 表示所有发送队列上的SKB都已经发送过了,/* 或者最后一个SKB的长度已经到达SKB的最大长度了,/* 说明不能再往这个SKB上添加数据了,需要分配一个新的SKB */new_segment:/* Allocate new segment. If the interface is SG, * allocate skb fitting to single page. */if (!sk_stream_memory_free(sk)) /* 判断sk->sk_wmem_queued 是否小于sk->sk_sndbuf, 即发送队列中段数据的总长度是否小于发送缓冲区的大小 */goto wait_for_sndbuf; /* 如果已经超过了,说明发送缓冲不够用了, 那么跳转到wait_for_sndbuf处理 *//* 分配SKB */skb = sk_stream_alloc_pskb(sk, select_size(sk, tp),   0, sk->sk_allocation);   /* 如果分配SKB 失败,说明整个系统的内存不够用了,跳转到wait_for_memory处理 */if (!skb)goto wait_for_memory;/* * Check whether we can use HW checksum. */ /* 看看网络设备能不能计算校验和 */if (sk->sk_route_caps & NETIF_F_ALL_CSUM)skb->ip_summed = CHECKSUM_PARTIAL;/* 把这个SKB放到发送队列的尾部 */skb_entail(sk, tp, skb);copy = size_goal; /*对于新的SKB, 可以拷贝的数据长度就等于size_goal */}/* sk_send_head != NULL && (copy = size_goal - skb->len > 0), 表示这个SKB没有发送过,并且还没到size_goal那么大,所以可以往最后一个SKB上添加数据 *//* Try to append data to the end of skb. */if (copy > seglen) /* 如果这个SKB剩余的空间大于这个数据块的大小,那么把要拷贝的长度置为要拷贝的大小,copy = min(copy, seglen)*/copy = seglen;/* Where to copy to? *//* 接下来确定拷贝到哪里去,看看是这个SKB的线性存储区还是聚合分散IO分段 */if (skb_tailroom(skb) > 0) {/* 看看这个SKB的线性存储区还有没有空间,如果有的话,再看看剩余空间和要拷贝的数据大小的关系 *//* We have some space in skb head. Superb! */if (copy > skb_tailroom(skb)) copy = skb_tailroom(skb); /* 这就是最终这次要拷贝的数据长度了 */if ((err = skb_add_data(skb, from, copy)) != 0) /* 把用户数据从from处拷贝长度为copy的数据到这个skb*/goto do_fault; /* 如果拷贝过程出错,那跳到do_fault去处理*/} else  { /* 这个SKB的线性存储区已经没有空间了,那就要把数据复制到支持分散聚合I/O的页中 */int merge = 0;/* 用于标识是否在最后一个页中添加数据,先初始化为0 */int i = skb_shinfo(skb)->nr_frags; /*获得这个SKB用了多少个分散的片段*/struct page *page = TCP_PAGE(sk);  /* 获得上次用于拷贝的页面地址,sk_sndmsg_page*/int off = TCP_OFF(sk); /*已有数据在上一次用的页中的偏移*/if (skb_can_coalesce(skb, i, page, off) &&    off != PAGE_SIZE) { /*看看能不能往最后一个页中追加数据,如果可以的话merge赋值为1*//* We can extend the last page * fragment. */merge = 1;} else if (i == MAX_SKB_FRAGS ||   (!i &&   !(sk->sk_route_caps & NETIF_F_SG))) { /*如果网络设备是不只是SG的或者分页片段已经达到上限了,那就不能再往这个SKB中添加数据了,而要分配新的SKB*//* Need to add new fragment and cannot * do this because interface is non-SG, * or because all the page slots are * busy. */tcp_mark_push(tp, skb); /*设置PUSH标志,跟新push_seq = write_seq,表示希望到push_seq的数据能尽快发送出去。*/goto new_segment; /*所以跳回到new_segment处分配新的SKB*/} else if (page) {if (off == PAGE_SIZE) { /* 最后一个页的数据已经满了 */put_page(page);TCP_PAGE(sk) = page = NULL;off = 0;}} else /* 最后一种情况,不用分配新的SKB,但是最后一个页也不能添加数据,所以要新开一个页,从这个页的起始处开始写数据,所以off要设为0 */off = 0;if (copy > PAGE_SIZE - off) //看看这个页还有多少剩余空间copy = PAGE_SIZE - off;if (!sk_stream_wmem_schedule(sk, copy)) /*看看用于输出的缓存是否已经达到上限,如果已经达到的话,要等到有可用输出缓存或者超时 */goto wait_for_memory;if (!page) {/*如果page = NULL, 一般是新开了一个SKB或者聚合分散IO的最后一个页已经用完了,那么要开辟一个新的页 *//* Allocate new cache page. */if (!(page = sk_stream_alloc_page(sk)))goto wait_for_memory;}/* 终于分配好了内存,可以开始往页上复制数据了 *//* Time to copy data. We are close to * the end! */err = skb_copy_to_page(sk, from, skb, page,       off, copy);if (err) {/* If this page was new, give it to the * socket so it does not get leaked. */if (!TCP_PAGE(sk)) { /* 如果拷贝失败了,要记录下sk_sndmsg_page = page, sk_sndmsg_off = 0,用以记录下来以备释放或者下一次拷贝时使用 */TCP_PAGE(sk) = page;TCP_OFF(sk) = 0;}goto do_error;}/* Update the skb. */if (merge) { /* 如果是在原来SKB的最后一个页中添加数据的话,需要更新这个页面的实际使用长度 */skb_shinfo(skb)->frags[i - 1].size +=copy;} else { /*如果是将数据拷贝到一个新的页中*/skb_fill_page_desc(skb, i, page, off, copy); /* 那么就要更新这个页的信息*/if (TCP_PAGE(sk)) { /* 如果sk_sndmsg_page != NULL, 表示用的是上次分配的页面,需要增加这个页的引用计数*/get_page(page);} else if (off + copy < PAGE_SIZE) { /* 否则sk_sndmsg_page == NULL,说明用的是最近新分配的页,并且这个页还没有用完*/get_page(page);/* 那么不仅需要增加新的页的引用计数*/TCP_PAGE(sk) = page; /*还需要修改sk_sndmsg_page为这个页,表示下次还可以接着用这个页*/}}TCP_OFF(sk) = off + copy;} /* 完成了一次数据拷贝 */if (!copied) /* 如果没有拷贝数据,那么清空PSH标志 */TCP_SKB_CB(skb)->flags &= ~TCPCB_FLAG_PSH;tp->write_seq += copy; /* 更新发送队列中的最后一个序列号write_seq */TCP_SKB_CB(skb)->end_seq += copy; /* 更新这个SKB的最后序列号,因为我们把往这个SKB中添加了新的数据 */skb_shinfo(skb)->gso_segs = 0;from += copy; /* 更新要复制的数据起始地址*/copied += copy; /* 更新已复制字节数的统计 */if ((seglen -= copy) == 0 && iovlen == 0) /*如果用户复制全部完了,那就跳到out,跳出两层while循环*/goto out;if (skb->len < mss_now || (flags & MSG_OOB)) /*如果这个SKB的数据长度小于MSS,说明还可以往这个SKB中添加数据,那么就继续复制;如果是带外数据,也继续复制数据*/continue;if (forced_push(tp)) {/* 检查是否要马上发送数据,如果从上次push之后新增加的数据已经超过了接收方窗口的一半,那就要马上发送数据*/tcp_mark_push(tp, skb); /*给这个SKB打上PUSH标记 */__tcp_push_pending_frames(sk, tp, mss_now, TCP_NAGLE_PUSH); /*调用发包函数发包*/} else if (skb == sk->sk_send_head) /*如果数据没有那么多,但是以前write_queue上的数据都发送完了,那么也把这个SKB发送出去*/tcp_push_one(sk, mss_now);continue;/* 否则就继续复制数据*/wait_for_sndbuf: /* 发送队列中SKB的数据总长度达到了发送缓冲区的上限*/set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);wait_for_memory: /*整个系统的内存不够用了*/if (copied) /* 虽然分配SKB失败了,但是之前有复制一些用户数据,那么就先把这些发送出去,并且去掉MSG_MORE标志,表示此次发送没有后续的数据了*/tcp_push(sk, tp, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);if ((err = sk_stream_wait_memory(sk, &timeo)) != 0) /*等待内存超时了,跳到do_error处理*/goto do_error;/*还未超时就等到了可用内存空间,有可能MSS发生了变化,所以重新获取MSS和size_goal,继续复制数据*/mss_now = tcp_current_mss(sk, !(flags&MSG_OOB));  size_goal = tp->xmit_size_goal;}}/*正常情况下,数据都复制完了,如果有复制数据,那就把这些数据都发送出去*/out:if (copied)tcp_push(sk, tp, flags, mss_now, tp->nonagle);TCP_CHECK_TIMER(sk);release_sock(sk);return copied; /*返回从用户空间拷贝了多少数据到内核空间*/do_fault:if (!skb->len) { /*如果SKB的长度为0,说明这个SKB是新分配的*/if (sk->sk_send_head == skb)sk->sk_send_head = NULL;__skb_unlink(skb, &sk->sk_write_queue); /*把这个SKB从发送队列中删除*/sk_stream_free_skb(sk, skb); /* 释放这个SKB */}do_error:if (copied) /* 如果已经复制了部分数据,还是要把这部分数据发送出去*/goto out;out_err: /* 完全没有复制任何数据,那只能返回错误码给用户了 */err = sk_stream_error(sk, flags, err);TCP_CHECK_TIMER(sk);release_sock(sk);return err;}


0 0