文章目录
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,