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

本文深入剖析Linux中write源码,介绍其系统调用原型及实现位置。详细阐述数据在vfs层、socket层、tcp层、ip层、网络设备层和网卡驱动层的传输过程,如vfs层根据文件的file_operations调用写入函数,tcp层将用户数据拷贝到skb并发送等,最后梳理了数据传输的整个流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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,
					
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值