Linux网络协议栈从应用层到内核层③

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;
	struct board_info *db = netdev_priv(dev);
	int int_status;
	unsigned long flags;
	u8 reg_save;

	dm9000_dbg(db, 3, "entering %s\n", __func__);

	/* 1. 开启自旋锁 */
	spin_lock_irqsave(&db->lock, flags);
	/* 2. 保存当前中断寄存器的值 */
	reg_save = readb(db->io_addr);
	/* 3. 中断屏蔽(操作DM9000的IMR寄存器,屏蔽所有中断) */
	dm9000_mask_interrupts(db);
	/* 4. 获取dm9000的中断状态寄存器(ISR),判断是什么操作触发了中断 */
	int_status = ior(db, DM9000_ISR); 
	iow(db, DM9000_ISR, int_status);  
	if (netif_msg_intr(db))
		dev_dbg(db->dev, "interrupt status %02x\n", int_status);
	/* 检测是否是接收数据时的中断 */
	if (int_status & ISR_PRS)
		dm9000_rx(dev);
	/* 检测是否数据包发送完成的中断 */
	if (int_status & ISR_PTS)
		dm9000_tx_done(dev, db);
	/* 检测是否连接状态发生的中断 */
	if (db->type != TYPE_DM9000E)
	{
		if (int_status & ISR_LNKCHNG)
		{
			schedule_delayed_work(&db->phy_poll, 1); // 启动网卡连接检测
		}
	}
	/* 5. 解除中断屏蔽 */
	dm9000_unmask_interrupts(db);
	/* 6. 恢复中断处理前中断寄存器的值 */
	writeb(reg_save, db->io_addr);
	/* 7. 解除自旋锁 */
	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. 当发送完成后,唤醒发送队列
*/

8、数据传输的整个流程

在这里插入图片描述

  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
摘 要:基于对Linux 下蓝牙协议栈BlueZ 源代码的分析,给出BlueZ的组织结构和特点。分析蓝牙USB 传输驱动机制和数据处理过程, 给出实现蓝牙设备驱动的重要数据结构和流程,并总结Linux 下开发蓝牙USB 设备驱动的一般方法和关键技术。 关键词:Linux 系统;蓝牙协议栈;设备驱动 USB Device Driver for Linux Bluetooth Stack LIANG Jun-xue, YU Bin (Institute of Electronic Technology, PLA Information Engineering University, Zhengzhou 450004) 【Abstract】This paper depicts the structure and characteristics of BlueZ based on analyzing the source code of Linux bluetooth stack BlueZ. It analyzes the implementation of bluetooth USB transport driver scheme and data processing procedure in detail, and gives the key data structure and implementation of bluetooth device driver. It summarizes the approach of developing Linux bluetooth USB device driver and the key technology. 【Key words】Linux system; bluetooth stack; device driver 计 算 机 工 程 Computer Engineering 第 34 卷 第 9 期 Vol.34 No.9 2008 年 5 月 May 2008 ·开发研究与设计技术· 文章编号:1000—3428(2008)09—0273—03 文献标识码:A 图分类号:TP391 1 概述 蓝牙技术是开放式通信规范,而 Linux 是开放源码的操 作系统。廉价设备与免费软件的结合,促进了蓝牙技术和 Linux 的发展与融合。 Linux最早的蓝牙协议栈是由Axis Communication Inc在 1999 年发布的 OpenBT 协议栈。 随后, IBM 发布了 BlueDrekar 协议栈,但没有公开其源码。Qualcomm Incorporated 在 2001 年发布的 BlueZ 协议栈被接纳为 2.4.6 内核的一部分。此外, Rappore Technology 及 Nokia 的 Affix Bluetooth Stack 都是 Linux 系统下的蓝牙协议栈,应用在不同的设备和领域。 BlueZ 是 Linux 的官方蓝牙协议栈,也是目前应用最广 泛的协议栈,几乎支持所有已通过认证的蓝牙设备。对于基 于主机的蓝牙应用,目前常见的硬件接口有 UART, USB 和 PC 卡等,USB 作为 PC 的标准外设接口,具有连接方便、兼 容性好和支持高速设备等特点,已广泛应用于蓝牙设备。 目前对 Linux 下 USB 设备驱动的研究已较为广泛而深 入[1-4] ,但对 Linux 下的蓝牙设备驱动还没有专门的研究。本 文在分析 USB 设备驱动和蓝牙协议栈的基础上,总结了 Linux 下开发蓝牙 USB 驱动程序的一般方法,并深入剖析了 其关键技术。 2 Linux 蓝牙协议栈 BlueZ 简介 BlueZ 目前已成为一个开放性的源码工程。它可以很好 地在 Linux 支持的各种体系的硬件平台下运行,包括各种单 处理器平台、多处理器平台及超线程系统。 BlueZ 由多个独立的模块组成,内核空间主要包括设备 驱动、蓝牙核心及 HCI 、L2CAP 与 SCO 音频、 RFCOMM, BNEP, CMTP 与 HIDP 、通用蓝牙 SDP 库和后 台服务及面向所有的标准套接字接口;在用户空间提供了 蓝牙配置、测试及协议分析等工具。其组织结构如图 1 所示, BlueZ 没有实现专门的 SDP ,而是将其实现为运行在后台 的蓝牙服务库例程(图 1 没有描述该后台服务)。 RFOMM 支 持标准的套接口,并提供了串行仿真 TTY 接口,这使串行端 口应用程序和协议可以不加更改地运行在蓝牙设备上,例如 通过点对点协议 PPP 可实现基于 TCP/IP 协议簇的所有网络 应用。BNEP 实现了蓝牙的以太网仿真,TCP/IP 可以直接 运行于其上。 USB设备驱动 (hci_usb.o) L2CAP(l2cap.o) RFCOMM (rfcomm.o) BNEP (bnep.o) CMTP (cmtp.o) 串口设备驱动 (hci_uart.o) 虚拟串口设备驱动 (hci_vhci.o) 音频 socket RFCOMM socket BNEP socket CMTP socket L2CAP socket HCI socket 内核 空间 用户 空间 串口设备 CAPI设备 输入设备 网络设备 HDIP socket 音频设备 AF_BLUETOOTH socket 音频(sco.o) PPP TCP/IP AF_INET socket BNEP (bnep.o) 其他设备驱动 (bluecard_cs.o等) BlueZ工具和实用程序 HDIP (hdip.o) BlueZ核心 及HCI(bluez.o/bluetooth.o) 图 1 BlueZ 组织结构 3 蓝牙 USB 设备驱动 设备驱动程序在 Linux 内核起着重要作用,它使某个 硬件能响应一个定义良好的内部编程接口。这些接口隐藏了 设备的工作细节,用户通过一组独立于特定驱动程序的标准 调用来操作设备。而将这些调用映射到作用于实际硬件设备 的特有操作上,则是驱动程序的任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值