文章目录
- 1、write源码剖析
- 2、vfs层进行数据传输
- 3、socket层进行数据传输
- 4、tcp层进行数据传输
- 5、ip层进行数据传输
- 6、网络设备层进行数据传输
- 7、网卡驱动层进行数据传输
- 8、数据传输的整个流程
1、write源码剖析
系统调用原型
ssize_t write(int fildes, const void *buf, size_t nbyte);
fildes:文件描述符
buf:用户缓冲区,用于存放要写入的数据
nbyte:用户缓冲区的大小
返回值表示成功写入了多少字节的数据,因为write并不保证一定将数据全部写完
write系统调用实现位于/fs/read_write.c
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
size_t, count)
{
/*struct fd {
struct file *file;
unsigned int flags;
};
*/
//得到要操作的文件
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
//如果文件fd对应的文件不存在,直接返回
if (f.file) {
//需要写文件的位置
loff_t pos = file_pos_read(f.file);
//调用vfs(虚拟文件系统) 提供的写函数,ret表示成功写入的数据字节大小
ret = vfs_write(f.file, buf, count, &pos);
//如果写入成功,就需要更改文件的操作(写入)位置
if (ret >= 0)
file_pos_write(f.file, pos);
fdput_pos(f);
}
return ret;
}
2、vfs层进行数据传输
接着会调用vfs提供的写入函数
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
//判断当前的文件是否有写权限 f_mode存放了文件的读写权限,类似的可以用if (file->f_mode & FMODE_READ)判断文件是否有读权限
if (!(file->f_mode & FMODE_WRITE))
return -EBADF;
if (!(file->f_mode & FMODE_CAN_WRITE))
return -EINVAL;
//检查用户空间缓冲区是否可访问
if (unlikely(!access_ok(VERIFY_READ, buf, count)))
return -EFAULT;
//验证要写入的区域是否有效。如果验证失败,`ret` 将不为零
ret = rw_verify_area(WRITE, file, pos, count);
if (!ret) {
//用户写入的数据最大为MAX_RW_COUNT,因此write不保证一次性都能将用户数据写入完成
if (count > MAX_RW_COUNT)
count = MAX_RW_COUNT;
file_start_write(file);
ret = __vfs_write(file, buf, count, pos);
if (ret > 0) {
fsnotify_modify(file);
add_wchar(current, ret);
}
inc_syscw(current);
file_end_write(file);
}
return ret;
}
ssize_t __vfs_write(struct file *file, const char __user *p, size_t count,
loff_t *pos)
{
if (file->f_op->write)
return file->f_op->write(file, p, count, pos);
else if (file->f_op->write_iter)
return new_sync_write(file, p, count, pos);
else
return -EINVAL;
}
如果文件中的f_op存在write,就会调用write,否则如果存在write_iter,就会调用new_sync_write。
那么f_op是什么呢?
其实每个文件都对应着自己的file_operations,只有实现了里面的这些函数,文件才能进行相应的操作。举个例子,比如epoll,是所有的文件都能加入到epoll,让内核帮我们等待吗?当然不是,只有文件的file_operations实现了poll函数才能放到epoll中等待。
换句话讲,就是如果file_operations没有实现write或者write_iter,那么文件就无法写入,即使这个文件有读写权限也不行。
file_operations也体现了Linux下一切皆文件的含义。
对于socket文件,file_operations在net/socket.c中初始化的
static const struct file_operations socket_file_ops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read_iter = sock_read_iter,
.write_iter = sock_write_iter,
.poll = sock_poll,
.unlocked_ioctl = sock_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_sock_ioctl,
#endif
.mmap = sock_mmap,
.release = sock_close,
.fasync = sock_fasync,
.sendpage = sock_sendpage,
.splice_write = generic_splice_sendpage,
.splice_read = sock_splice_read,
};
socket_file_ops 中没有write,却有write_iter(sock_write_iter),但后面却调用new_sync_write函数
但是不用担心,最终还是会调用到sock_write_iter函数
想
static ssize_t new_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
/*struct iovec{
void __user *iov_base; //缓存区
__kernel_size_t iov_len; //缓冲区的大小
};*/
//将用户缓冲区赋值给struct iovec,这个结构体可以用于多缓冲区的写入(writev)和读取(readv),以减少系统调用,提高效率。 用户层可以传入多个缓冲区
struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len };
//struct kiocb通常用于 Linux 的异步 I/O 操作,当IO操作完成时,ki_complete函数将被调用
/*struct kiocb {
struct file *ki_filp; //文件
loff_t ki_pos; //偏移量
void (*ki_complete)(struct kiocb *iocb, long ret, long ret2); //回调函数
void *private; //用于存储与特定异步 I/O 操作相关的私有数据
int ki_flags; //用于存储与异步 I/O 操作相关的各种标志
};*/
struct kiocb kiocb;
//用于处理 I/O 向量(I/O vectors)的结构体。I/O 向量是一种用于表示不连续内存区域的数据结构,
//struct iov_iter 提供了迭代和遍历这些向量的方法。
struct iov_iter iter;
ssize_t ret;
//初始化上述的结构体
init_sync_kiocb(&kiocb, filp);
kiocb.ki_pos = *ppos;
iov_iter_init(&iter, WRITE, &iov, 1, len);
//调用write_iter(sock_write_iter)写入数据
ret = filp->f_op->write_iter(&kiocb, &iter);
BUG_ON(ret == -EIOCBQUEUED);
if (ret > 0)
*ppos = kiocb.ki_pos;
return ret;
}
3、socket层进行数据传输
位于net/socket.c
static ssize_t sock_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
//获取文件
struct file *file = iocb->ki_filp;
//获取文件对应的socket。在创建socket时,会创建对应的file,并将socket的指针放在file的private_data
struct socket *sock = file->private_data;
//这个结构体用于封装要发送的消息。这里将 iov_iter 和 kiocb 传递给 msg
struct msghdr msg = {.msg_iter = *from,
.msg_iocb = iocb};
ssize_t res;
if (iocb->ki_pos != 0)
return -ESPIPE;
if (file->f_flags & O_NONBLOCK)
msg.msg_flags = MSG_DONTWAIT;
if (sock->type == SOCK_SEQPACKET)
msg.msg_flags |= MSG_EOR;
//进一步调用sock_sendmsg
res = sock_sendmsg(sock, &msg);
*from = msg.msg_iter;
return res;
}
int sock_sendmsg(struct socket *sock, struct msghdr *msg)
{
int err = security_socket_sendmsg(sock, msg,
msg_data_left(msg));
//进一步调用sock_sendmsg_nosec
return err ?: sock_sendmsg_nosec(sock, msg);
}
static inline int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg)
{
//调用sock->ops->sendmsg
int ret = sock->ops->sendmsg(sock, msg, msg_data_left(msg));
BUG_ON(ret == -EIOCBQUEUED);
return ret;
}
这里的sock->ops在协议栈初始化时就已经确定了,指向了net/ipv4/af_inet.c中的inet_stream_ops
static struct inet_protosw inetsw_array[] =
{
{
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcp_prot,
.ops = &inet_stream_ops,
.flags = INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
...
}
const struct proto_ops inet_stream_ops = {
...
.sendmsg = inet_sendmsg,
.recvmsg = inet_recvmsg,
...
};
EXPORT_SYMBOL(inet_stream_ops);
其实调用的就是af_inet.c中的inet_sendmsg
int inet_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
struct sock *sk = sock->sk;
sock_rps_record_flow(sk);
/* We may need to bind the socket. */
if (!inet_sk(sk)->inet_num && !sk->sk_prot->no_autobind &&
inet_autobind(sk))
return -EAGAIN;
return sk->sk_prot->sendmsg(sk, msg, size);
}
接着调用sock中sk_prot的sendmsg
sk_prot也是在协议栈初始化时就已经确定了,指向了net/ipv4/af_inet.c中tcp_prot
struct proto tcp_prot = {
...
.recvmsg = tcp_recvmsg,
.sendmsg = tcp_sendmsg,
...
};
最终调用了tcp_sendmsg函数
4、tcp层进行数据传输
int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
struct sockcm_cookie sockc;
int flags, err, copied = 0;
int mss_now = 0, size_goal, copied_syn = 0;
bool process_backlog = false;
bool sg;
long timeo;
//对这个socket加锁
lock_sock(sk);
flags = msg->msg_flags;
if ((flags & MSG_FASTOPEN) && !tp->repair) {
//使用tcp fastopen来发送数据,允许客户端在SYN包中携带应用数据
err = tcp_sendmsg_fastopen(sk, msg, &copied_syn, size);
if (err == -EINPROGRESS && copied_syn > 0)
goto out;
else if (err)
goto out_err;
}
//计算超时时间,如果设置了MSG_DONTWAIT标记,则超时时间为0
timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
//检测TCP连接是否受到应用层限制的
tcp_rate_check_app_limited(sk); /* is sending application-limited? */
//只有ESTABLISHED和CLOSE_WAIT两个状态可以发送数据
//CLOSE_WAIT是收到对端FIN但是本端还没有发送FIN时所处状态,所以也可以发送数据
//TCP快速打开(被动端),它允许在连接完全建立之前发送数据
//除了上述的其他状态都需要等待连接完成,才能传输数据
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;
}
sockc.tsflags = sk->sk_tsflags;
if (msg->msg_controllen) {
err = sock_cmsg_send(sk, msg, &sockc);
if (unlikely(err)) {
err = -EINVAL;
goto out_err;
}
}
/* This should be in poll */
sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk);
//copied将记录本次能够写入TCP的字节数,如果成功,最终会返回给应用,初始化为0
copied = 0;
restart:
//每次发送都操作都会重新获取MSS值,保存到mss_now中
mss_now = tcp_send_mss(sk, &size_goal, flags);
err = -EPIPE;
//检查之前TCP连接是否发生过异常
if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
goto do_error;
sg = !!(sk->sk_route_caps & NETIF_F_SG);
//msg里保存着用户传入一个或者多个缓冲区,而msg_data_left(msg)返回的就是缓冲区数据量的大小
while (msg_data_left(msg)) {
int copy = 0;
int max = size_goal;
//获取发送队列中最后一个数据块,因为该数据块当前已保存数据可能还没有超过
//size_goal,所以可以继续往该数据块中填充数据
skb = tcp_write_queue_tail(sk);
//tcp_send_head()返回sk_send_head,指向发送队列中下一个要发送的数据包
//sk_send_head如果为NULL表示待发送的数据为空(可能有待确认数据)
//如果不为NULL,copy则表示还能往这个skb放入多少数据
if (tcp_send_head(sk)) {
if (skb->ip_summed == CHECKSUM_NONE)
max = mss_now;
copy = max - skb->len;
}
//copy <= 0说明发送队列最后一个skb数据量也达到了size_goal,不能继续填充数据了
if (copy <= 0 || !tcp_skb_can_collapse_to(skb)) {
bool first_skb;
new_segment:
//分配新的skb
//即将分配内存,首先检查内存使用是否会超限,如果会要先等待有内存可用
if (!sk_stream_memory_free(sk))
goto wait_for_sndbuf;
if (process_backlog && sk_flush_backlog(sk)) {
process_backlog = false;
goto restart;
}
//判断即将申请的skb是否是发送队列的第一个skb
first_skb = skb_queue_empty(&sk->sk_write_queue);
//申请skb
//分配skb,select_size()的返回值决定了skb的线性区域大小
skb = sk_stream_alloc_skb(sk,
select_size(sk, sg, first_skb),
sk->sk_allocation,
first_skb);
//分配失败,需要等待有剩余内存可用后才能继续发送
if (!skb)
goto wait_for_memory;
process_backlog = true;
//根据硬件能力确定TCP是否需要执行校验工作
if (sk_check_csum_caps(sk))
skb->ip_summed = CHECKSUM_PARTIAL;
//将新分配的skb加入到TCB的发送队列中,并且更新相关内存记账信息
skb_entail(sk, skb);
//设置本轮要拷贝的数据量为size_goal,因为该skb是新分配的,所以
//一定可以容纳这么多,但是具体能不能拷贝这么多,还需要看有没有这么
//多的数据要发送,
copy = size_goal;
max = size_goal;
if (tp->repair)
TCP_SKB_CB(skb)->sacked |= TCPCB_REPAIRED;
}
/* Try to append data to the end of skb. */
//如果skb可拷贝的数据量(copy)大于用户传入的数据量,那么就可以一次性全部拷贝完
if (copy > msg_data_left(msg))
copy = msg_data_left(msg);
//如果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 {
//merge用于指示是否可以将新拷贝的数据和当前skb的最后一个片段合并。如果
//它们在页面内刚好是连续的,那么就可以合并为一个片段
bool merge = true;
//i为当前skb中已经存在的分片个数
int i = skb_shinfo(skb)->nr_frags;
//page指向上一次分配的页面,off指向该页面中的偏移量
struct page_frag *pfrag = sk_page_frag(sk);
if (!sk_page_frag_refill(sk, pfrag))
goto wait_for_memory;
//该函数用于判断该skb最后一个片段是否就是当前页面的最后一部分,如果是,那么新拷贝的
//数据和该片段就可以合并,所以设置merge为1,这样可以节省一个frag_list[]位置
if (!skb_can_coalesce(skb, i, pfrag->page,
pfrag->offset)) {
if (i >= sysctl_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;
//拷贝copy字节数据到页面中
err = skb_copy_to_page_nocache(sk, &msg->msg_iter, skb,
pfrag->page,
pfrag->offset,
copy);
if (err)
goto do_error;
//更新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;
}
//如果本轮是第一次拷贝,清除PUSH标记
if (!copied)
TCP_SKB_CB(skb)->tcp_flags &= ~TCPHDR_PSH;
//write_seq记录的是发送队列中下一个要分配的序号,所以这里需要更新它
tp->write_seq += copy;
//更新该数据包的最后一个字节的序号
TCP_SKB_CB(skb)->end_seq += copy;
tcp_skb_pcount_set(skb, 0);
//累加已经拷贝字节数
copied += copy;
//如果所有要发送的数据都拷贝完了,并且设置了MSG_EOR,结束发送过程
if (!msg_data_left(msg)) {
if (unlikely(flags & MSG_EOR))
TCP_SKB_CB(skb)->eor = 1;
goto out;
}
//如果该skb没有填满,继续下一轮拷贝
if (skb->len < max || (flags & MSG_OOB) || unlikely(tp->repair))
continue;
//如果需要设置PUSH标志位,那么设置PUSH,然后发送数据包,可将PUSH可以让TCP尽快的发送数据
if (forced_push(tp)) {
tcp_mark_push(tp, skb);
//尽可能的将发送队列中的skb发送出去,禁用nalge
__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
} else if (skb == tcp_send_head(sk))
//当前只有这一个skb,也发送出去。因为只有一个,所以肯定也不存在拥塞,可以发送
tcp_push_one(sk, mss_now);
continue;
wait_for_sndbuf:
//设置套接字结构中发送缓存不足的标志
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
wait_for_memory:
//如果已经有数据拷贝到了发送缓存中,那么调用tcp_push()立即发送,这样可能可以
//让发送缓存快速的有剩余空间可用
if (copied)
tcp_push(sk, flags & ~MSG_MORE, mss_now,
TCP_NAGLE_PUSH, size_goal);
//等待有空余内存可以使用,如果timeo不为0,那么这一步会休眠
err = sk_stream_wait_memory(sk, &timeo);
if (err != 0)
goto do_error;
//睡眠后MSS可能发生了变化,所以重新计算
mss_now = tcp_send_mss(sk, &size_goal, flags);
}
out:
//如果拷贝了数据到发送缓存区,尝试进行一次发送
if (copied) {
tcp_tx_timestamp(sk, sockc.tsflags, tcp_write_queue_tail(sk));
tcp_push(sk, flags, mss_now, tp->nonagle, size_goal);
}
out_nopush:
release_sock(sk);
//返回本次写入的数据量
return copied + copied_syn;
do_fault:
//发生了错误,并且当前skb尚未包含任何数据,那么需要释放该skb
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 + copied_syn)
goto out;
out_err:
err = sk_stream_error(sk, flags, err);
if (unlikely(skb_queue_len(&sk->sk_write_queue) == 0 && err == -EAGAIN))
sk->sk_write_space(sk);
release_sock(sk);
return err;
}
tcp_sendmsg主要做了以下几件事:
1.判断套接字状态
2.将用户数据拷贝到skb中,优先考虑报文的线性区,然后是分页区,必要时需要使用新skb或者新分页来存放用户数据
3.根据具体的情况调用__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH)(禁用了nagle算法,可以发送多个skb)或者tcp_push_one(sk, mss_now)(使用nagle算法,只能发送一个skb)发送数据
无论是调用上面的哪个函数发送数据,但最终都会调用到tcp_write_xmit函数
static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
int push_one, gfp_t gfp)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
unsigned int tso_segs, sent_pkts;
int cwnd_quota;
int result;
bool is_cwnd_limited = false;
u32 max_segs;
//sent_pkts用来统计函数中已发送报文总数
sent_pkts = 0;
//检查是不是只发送一个skb buffer,即push one
if (!push_one) {
//执行MTU探测
result = tcp_mtu_probe(sk);
if (!result) {
return false;
} else if (result > 0) {
sent_pkts = 1;
}
}
max_segs = tcp_tso_segs(sk, mss_now);// 计算最大可发送的段数
while ((skb = tcp_send_head(sk))) {// 遍历发送队列
unsigned int limit;
/* 设置有关TSO的信息,包括GSO类型,GSO分段的大小等等。
* 这些信息是准备给软件TSO分段使用的。
* 如果网络设备不支持TSO,但又使用了TSO功能,
* 则报文在提交给网络设备之前,需进行软分段,即由代码实现TSO分段。
*/
tso_segs = tcp_init_tso_segs(skb, mss_now);
BUG_ON(!tso_segs);
if (unlikely(tp->repair) && tp->repair_queue == TCP_SEND_QUEUE) {
skb_mstamp_get(&skb->skb_mstamp);
goto repair;
}
/* 检查congestion windows, 可以发送几个segment */
/* 检测拥塞窗口的大小,如果为0,则说明拥塞窗口已满,目前不能发送。
* 拿拥塞窗口和正在网络上传输的包数目相比,如果拥塞窗口还大,
* 则返回拥塞窗口减掉正在网络上传输的包数目剩下的大小。
* 该函数目的是判断正在网络上传输的包数目是否超过拥塞窗口,
* 如果超过了,则不发送。
*/
cwnd_quota = tcp_cwnd_test(tp, skb);
if (!cwnd_quota) {
//push_one为2表示需要强制发送,此时就设置窗口大小为1,表示可以发送一个数据包
if (push_one == 2)
cwnd_quota = 1;
else
break;
}
//检测当前报文是否完全处于发送窗口内,如果是则可以发送,否则不能发送
if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))
break;
//tso_segs=1表示无需tso分段
if (tso_segs == 1) {
//根据nagle算法,计算是否需要发送数据
if (unlikely(!tcp_nagle_test(tp, skb, mss_now,
(tcp_skb_is_last(sk, skb) ?
nonagle : TCP_NAGLE_PUSH))))
break;
} else {
/* 当不止一个skb时,通过TSO计算是否需要延时发送 */
/* 如果需要TSO分段,则检测该报文是否应该延时发送。
* tcp_tso_should_defer()用来检测GSO段是否需要延时发送。
* 在段中有FIN标志,或者不处于open拥塞状态,或者TSO段延时超过2个时钟滴答,
* 或者拥塞窗口和发送窗口的最小值大于64K或三倍的当前有效MSS,在这些情况下会立即发送,
* 而其他情况下会延时发送,这样主要是为了减少软GSO分段的次数,以提高性能。
*/
if (!push_one &&
tcp_tso_should_defer(sk, skb, &is_cwnd_limited,
max_segs))
break;
}
limit = mss_now;
/* 在TSO分片大于1的情况下,且TCP不是URG模式。通过MSS计算发送数据的limit
* 以发送窗口和拥塞窗口的最小值作为分段段长*/
if (tso_segs > 1 && !tcp_urg_mode(tp))
limit = tcp_mss_split_point(sk, skb, mss_now,
min_t(unsigned int,
cwnd_quota,
max_segs),
nonagle);
/* 当skb的长度大于限制时,需要调用tso_fragment分片,如果分段失败则暂不发送 */
if (skb->len > limit &&
unlikely(tso_fragment(sk, skb, limit, mss_now, gfp)))
break;
//检查当前TCP发送队列的状态
//它可能会考虑队列的长度、当前的网络条件、拥塞窗口的大小以及其他相关因素,
//以确定是否应该继续发送数据或采取其他行动(如延迟发送)。这个函数的主要目的是避免队列过度拥塞,从而保持网络传输的稳定性和效率。
if (tcp_small_queue_check(sk, skb, 0))
break;
//调用tcp_transmit_skb()发送TCP段,其中第三个参数1表示是否需要克隆被发送的报文
if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
break;
repair:
/* 更新统计,并启动重传计时器 */
/* 调用tcp_event_new_data_sent()-->tcp_advance_send_head()更新sk_send_head,
* 即取发送队列中的下一个SKB。同时更新snd_nxt,即等待发送的下一个TCP段的序号,
* 然后统计发出但未得到确认的数据报个数。最后如果发送该报文前没有需要确认的报文,
* 则复位重传定时器,对本次发送的报文做重传超时计时。
*/
tcp_event_new_data_sent(sk, skb);
/* 更新struct tcp_sock中的snd_sml字段。snd_sml表示最近发送的小包(小于MSS的段)的最后一个字节序号,
* 在发送成功后,如果报文小于MSS,即更新该字段,主要用来判断是否启动nagle算法
*/
tcp_minshall_update(tp, mss_now, skb);
sent_pkts += tcp_skb_pcount(skb);
if (push_one)
break;
}
/* 如果本次有数据发送,则对TCP拥塞窗口进行检查确认。*/
if (likely(sent_pkts)) {
if (tcp_in_cwnd_reduction(sk))
tp->prr_out += sent_pkts;
/* Send one loss probe per tail loss episode. */
if (push_one != 2)
//丢包检测
tcp_schedule_loss_probe(sk);
//更新拥塞控制状态
is_cwnd_limited |= (tcp_packets_in_flight(tp) >= tp->snd_cwnd);
//验证拥塞窗口
tcp_cwnd_validate(sk, is_cwnd_limited);
return false;
}
/*
* 如果本次没有数据发送,则根据已发送但未确认的报文数packets_out和sk_send_head返回,
* packets_out不为零或sk_send_head为空都视为有数据发出,因此返回成功。
*/
return !tp->packets_out && tcp_send_head(sk);
}
接着会调用tcp_transmit_skb函数,填充tcp头部
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
gfp_t gfp_mask)
{
return __tcp_transmit_skb(sk, skb, clone_it, gfp_mask,
tcp_sk(sk)->rcv_nxt);
}
static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
const struct inet_connection_sock *icsk = inet_csk(sk);
struct inet_sock *inet;
struct tcp_sock *tp;
struct tcp_skb_cb *tcb;
struct tcp_out_options opts;
unsigned int tcp_options_size, tcp_header_size;
struct sk_buff *oskb = NULL;
struct tcp_md5sig_key *md5;
struct tcphdr *th;
int err;
BUG_ON(!skb || !tcp_skb_pcount(skb));
tp = tcp_sk(sk);
//根据传递进来的clone_it参数来确定是否需要克隆待发送的报文
if (clone_it) {
TCP_SKB_CB(skb)->tx.in_flight = TCP_SKB_CB(skb)->end_seq
- tp->snd_una;
oskb = skb;
//如果skb已经被clone,则只能复制该skb的数据到新分配的skb中
if (unlikely(skb_cloned(skb)))
skb = pskb_copy(skb, gfp_mask);
else
//clone新的skb
skb = skb_clone(skb, gfp_mask);
if (unlikely(!skb))
return -ENOBUFS;
}
skb_mstamp_get(&skb->skb_mstamp);
//获取INET层和TCP层的传输控制块、skb中的TCP私有数据块
inet = inet_sk(sk);
tcb = TCP_SKB_CB(skb);
memset(&opts, 0, sizeof(opts));
/*根据TCP选项重新调整TCP首部的长度。*/
/*判断当前TCP报文是否是SYN段,因为有些选项只能出现在SYN报文中,需做特别处理。*/
if (unlikely(tcb->tcp_flags & TCPHDR_SYN))
tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
else
tcp_options_size = tcp_established_options(sk, skb, &opts,
&md5);
/*tcp首部的总长度等于可选长度加上TCP头部。*/
tcp_header_size = tcp_options_size + sizeof(struct tcphdr);
skb->ooo_okay = sk_wmem_alloc_get(sk) < SKB_TRUESIZE(1);
/*调用skb_push()在数据部分的头部添加TCP首部,长度即为之前计算得到的那个tcp_header_size,实际上是把data指针往上移。*/
skb_push(skb, tcp_header_size);
skb_reset_transport_header(skb);
skb_orphan(skb);
// 将skb和sock关联起来,并设置skb的析构函数
skb->sk = sk;
skb->destructor = skb_is_tcp_pure_ack(skb) ? __sock_wfree : tcp_wfree;
// 从sock中设定skb的哈希值
skb_set_hash_from_sk(skb, sk);
// 增加skb占用的内存大小计数
atomic_add(skb->truesize, &sk->sk_wmem_alloc);
//填充TCP首部中的源端口source、目的端口dest、TCP报文的序号seq、确认序号ack_seq以及各个标志位
th = (struct tcphdr *)skb->data;
th->source = inet->inet_sport;
th->dest = inet->inet_dport;
th->seq = htonl(tcb->seq);
th->ack_seq = htonl(rcv_nxt);
*(((__be16 *)th) + 6) = htons(((tcp_header_size >> 2) << 12) |
tcb->tcp_flags);
th->check = 0;
th->urg_ptr = 0;
// 如果当前包含紧急指针的包在snd_una探测窗口之下,
// 则需要设置紧急指针。
if (unlikely(tcp_urg_mode(tp) && before(tcb->seq, tp->snd_up))) {
if (before(tp->snd_up, tcb->seq + 0x10000)) {
th->urg_ptr = htons(tp->snd_up - tcb->seq);
th->urg = 1;
} else if (after(tcb->seq + 0xFFFF, tp->snd_nxt)) {
th->urg_ptr = htons(0xFFFF);
th->urg = 1;
}
}
//构建TCP选项,例如窗口大小,时间戳等选项
tcp_options_write((__be32 *)(th + 1), tp, &opts);
// 设置skb的GSO类型
skb_shinfo(skb)->gso_type = sk->sk_gso_type;
//分两种情况设置TCP首部的接收窗口的大小
if (likely(!(tcb->tcp_flags & TCPHDR_SYN))) {
//如果不是SYN报文,则调用tcp_select_window()计算当前接收窗口的大小
th->window = htons(tcp_select_window(sk));
tcp_ecn_send(sk, skb, th, tcp_header_size);
} else {
//如果是SYN段,则设置接收窗口初始值为rcv_wnd
th->window = htons(min(tp->rcv_wnd, 65535U));
}
#ifdef CONFIG_TCP_MD5SIG
// 如果启用了MD5签名,计算MD5哈希,因为我们现在有了所需的全部数据
if (md5) {
sk_nocaps_add(sk, NETIF_F_GSO_MASK);
tp->af_specific->calc_md5_hash(opts.hash_location,
md5, sk, skb);
}
#endif
// 由底层网络函数完成skb的校验和
icsk->icsk_af_ops->send_check(sk, skb);
//如果skb中设置了TCPHDR_ACK标志,则记录ACK已发送的事件
if (likely(tcb->tcp_flags & TCPHDR_ACK))
tcp_event_ack_sent(sk, tcp_skb_pcount(skb), rcv_nxt);
// 如果skb的长度不只是TCP头部的长度,表示有数据被发送,
// 更新统计信息。
if (skb->len != tcp_header_size) {
tcp_event_data_sent(tp, sk);
tp->data_segs_out += tcp_skb_pcount(skb);
}
// 如果当前数据段的结束序列号在snd_nxt之后或与之相等,
// 或者如果是一个单独的序列号,更新发送的数据包统计。
if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq)
TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS,
tcp_skb_pcount(skb));
tp->segs_out += tcp_skb_pcount(skb);
// 设置skb中GSO(分段卸载)相关的字段
skb_shinfo(skb)->gso_segs = tcp_skb_pcount(skb);
skb_shinfo(skb)->gso_size = tcp_skb_mss(skb);
// 我们使用的时间戳应保持私有。
skb->tstamp.tv64 = 0;
// 清理我们对IP栈的"痕迹",重置skb的控制块。
memset(skb->cb, 0, max(sizeof(struct inet_skb_parm),
sizeof(struct inet6_skb_parm)));
//调用发送接口queue_xmit发送报文,进入到ip层,如果失败返回错误码。在TCP中该接口实现函数为ip_queue_xmit()
err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);
// 如果出现错误,调用tcp_enter_cwr函数进入拥塞窗口减少状态,并评估错误。
if (unlikely(err > 0)) {
tcp_enter_cwr(sk);
err = net_xmit_eval(err);
}
// 如果发送成功并且有原始的skb,更新相关统计数据。
if (!err && oskb) {
skb_mstamp_get(&oskb->skb_mstamp);
tcp_rate_skb_sent(sk, oskb);
}
return err;
}
5、ip层进行数据传输
icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl)函数就会调用到ip_output.c中的ip_queue_xmit函数,主要就是获取路由,填充ip头部信息
int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)
{
struct inet_sock *inet = inet_sk(sk);
struct net *net = sock_net(sk);
struct ip_options_rcu *inet_opt;
struct flowi4 *fl4; //这个结构体帮助内核处理IPv4网络数据包,包括路由选择、策略路由、流量控制等
struct rtable *rt; //这个结构体包含了关于特定路由条目的各种信息,例如目标地址、下一跳网关、网络接口等
struct iphdr *iph;
int res;
rcu_read_lock();
inet_opt = rcu_dereference(inet->inet_opt);
fl4 = &fl->u.ip4;
//如果还没有查询过路由,那么就先查询路由。对于TCP,大多数情况下都已经查询过了
//先从skb中查找路由信息
rt = skb_rtable(skb);
if (rt)
goto packet_routed;
//路由和套接字是关联的,一般来讲,一旦查询后,目的地址不发生变化,路由查询结果
//不会有变化,所以往往会将路由查询结果缓存到sk中,上面发现skb->dst中没有设置,
//再检查sk中缓存的路由信息是否依然有效,如果也无效,那么向路由子系统发起查询
rt = (struct rtable *)__sk_dst_check(sk, 0);
if (!rt) {//如果路由缓存项过期,则重新通过输出网络设备dev,目的地址,源地址等信息查找输出路由缓存项。
//如果查找到对应的路由缓存项,则将其缓存到输出控制块中,否则丢弃该数据包
__be32 daddr;
//如果有源路由选项,在查路由之前替换下目的地址
daddr = inet->inet_daddr;
if (inet_opt && inet_opt->opt.srr)
daddr = inet_opt->opt.faddr;
//根据这些参数查找路由信息
rt = ip_route_output_ports(net, fl4, sk,
daddr, inet->inet_saddr,
inet->inet_dport,
inet->inet_sport,
sk->sk_protocol,
RT_CONN_FLAGS(sk),
sk->sk_bound_dev_if);
if (IS_ERR(rt))
goto no_route;
sk_setup_caps(sk, &rt->dst);
}
skb_dst_set_noref(skb, &rt->dst);//如果没有过期则使用缓存再传输控制块中的路由缓存项
packet_routed:
//查找到输出路由以后,先进行严格源路由选项的处理,如果存在严格源路由选项,并且路由使用网关地址,则丢弃
if (inet_opt && inet_opt->opt.is_strictroute && rt->rt_uses_gateway)
goto no_route;
skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : 0));//现在只要要往哪里发送了,申请并创建IP头部
skb_reset_network_header(skb); //重新设置网络层头部指针skb->network_header
iph = ip_hdr(skb);
*((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
if (ip_dont_fragment(sk, &rt->dst) && !skb->ignore_df)//如果有设置不需要分片,设置DF标记位,否则初始化frag_off为0
iph->frag_off = htons(IP_DF);
else
iph->frag_off = 0;
iph->ttl = ip_select_ttl(inet, &rt->dst);//设置IP头部的TTL
iph->protocol = sk->sk_protocol;//设置IP头部的协议
//设置源IP和目的IP
ip_copy_addrs(iph, fl4);
/* Transport layer set skb->h.foo itself. */
//如果有选项,则需要给IP头部添加选项部分
if (inet_opt && inet_opt->opt.optlen) {
iph->ihl += inet_opt->opt.optlen >> 2;
//重新调整了IP首部长度,加上了选项部分的长度
ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0);
}
ip_select_ident_segs(net, skb, sk,
skb_shinfo(skb)->gso_segs ?: 1);
skb->priority = sk->sk_priority;
skb->mark = sk->sk_mark;
//对于单播包使用的是ip_output,多播使用的是ip_mc_output
res = ip_local_out(net, sk, skb);
rcu_read_unlock();
return res;
no_route:
rcu_read_unlock();
IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
kfree_skb(skb);
return -EHOSTUNREACH;
}
接着会调用ip_local_out函数
int ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{
int err;
err = __ip_local_out(net, sk, skb);
if (likely(err == 1))
err = dst_output(net, sk, skb);
return err;
}
在ip_local_out内又会调用__ip_local_out或者dst_output,如果调用了__ip_local_out,在它内部还是会调用到dst_output函数
int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{
// 获取指向sk_buff中IP报头的指针
struct iphdr *iph = ip_hdr(skb);
// 设置IP报头中的总长度字段为skb的长度,htons用于将主机字节顺序转换为网络字节顺序
iph->tot_len = htons(skb->len);
// 计算和填充IP报头的校验和
ip_send_check(iph);
/* 如果出口设备属于一个L3主设备,就将skb传递给它的处理函数
* l3mdev_ip_out负责处理skb,可能进行一些特定于该设备的处理
*/
skb = l3mdev_ip_out(sk, skb);
// 如果skb为空,说明处理不成功,返回0
if (unlikely(!skb))
return 0;
// 设置skb的协议字段为IP协议,htons用于将主机字节顺序转换为网络字节顺序
skb->protocol = htons(ETH_P_IP);
// 调用netfilter钩子,以便进行进一步的处理(例如,过滤,NAT等)
// nf_hook会根据配置决定是否处理skb或将其传递给下一个处理阶段
return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT,
net, sk, skb, NULL, skb_dst(skb)->dev,
dst_output);
}
static inline int dst_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
return skb_dst(skb)->output(net, sk, skb);
}
这里会调用到output函数,这里的output就是ip_output函数
int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
//获取网络设备
struct net_device *dev = skb_dst(skb)->dev;
//更新输出的统计信息
IP_UPD_PO_STATS(net, IPSTATS_MIB_OUT, skb->len);
//设置网络设备和协议
skb->dev = dev;
skb->protocol = htons(ETH_P_IP);
//如果 Netfilter 钩子函数没有返回 NF_DROP(表示丢弃数据包),
//那么 ip_finish_output 函数最终会被调用,以完成数据包的发送。
return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING,
net, sk, skb, NULL, dev,
ip_finish_output,
!(IPCB(skb)->flags & IPSKB_REROUTED));
}
紧接着会调用ip_finish_output
static int ip_finish_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
unsigned int mtu;
#if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM)
/* Policy lookup after SNAT yielded a new policy */
if (skb_dst(skb)->xfrm) {
IPCB(skb)->flags |= IPSKB_REROUTED;
return dst_output(net, sk, skb);
}
#endif
//获取数据包的目的地 MTU
mtu = ip_skb_dst_mtu(sk, skb);
//如果数据包是巨型帧,就使用ip_finish_output_gso来处理
if (skb_is_gso(skb))
return ip_finish_output_gso(net, sk, skb, mtu);
//skb的长度大于对端的mtu,或者设置了IPSKB_FRAG_PMTU,就会调用ip_fragment进行分片
//分完片后再调用ip_finish_output2
if (skb->len > mtu || (IPCB(skb)->flags & IPSKB_FRAG_PMTU))
return ip_fragment(net, sk, skb, mtu, ip_finish_output2);
return ip_finish_output2(net, sk, skb);
}
然后会调用ip_finish_output2函数
static int ip_finish_output2(struct net *net, struct sock *sk, struct sk_buff *skb)
{
struct dst_entry *dst = skb_dst(skb);
struct rtable *rt = (struct rtable *)dst;
struct net_device *dev = dst->dev;
unsigned int hh_len = LL_RESERVED_SPACE(dev);
struct neighbour *neigh;
u32 nexthop;
//如果与此数据包关联的路由是多播类型,则使用 IP_UPD_PO_STATS 宏来增加 OutMcastPkts 和 OutMcastOctets 计数
if (rt->rt_type == RTN_MULTICAST) {
IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTMCAST, skb->len);
} else if (rt->rt_type == RTN_BROADCAST)//如果广播路由,则会增加 OutBcastPkts 和 OutBcastOctets 计数。
IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTBCAST, skb->len);
//确保 skb 结构有足够的空间容纳需要添加的任何链路层头
if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
struct sk_buff *skb2;
//确保 skb 结构有足够的空间容纳需要添加的任何链路层头
skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
if (!skb2) {
kfree_skb(skb);
return -ENOMEM;
}
if (skb->sk)
skb_set_owner_w(skb2, skb->sk);
consume_skb(skb);
skb = skb2;
}
if (lwtunnel_xmit_redirect(dst->lwtstate)) {
int res = lwtunnel_xmit(skb);
if (res < 0 || res == LWTUNNEL_XMIT_DONE)
return res;
}
rcu_read_lock_bh();
//查询路由层找到下一跳的ip地址
nexthop = (__force u32) rt_nexthop(rt, ip_hdr(skb)->daddr);
//再根据下一跳的ip地址查找邻居缓存
neigh = __ipv4_neigh_lookup_noref(dev, nexthop);
if (unlikely(!neigh))
//如果未找到,则调用__neigh_create 创建一个邻居,内部就是ARP相关的操作
neigh = __neigh_create(&arp_tbl, &nexthop, dev, false);
if (!IS_ERR(neigh)) {
//调用 dst_neigh_output 继续传递 skb
int res = dst_neigh_output(dst, neigh, skb);
rcu_read_unlock_bh();
return res;
}
rcu_read_unlock_bh();
net_dbg_ratelimited("%s: No header cache and no neighbour!\n",
__func__);
kfree_skb(skb);
return -EINVAL;
}
紧接着会调用到dst_neigh_output函数
static inline int dst_neigh_output(struct dst_entry *dst, struct neighbour *n,
struct sk_buff *skb)
{
const struct hh_cache *hh;
//如果路由条目 dst 有一个待确认的标志,则将其清除,并更新邻居 n 的确认时间戳
if (dst->pending_confirm) {
unsigned long now = jiffies;
dst->pending_confirm = 0;
if (n->confirmed != now)
n->confirmed = now;
}
//获取下一跳ip地址所对应的硬件头信息(内部包含mac地址)
hh = &n->hh;
//如果邻居处于已连接状态(NUD_CONNECTED)并且硬件头缓存中有有效的数据,则调用neigh_hh_output发送数据包
if ((n->nud_state & NUD_CONNECTED) && hh->hh_len)
return neigh_hh_output(hh, skb);
else //否则调用邻居条目 n 中的 output 方法来发送数据包
return n->output(n, skb);
}
以上两种情况,最后都会到 dev_queue_xmit,它将 skb 发送给 Linux 网络设备子系统,在它进入设备驱动程序层之前将对其进行更多处理。让我们沿着 neigh_hh_output 和 n->output 代码继续向下,直到达到 dev_queue_xmit
neigh_hh_output函数
static inline int neigh_hh_output(const struct hh_cache *hh, struct sk_buff *skb)
{
unsigned int seq;
int hh_len;
//填充mac地址
do {
seq = read_seqbegin(&hh->hh_lock);
hh_len = hh->hh_len;
if (likely(hh_len <= HH_DATA_MOD)) {
memcpy(skb->data - HH_DATA_MOD, hh->hh_data, HH_DATA_MOD);
} else {
int hh_alen = HH_DATA_ALIGN(hh_len);
memcpy(skb->data - hh_alen, hh->hh_data, hh_alen);
}
} while (read_seqretry(&hh->hh_lock, seq));
//更新 skb 内指向数据缓冲区的指针和数据长度
skb_push(skb, hh_len);
//调用 dev_queue_xmit 将 skb 传递给 Linux 网络设备子系统
return dev_queue_xmit(skb);
}
如果邻居已经关闭,则会调用n->output,也就是struct neighbour中的output,这里的output就是neigh_ops中的output,而neigh_ops的初始化又是在net/ipv4/arp.c中
static const struct neigh_ops arp_generic_ops = {
.family = AF_INET,
.solicit = arp_solicit,
.error_report = arp_error_report,
.output = neigh_resolve_output,
.connected_output = neigh_connected_output,
};
最终调用的是neigh_resolve_output
int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb)
{
int rc = 0;
//neigh_event_send() 函数发送一个查询邻居节点 MAC 地址的 ARP 请求
//在这个函数内部会把skb添加到邻居节点信息对象的 arp_queue 队列中,等待获取到邻居节点 MAC 地址后重新发送这个数据包
if (!neigh_event_send(neigh, skb)) {
int err;
struct net_device *dev = neigh->dev;
unsigned int seq;
// 网络设备可以使用L2帧头缓存(dev->header_ops->cache),但是还没有建立缓存(dst->hh)
if (dev->header_ops->cache && !neigh->hh.hh_len)
neigh_hh_init(neigh);
do {
__skb_pull(skb, skb_network_offset(skb));
seq = read_seqbegin(&neigh->ha_lock);
//设置数据包的目标 MAC 地址
err = dev_hard_header(skb, dev, ntohs(skb->protocol),
neigh->ha, NULL, skb->len);
} while (read_seqretry(&neigh->ha_lock, seq));
// 首部构造成功,输出数据包
if (err >= 0)
rc = dev_queue_xmit(skb);
else
goto out_kfree_skb;
}
out:
return rc;
out_kfree_skb:
rc = -EINVAL;
kfree_skb(skb);
goto out;
}
6、网络设备层进行数据传输
如果上述过程一次顺利,那么就会调用到网络设备层的dev_queue_xmit,将数据做进一步处理
int dev_queue_xmit(struct sk_buff *skb)
{
return __dev_queue_xmit(skb, NULL);
}
static int __dev_queue_xmit(struct sk_buff *skb, void *accel_priv)
{
struct net_device *dev = skb->dev;
//struct netdev_queue用于表示一个网络设备的发送队列
struct netdev_queue *txq;
//struct Qdisc负责决定如何排队和发送数据包
struct Qdisc *q;
int rc = -ENOMEM;
//设置mac的头部偏移
skb_reset_mac_header(skb);
if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_SCHED_TSTAMP))
__skb_tstamp_tx(skb, NULL, skb->sk, SCM_TSTAMP_SCHED);
rcu_read_lock_bh();
skb_update_prio(skb);
qdisc_pkt_len_init(skb);
#ifdef CONFIG_NET_CLS_ACT
skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_EGRESS);
# ifdef CONFIG_NET_EGRESS
if (static_key_false(&egress_needed)) {
skb = sch_handle_egress(skb, &rc, dev);
if (!skb)
goto out;
}
# endif
#endif
//现在已经处于mac层,不需要对应的路由信息,因此可以将路由信息删除
if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
skb_dst_drop(skb);
else
skb_dst_force(skb);
//获取网络设备的队列
txq = netdev_pick_tx(dev, skb, accel_priv);
//从netdev_queue结构上取下设备的qdisc
q = rcu_dereference_bh(txq->qdisc);
trace_net_dev_queue(skb);
/*如果Qdisc有对应的enqueue规则,就会调用__dev_xmit_skb,进入带有拥塞的控制的Flow,注意这个地方,虽然是走拥塞控制的
*Flow但是并不一定非得进行enqueue操作,只有Busy的状况下,才会走Qdisc的enqueue/dequeue操作进行
*/
if (q->enqueue) {
rc = __dev_xmit_skb(skb, q, dev, txq);
goto out;
}
//此处是设备没有Qdisc的,实际上没有enqueue/dequeue的规则,无法进行拥塞控制的操作,则直接发送
if (dev->flags & IFF_UP) {
// 当前CPU编号
int cpu = smp_processor_id(); /* ok because BHs are off */
if (txq->xmit_lock_owner != cpu) {
if (unlikely(__this_cpu_read(xmit_recursion) >
XMIT_RECURSION_LIMIT))
goto recursion_alert;
skb = validate_xmit_skb(skb, dev);
if (!skb)
goto out;
HARD_TX_LOCK(dev, txq, cpu);
//这个地方判断一下txq不是stop状态,那么就直接调用dev_hard_start_xmit函数来发送数据
if (!netif_xmit_stopped(txq)) {
__this_cpu_inc(xmit_recursion);
skb = dev_hard_start_xmit(skb, dev, txq, &rc);
__this_cpu_dec(xmit_recursion);
// 如果发送完成,就解锁
if (dev_xmit_complete(rc)) {
HARD_TX_UNLOCK(dev, txq);
goto out;
}
}
HARD_TX_UNLOCK(dev, txq);
net_crit_ratelimited("Virtual device %s asks to queue packet!\n",
dev->name);
} else {
recursion_alert:
net_crit_ratelimited("Dead loop on virtual device %s, fix it urgently!\n",
dev->name);
}
}
rc = -ENETDOWN;
rcu_read_unlock_bh();
atomic_long_inc(&dev->tx_dropped);
kfree_skb_list(skb);
return rc;
out:
rcu_read_unlock_bh();
return rc;
}
先检查是否有enqueue的规则,如果有即调用__dev_xmit_skb进入拥塞控制的flow,如果没有且txq处于On的状态,那么就调用dev_hard_start_xmit直接发送到driver,好 那先分析带Qdisc策略的flow 进入__dev_xmit_skb
static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q,
struct net_device *dev,
struct netdev_queue *txq)
{
spinlock_t *root_lock = qdisc_lock(q);
struct sk_buff *to_free = NULL;
bool contended;
int rc;
qdisc_calculate_pkt_len(skb, q);
/*
* Heuristic to force contended enqueues to serialize on a
* separate lock before trying to get qdisc main lock.
* This permits qdisc->running owner to get the lock more
* often and dequeue packets faster.
*/
contended = qdisc_is_running(q);
if (unlikely(contended))
spin_lock(&q->busylock);
spin_lock(root_lock);
//主要是判定Qdisc的state: __QDISC_STATE_DEACTIVATED,如果处于非活动的状态,就DROP这个包,返回NET_XMIT_DROP
if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) {
__qdisc_drop(skb, &to_free);
rc = NET_XMIT_DROP;
//(q->flags & TCQ_F_CAN_BYPASS)表示qdisc允许数据包绕过排队系统
//!qdisc_qlen(q)表示qdisc的队列中没有等待发送的数据
//qdisc_run_begin(q)判断队列是否是运行状态,如果是,返回true,否则将状态设置为运行状态,然后返回false
//网络没有拥塞
} else if ((q->flags & TCQ_F_CAN_BYPASS) && !qdisc_qlen(q) &&
qdisc_run_begin(q)) {
/*
* This is a work-conserving queue; there are no old skbs
* waiting to be sent out; and the qdisc is not running -
* xmit the skb directly.
*/
//增加qdisc发送的字节数和数据包数
qdisc_bstats_update(q, skb);
//发送数据包
if (sch_direct_xmit(skb, q, dev, txq, root_lock, true)) {
if (unlikely(contended)) {
spin_unlock(&q->busylock);
contended = false;
}
//如果发送数据包成功,继续发生
__qdisc_run(q);
//如果发送数据包失败,那么会调用qdisc_run_end将队列的状态设置为停止状态
} else
qdisc_run_end(q);
rc = NET_XMIT_SUCCESS;
//网络拥塞
} else {
//将数据包加入队列
rc = q->enqueue(skb, q, &to_free) & NET_XMIT_MASK;
//如果Qdisc q不是运行状态,就设置成运行状态
if (qdisc_run_begin(q)) {
if (unlikely(contended)) {
spin_unlock(&q->busylock);
contended = false;
}
__qdisc_run(q);
}
}
spin_unlock(root_lock);
if (unlikely(to_free))
kfree_skb_list(to_free);
if (unlikely(contended))
spin_unlock(&q->busylock);
return rc;
}
如果发送了网络拥塞,则会将数据放到设备的发送队列中,如果没有发生网络拥塞,那么就会调用sch_direct_xmit函数发生数据
下面来分析sch_direct_xmit,这个函数可能传输几个数据包,因为在不经过queue状况下和经过queue的状况下都会调通过这个函数发送,如果是queue状况,肯定是能够传输多个数据包了
int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q,
struct net_device *dev, struct netdev_queue *txq,
spinlock_t *root_lock, bool validate)
{
int ret = NETDEV_TX_BUSY;
/* And release qdisc */
//调用该函数时队列策略的队列锁已经被锁了,现在解锁
spin_unlock(root_lock);
if (validate)
skb = validate_xmit_skb_list(skb, dev);
//如果这个skb有效
if (likely(skb)) {
//取得发送队列的锁
HARD_TX_LOCK(dev, txq, smp_processor_id());
//如果发送队列已经开启
if (!netif_xmit_frozen_or_stopped(txq))
/*如果说txq被stop,即置位QUEUE_STATE_ANY_XOFF_OR_FROZEN,就直接ret = NETDEV_TX_BUSY
*如果说txq 正常运行,那么直接调用dev_hard_start_xmit发送数据包*/
skb = dev_hard_start_xmit(skb, dev, txq, &ret);
HARD_TX_UNLOCK(dev, txq);
} else {
spin_lock(root_lock);
return qdisc_qlen(q);
}
spin_lock(root_lock);
//进行返回值处理! 如果ret < NET_XMIT_MASK 为true 否则 flase
if (dev_xmit_complete(ret)) {
//这个地方需要注意可能有driver的负数的case,也意味着这个skb被drop了
ret = qdisc_qlen(q);
} else {
if (unlikely(ret != NETDEV_TX_BUSY))
net_warn_ratelimited("BUG %s code %d qlen %d\n",
dev->name, ret, q->q.qlen);
//发生Tx Busy的时候,重新进行requeu
ret = dev_requeue_skb(skb, q);
}
//如果txq stop并且ret !=0 说明已经无法发送数据包了ret = 0
if (ret && netif_xmit_frozen_or_stopped(txq))
ret = 0;
return ret;
}
如果前面调用dev_hard_start_xmit发送数据失败,则会调用dev_requeue_skb把skb放到发送队列中,并设置对应的软中断,当网卡不忙时,就会触发,然后再次发送数据
继续看dev_hard_start_xmit,这个函数比较简单,调用xmit_one来发送一个到多个数据包了
struct sk_buff *dev_hard_start_xmit(struct sk_buff *first, struct net_device *dev,
struct netdev_queue *txq, int *ret)
{
struct sk_buff *skb = first;
int rc = NETDEV_TX_OK;
while (skb) {
struct sk_buff *next = skb->next;
skb->next = NULL;
//将此数据包送到driver Tx函数,因为dequeue的数据也会从这里发送,所以会有netx
rc = xmit_one(skb, dev, txq, next != NULL);
//如果发送不成功,next还原到skb->next 退出
if (unlikely(!dev_xmit_complete(rc))) {
skb->next = next;
goto out;
}
/*如果发送成功,把next置给skb,一般的next为空 这样就返回,如果不为空就继续发!*/
skb = next;
//如果txq被stop,并且skb需要发送,就产生TX Busy的问题
if (netif_xmit_stopped(txq) && skb) {
rc = NETDEV_TX_BUSY;
break;
}
}
out:
*ret = rc;
return skb;
}
对于xmit_one这个来讲比较简单了,下面代码中列出了xmit_one, netdev_start_xmit,__netdev_start_xmit 这个三个函数,其目的就是将封包送到driver的tx函数了
static int xmit_one(struct sk_buff *skb, struct net_device *dev,
struct netdev_queue *txq, bool more)
{
unsigned int len;
int rc;
/*如果有抓包的工具的话,这个地方会进行抓包,such as Tcpdump*/
if (!list_empty(&ptype_all) || !list_empty(&dev->ptype_all))
dev_queue_xmit_nit(skb, dev);
len = skb->len;
trace_net_dev_start_xmit(skb, dev);
/*调用netdev_start_xmit,快到driver的tx函数了*/
rc = netdev_start_xmit(skb, dev, txq, more);
trace_net_dev_xmit(skb, rc, dev, len);
return rc;
}
static inline netdev_tx_t netdev_start_xmit(struct sk_buff *skb, struct net_device *dev,
struct netdev_queue *txq, bool more)
{
//获取对应网卡驱动的操作函数集
const struct net_device_ops *ops = dev->netdev_ops;
int rc;
/*__netdev_start_xmit 里面就完全是使用driver 的ops去发包了,其实到此为止,一个skb已经从netdevice
*这个层面送到driver层了,接下来会等待driver的返回*/
rc = __netdev_start_xmit(ops, skb, dev, more);
/*如果返回NETDEV_TX_OK,那么会更新下Txq的trans时间戳哦,txq->trans_start = jiffies;*/
if (rc == NETDEV_TX_OK)
txq_trans_update(txq);
return rc;
}
static inline netdev_tx_t __netdev_start_xmit(const struct net_device_ops *ops,
struct sk_buff *skb, struct net_device *dev,
bool more)
{
skb->xmit_more = more ? 1 : 0;
//调用网卡驱动的发送函数,真正的通过硬件发送数据
return ops->ndo_start_xmit(skb, dev);
}
7、网卡驱动层进行数据传输
通过调用ndo_start_xmit函数,数据才能真正被网卡发送出去。对应不同的网卡驱动,ndo_start_xmit都有各自的实现方式,也是必须要实现的,这样才能和上层的协议栈衔接起来
以DM9000驱动为例
/*分析DM9000发生数据函数**/
/*
* Hardware start transmission.
* Send a packet to media from the upper layer.
*/
static int
dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
unsigned long flags;
board_info_t *db = netdev_priv(dev);
dm9000_dbg(db, 3, "%s:\n", __func__);
if (db->tx_pkt_cnt > 1)
return NETDEV_TX_BUSY;
spin_lock_irqsave(&db->lock, flags);
/* Move data to DM9000 TX RAM */
//写数据到DM9000 Tx RAM中, 写地址自动增加
writeb(DM9000_MWCMD, db->io_addr);
/*将skb中的数据写入寄存器,然后发送字节改变*/
(db->outblk)(db->io_data, skb->data, skb->len);
dev->stats.tx_bytes += skb->len;
db->tx_pkt_cnt++;
/*第一个发送包立刻发送, 第二个排列到发送队列中去*/
/* TX control: First packet immediately send, second packet queue */
if (db->tx_pkt_cnt == 1) {
dm9000_send_packet(dev, skb->ip_summed, skb->len);
} else {
/* Second packet */
db->queue_pkt_len = skb->len;
db->queue_ip_summed = skb->ip_summed;
/*告诉网络协议栈,停止发送数据。*/
netif_stop_queue(dev);
}
spin_unlock_irqrestore(&db->lock, flags);
/* free this SKB */
/*释放skb*/
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
/*当发送完成后,会触发一次发送完成的中断。 当然要去中断处理函数中*/
static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
board_info_t *db = netdev_priv(dev);
int int_status;
unsigned long flags;
u8 reg_save;
dm9000_dbg(db, 3, "entering %s\n", __func__);
/* A real interrupt coming */
/* holders of db->lock must always block IRQs */
spin_lock_irqsave(&db->lock, flags);
/* Save previous register address */
reg_save = readb(db->io_addr); //存储以前的地址
/* Disable all interrupts */ //屏蔽所有中断
iow(db, DM9000_IMR, IMR_PAR);
/* Got DM9000 interrupt status */
int_status = ior(db, DM9000_ISR); /* Got ISR */
iow(db, DM9000_ISR, int_status); /* Clear ISR status */
if (netif_msg_intr(db))
dev_dbg(db->dev, "interrupt status %02x\n", int_status);
/* Received the coming packet */ //接受中断发生
if (int_status & ISR_PRS)
dm9000_rx(dev);
/* Trnasmit Interrupt check */
if (int_status & ISR_PTS) //检测是否发送完成
dm9000_tx_done(dev, db);
if (db->type != TYPE_DM9000E) {
if (int_status & ISR_LNKCHNG) {
/* fire a link-change request */
schedule_delayed_work(&db->phy_poll, 1);
}
}
/* Re-enable interrupt mask */
iow(db, DM9000_IMR, db->imr_all); //使能中断
/* Restore previous register address */
writeb(reg_save, db->io_addr); //恢复以前的地址
spin_unlock_irqrestore(&db->lock, flags);
return IRQ_HANDLED;
}
/*分析中断发送完成处理函数*/
static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
{
int tx_status = ior(db, DM9000_NSR); /* Got TX status */ //得到发送的状态
if (tx_status & (NSR_TX2END | NSR_TX1END)) {
/* One packet sent complete */ //是一个包就发送完成
db->tx_pkt_cnt--;
dev->stats.tx_packets++;
if (netif_msg_tx_done(db))
dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status);
/* Queue packet check & send */ //如果超过2个,进入队列。发送
if (db->tx_pkt_cnt > 0)
dm9000_send_packet(dev, db->queue_ip_summed,
db->queue_pkt_len);
netif_wake_queue(dev); //唤醒发送队列
}
}
/*
* 总结:
1. 通知网络协议栈,停止发送队列
2. 写skb数据到寄存器中去
3. 释放skb资源
4. 当发送完成后,唤醒发送队列
*/