Linux内核协议栈(4) 跟踪sendto调用
2016-03-23 22:03
609 查看
int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) { struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *skb; int flags, err, copied = 0; int mss_now = 0, size_goal, copied_syn = 0; bool sg; long timeo; lock_sock(sk); flags = msg->msg_flags; if (flags & MSG_FASTOPEN) { //tcpfastopen特性 err = tcp_sendmsg_fastopen(sk, msg, &copied_syn, size); if (err == -EINPROGRESS && copied_syn > 0) goto out; else if (err) goto out_err; } timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);//如果不是非阻塞IO,则得到超时时间 /* Wait for a connection to finish. One exception is TCP Fast Open * (passive side) where data is allowed to be sent before a connection * is fully established. */ if (((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) && !tcp_passive_fastopen(sk)) { err = sk_stream_wait_connect(sk, &timeo);//如果没有建立连接或连接关闭,则等待连接完成 if (err != 0) goto do_error; } //发送到接受队列中 if (unlikely(tp->repair)) { if (tp->repair_queue == TCP_RECV_QUEUE) { copied = tcp_send_rcvq(sk, msg, size); goto out_nopush; } err = -EINVAL; if (tp->repair_queue == TCP_NO_QUEUE) goto out_err; /* 'common' sending to sendq */ } /* This should be in poll */ sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk);//清除异步情况下队列满标志 /* 获取当前的发送MSS. * 获取可发送到网卡的最大数据长度,如果使用GSO,会是MSS的整数倍。 */ mss_now = tcp_send_mss(sk, &size_goal, flags); /* Ok commence sending. */ copied = 0; /* 如果连接有错误,或者不允许发送数据了,那么返回-EPIPE */ err = -EPIPE; if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN)) goto out_err; sg = !!(sk->sk_route_caps & NETIF_F_SG);/* 网卡是否支持分散聚合 */ /* 遍历用户层的数据块数组 */ while (msg_data_left(msg)) { int copy = 0; int max = size_goal; skb = tcp_write_queue_tail(sk); //重传队列其实就是发送队列(sk->sk_write_queue),保存着发送且未得到确认的数据 if (tcp_send_head(sk)) { //sk->sk_send_head指向当前要发送buff的位置,如果为空,说明buf没有空间了 /* 如果网卡不支持检验和计算,那么skb的最大长度为MSS,即不能使用GSO */ if (skb->ip_summed == CHECKSUM_NONE) max = mss_now; //最大报文长度 copy = max - skb->len; } /* 需要使用新的skb来装数据 */ if (copy <= 0) { new_segment: /* Allocate new segment. If the interface is SG, * allocate skb fitting to single page. */ /* 如果发送队列的总大小sk_wmem_queued大于等于发送缓存的上限sk_sndbuf, * 或者发送缓存中尚未发送的数据量超过了用户的设置值,就进入等待。 */ if (!sk_stream_memory_free(sk)) goto wait_for_sndbuf; /* 申请一个skb,其线性数据区的大小为: * 通过select_size()得到的线性数据区中TCP负荷的大小 + 最大的协议头长度。 * 如果申请skb失败了,或者虽然申请skb成功,但是从系统层面判断此次申请不合法, * 那么就进入睡眠,等待内存。 */ skb = sk_stream_alloc_skb(sk, select_size(sk, sg), sk->sk_allocation, skb_queue_empty(&sk->sk_write_queue)); 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_entail(sk, skb); copy = size_goal; max = size_goal; /* All packets are restored as if they have * already been sent. skb_mstamp isn't set to * avoid wrong rtt estimation. * * 如果使用了TCP REPAIR选项,那么为skb设置“发送时间”。 */ if (tp->repair) TCP_SKB_CB(skb)->sacked |= TCPCB_REPAIRED; } /* Try to append data to the end of skb. */ if (copy > msg_data_left(msg)) copy = msg_data_left(msg); /* Where to copy to? * * 如果skb的线性数据区还有剩余空间,就先复制到线性数据区。 * */ if (skb_availroom(skb) > 0) { /* We have some space in skb head. Superb! */ copy = min_t(int, copy, skb_availroom(skb)); /* 拷贝用户空间的数据到内核空间,同时计算校验和 */ err = skb_add_data_nocache(sk, skb, &msg->msg_iter, copy); if (err) goto do_fault; } else {/* 如果skb的线性数据区已经用完了,那么就使用分页区 */ bool merge = true; int i = skb_shinfo(skb)->nr_frags; struct page_frag *pfrag = sk_page_frag(sk); /* 检查分页是否有可用空间,如果没有就申请新的page。 * 如果申请失败,说明系统内存不足。 * 之后会设置TCP内存压力标志,减小发送缓冲区的上限,睡眠等待内存。 */ if (!sk_page_frag_refill(sk, pfrag)) goto wait_for_memory; if (!skb_can_coalesce(skb, i, pfrag->page, pfrag->offset)) { /* 不能追加时,检查分页数是否达到了上限,或者网卡不支持分散聚合。 * 如果是的话,就为此skb设置PSH标志,尽快地发送出去。 * 然后跳转到new_segment处申请新的skb,来继续填装数据。 */ if (i == MAX_SKB_FRAGS || !sg) { tcp_mark_push(tp, skb); goto new_segment; } merge = false; } copy = min_t(int, copy, pfrag->size - pfrag->offset); if (!sk_wmem_schedule(sk, copy)) goto wait_for_memory; /* 拷贝用户空间的数据到内核空间,同时计算校验和。 * 更新skb的长度字段,更新sock的发送队列大小和预分配缓存。 */ err = skb_copy_to_page_nocache(sk, &msg->msg_iter, skb, pfrag->page, pfrag->offset, copy); if (err) goto do_error; /* Update the skb. */ if (merge) {/* 如果把数据追加到最后一个分页了,更新最后一个分页的数据大小 */ skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy); } else { /* 初始化新增加的页 */ skb_fill_page_desc(skb, i, pfrag->page, pfrag->offset, copy); get_page(pfrag->page); } pfrag->offset += copy; } /* 如果这是第一次拷贝,取消PSH标志 */ if (!copied) TCP_SKB_CB(skb)->tcp_flags &= ~TCPHDR_PSH; tp->write_seq += copy; /* 更新发送队列的最后一个序号 */ TCP_SKB_CB(skb)->end_seq += copy;/* 更新skb的结束序号 */ tcp_skb_pcount_set(skb, 0); copied += copy; /* 下次拷贝的地址 */ if (!msg_data_left(msg)) { tcp_tx_timestamp(sk, skb); goto out; } /* 如果skb还可以继续填充数据,或者发送的是带外数据,或者使用TCP REPAIR选项, * 那么继续拷贝数据,先不发送。 */ if (skb->len < max || (flags & MSG_OOB) || unlikely(tp->repair)) continue; if (forced_push(tp)) { tcp_mark_push(tp, skb); __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH); } else if (skb == tcp_send_head(sk)) tcp_push_one(sk, mss_now); continue; wait_for_sndbuf: set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); wait_for_memory: /* 如果已经有数据复制到发送队列了,就尝试立即发送 */ if (copied) tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH, size_goal); /* 分两种情况: * 1. sock的发送缓存不足。等待sock有发送缓存可写事件,或者超时。 * 2. TCP层内存不足,等待2~202ms之间的一个随机时间。 */ err = sk_stream_wait_memory(sk, &timeo); if (err != 0) goto do_error; /* 睡眠后MSS和TSO段长可能会发生变化,重新计算 */ mss_now = tcp_send_mss(sk, &size_goal, flags); } out: /* 如果已经有数据复制到发送队列了,就尝试立即发送 */ if (copied) tcp_push(sk, flags, mss_now, tp->nonagle, size_goal); out_nopush: release_sock(sk); return copied + copied_syn; do_fault: if (!skb->len) { tcp_unlink_write_queue(skb, sk); /* It is the one place in all of TCP, except connection * reset, where we can be unlinking the send_head. */ tcp_check_send_head(sk, skb);/* 是否要撤销sk->sk_send_head */ sk_wmem_free_skb(sk, skb);/* 更新发送队列的大小和预分配缓存,释放skb */ } do_error: if (copied + copied_syn) goto out; out_err: err = sk_stream_error(sk, flags, err); /* make sure we wake any epoll edge trigger waiter */ if (unlikely(skb_queue_len(&sk->sk_write_queue) == 0 && err == -EAGAIN)) sk->sk_write_space(sk); release_sock(sk); return err; }
static inline int select_size(struct sock *sk) { struct tcp_sock *tp = tcp_sk(sk); int tmp = tp->mss_cache;/* Cached effective mss, not including SACKS --> 用于缓存下刚刚计算所得的MSS */ if (sk->sk_route_caps & NETIF_F_SG) { if (sk_can_gso(sk)) /* 什么东西? */ tmp = 0; else { int pgbreak = SKB_MAX_HEAD(MAX_TCP_HEADER); //128+128 if (tmp >= pgbreak && tmp <= pgbreak + (MAX_SKB_FRAGS - 1) * PAGE_SIZE) tmp = pgbreak; } } return tmp; }
未完待续...
相关文章推荐
- linux下安装Gnuplot
- linux目录作用详解(超详细,树状排版)
- Linux设备驱动之Ioctl控制
- linux多线程-----同步机制(互斥量、读写锁、条件变量)
- linux分享六:nohup与&,守护进程
- Linux IPC
- linux基本操作命令
- centos7.2中搭建ARM开发环境所需工具初体验
- Linux下SVN Server 的使用及权限配置权限
- SELinux配置property
- linux 知识体系精炼版
- 【Linux多进程同步】记录锁
- linux系统学习
- linux读书笔记(5章)
- 20135220谈愈敏Linux Book_5
- Linux:使用awk命令获取文本的某一行,某一列
- arm-linux-gcc.tgz安装
- linux mint 17.3 安装cuda7.5 toolkit
- Linux环境下jdk的配置
- linux中文件颜色,蓝色,白色等各自代表的含义