当应用层程序调用send发送数据之后,相应系统调用为sys_sendmsg,在socket文件系统中,该调用指向inet_sendmsg。
不同的传输层协议inet_sendmsg的proto指向的操作也不一样,而对于TCP协议,inet_sendmsg指向tcp_sendmsg函数,
所以tcp的发送函数接口是tcp_sendmsg。在介绍tcp_sendmsg之前需要介绍tcp的发送队列,在sock结构中有两个和发送队列
有关的变量,分别为sk_write_queue和sk_send_head。sk_write_queue指向整个发送队列,其中包括了已发送未被确认的数
据以及还未发送的数据;sk_send_head指向当前要发送的数据,即下一个要发送的SKB,结构示意图如下:
上图表示当前发送队列有三个SKB,已经发送了SKB1但是还未被确认,下一个要发送的是SKB2。TCP中的最主要到发送函数
是tcp_transmit_skb,所有的SKB都经过该函数进行发送。
了解了发送队列之后再来看看tcp_sendmsg,该函数的实现如下(linux-2.6.38):
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 sg, err, copied;
long timeo;
//首先对sock加锁防止下半段中断访问
lock_sock(sk);
TCP_CHECK_TIMER(sk);
//对于阻塞的发送模式还需设置超时时间
flags = msg->msg_flags;
timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
//只有在ESTABLISHED和CLOSE_WAIT状态下对方才能够接收数据,尝试等待连接的建立
if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
goto out_err;
clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
//获取当前的MSS大小
mss_now = tcp_send_mss(sk, &size_goal, flags);
/*iov可以看做是一个数组,iovlen表示数组的长度。每一项都是一个数据段。*/
/*struct msghdr和struct iovec在内核的消息通信机制里很常见*/
iovlen = msg->msg_iovlen;
iov = msg->msg_iov;//获取第一个iovec
copied = 0;
err = -EPIPE;
if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
goto out_err;
sg = sk->sk_route_caps & NETIF_F_SG;
/*循环操作iov,TCP是面向字节流而不是面向数据报的,可以发生粘包现象*/
/*如果前一个SKB的小于MSS,新的数据可以将部分数据填入旧的SKB中*/
while (--iovlen >= 0) {
size_t seglen = iov->iov_len; //读取该段数据的长度
unsigned char __user *from = iov->iov_base; //指向数据区域
iov++;
while (seglen > 0) {
int copy = 0;
int max = size_goal;
//得到发送队列的尾部的skb,因为尾部才可能有剩余空间
skb = tcp_write_queue_tail(sk);
if (tcp_send_head(sk)) { //判断是否还有未发送的数据,即sk_send_head是否非空,
if (skb->ip_summed == CHECKSUM_NONE)
max = mss_now;
copy = max - skb->len;
}
//不能够填充到sk_send_head指向的SKB的话,新建一个SKB
if (copy <= 0) {
new_segment:
//首先判断发送队列总长度是否超过发送缓冲区上限,即sk->sk_wmem_queued < sk->sk_sndbuf
if (!sk_stream_memory_free(sk))
goto wait_for_sndbuf; //设置SOCK_NOSPACE标志位后等待空间
//分配一个新的SKB封装新的数据,失败的话也要等待空间,select_size返回一个skb连续数据域的长度,在2.6.38中返回的是0,表示数据全部存储在skb尾部的frags中
skb = sk_stream_alloc_skb(sk,select_size(sk, sg),sk->sk_allocation);
if (!skb)
goto wait_for_memory;
if (sk->sk_route_caps & NETIF_F_ALL_CSUM)
skb->ip_summed = CHECKSUM_PARTIAL;
skb_entail(sk, skb); //将新的SKB插入发送队列尾部
copy = size_goal;
max = size_goal;
}
if (copy > seglen)
copy = seglen;
if (skb_tailroom(skb) > 0) { //判断skb的连续数据存储区是否还有空间
if (copy > skb_tailroom(skb)) //不够长的话放入部分数据
copy = skb_tailroom(skb);
if ((err = skb_add_data(skb, from, copy)) != 0)
goto do_fault;
} else {
//这一段是将数据复制到skb尾部的skb_shared_info中的frags,比较复杂,这样做的目的是支持物理上不连续的数据,减少合并操作提高效率
int merge = 0;
int i = skb_shinfo(skb)->nr_frags;
struct page *page = TCP_PAGE(sk);//返回sk最近操作的page,每个frag的数据都是放在page中
int off = TCP_OFF(sk);
if (skb_can_coalesce(skb, i, page, off) &&
off != PAGE_SIZE) {
/* We can extend the last page
* fragment. */
merge = 1;
} else if (i == MAX_SKB_FRAGS || !sg) {
/* 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);
goto new_segment;
} else if (page) {
if (off == PAGE_SIZE) { //PAGE被填满了,重置
put_page(page);
TCP_PAGE(sk) = page = NULL;
off = 0;
}
} else {
off = 0;
if (copy > PAGE_SIZE - off) //不够空间的话先拷贝部分数据
copy = PAGE_SIZE - off;
if (!sk_wmem_schedule(sk, copy))
goto wait_for_memory;
if (!page) {
/* Allocate new cache page. */
if (!(page = sk_stream_alloc_page(sk)))
goto wait_for_memory;
}
//具体的数据拷贝过程
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)) {
TCP_PAGE(sk) = page;
TCP_OFF(sk) = 0;
}
goto do_error;
}
/* Update the skb. */
if (merge) {
skb_shinfo(skb)->frags[i - 1].size +=
copy;
} else {
skb_fill_page_desc(skb, i, page, off, copy);
if (TCP_PAGE(sk)) {
get_page(page);
} else if (off + copy < PAGE_SIZE) {
get_page(page);
TCP_PAGE(sk) = page;
}
}
TCP_OFF(sk) = off + copy;
}
//如果这一次的数据超过了最大窗口的一半,设置PUSH标志调用__tcp_push_pending_frames发送sk_send_head的所有数据
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)) //否则如果只有当前的SKB未发送过,调用tcp_push_one发送sk_send_head的第一个数据,即当前SKB
tcp_push_one(sk, mss_now);
//上面两个判断的结果最终都会调用tcp_write_xmit函数,这个函数会从传入的sock得到sk_send_head,发送该队列上的所有数据
//因为第二个判断只有一个SKB,所以只发送一个SKB.但是两者传入tcp_write_xmit的参数不一样。前者会发送MTU探测包,后者只发送一个数据包
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);
if ((err = sk_stream_wait_memory(sk, &timeo)) != 0) //等待内存分配
goto do_error;
mss_now = tcp_send_mss(sk, &size_goal, flags);
}
}
out:
if (copied)
tcp_push(sk, flags, mss_now, tp->nonagle);
TCP_CHECK_TIMER(sk);
release_sock(sk);
return copied;
do_fault:
if (!skb->len) {
tcp_unlink_write_queue(skb, sk);
tcp_check_send_head(sk, skb);
sk_wmem_free_skb(sk, 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;
}