这篇笔记记录的TCP协议对发送数据相关系统调用内核实现,虽然发送相关的系统调用接口由很多,但是到了TCP协议层,都统一由tcp_sendmsg()处理。
1. 发送队列
在看tcp_sendmsg()代码之前,有必要先看下发送队列的组织和使用方式。
注:要特别注意的是,sk_send_head跟踪的是那些尚未发送过的数据,不包括重传数据。
2. tcp_sendmsg()
该函数要完成的工作就是将应用程序要发送的数据组织成skb,然后尽可能的发出去。
@msg:要发送的数据;
@size:本次要发送的数据量
int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
size_t size)
{
struct sock *sk = sock->sk;
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);
//计算超时时间,如果设置了MSG_DONTWAIT标记,则超时时间为0
flags = msg->msg_flags;
timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
//只有ESTABLISHED和CLOSE_WAIT两个状态可以发送数据,其它状态需要等待连接完成;
//CLOSE_WAIT是收到对端FIN但是本端还没有发送FIN时所处状态,所以也可以发送数据
if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
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值,保存到mss_now中
mss_now = tcp_current_mss(sk, !(flags&MSG_OOB));
//获取一个skb可以容纳的数据量。如果不支持TSO,那么该值就是MSS,否则是MSS的整数倍
size_goal = tp->xmit_size_goal;
//应用要发送的数据被保存在msg中,以数组方式组织,msg_iovlen为数组大小,msg_iov为数组第一个元素
iovlen = msg->msg_iovlen;
iov = msg->msg_iov;
//copied将记录本次能够写入TCP的字节数,如果成功,最终会返回给应用,初始化为0
copied = 0;
//检查之前TCP连接是否发生过异常
err = -EPIPE;
if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
goto do_error;
//外层循环用来遍历msg_iov数组
while (--iovlen >= 0) {
//msg_iov数组中每个元素包含的数据量都可以不同,每个元素自己有多少数据量记录在自己的iov_len字段中
int seglen = iov->iov_len;
//from指向要拷贝的数据起点
unsigned char __user *from = iov->iov_base;
//iov指向下一个数组元素
iov++;
//内层循环用于拷贝一个数组元素
while (seglen >