前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux tcp/ip 源码分析 - write

Linux tcp/ip 源码分析 - write

作者头像
KINGYT
修改2019-06-11 16:08:43
2K0
修改2019-06-11 16:08:43
举报

上面的文章已经分析了tcp建立的整个过程,下面我们来看下write是如何实现tcp写的。

代码语言:javascript
复制
// fs/read_write.c
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
    size_t, count)
{
  struct fd f = fdget_pos(fd);
  ...
  if (f.file) {
    ...
    ret = vfs_write(f.file, buf, count, &pos);
    ...
  }

  return ret;
}

该方法先通过fd找到struct file,再调用vfs_write继续执行write逻辑。

代码语言:javascript
复制
// fs/read_write.c
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
  ssize_t ret;
  ...
  if (!ret) {
    ...
    ret = __vfs_write(file, buf, count, pos);
    ...
  }

  return ret;
}
EXPORT_SYMBOL_GPL(vfs_write);

该方法又调用了__vfs_write方法。

代码语言:javascript
复制
// fs/read_write.c
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;
}

由第一篇文章我们可以知道,file->f_op的值为&socket_file_ops。

代码语言:javascript
复制
// 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方法,所以上面的__vfs_write方法最终会调用new_sync_write方法。

代码语言:javascript
复制
// fs/read_write.c
static ssize_t new_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
  struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len };
  struct kiocb kiocb;
  struct iov_iter iter;
  ssize_t ret;

  init_sync_kiocb(&kiocb, filp);
  ...
  iov_iter_init(&iter, WRITE, &iov, 1, len);

  ret = call_write_iter(filp, &kiocb, &iter);
  ...
  return ret;
}

该方法的各种初始化最终使得,kiocb持有filp,即我们要写入的文件,iter持有iov,iov又持有buf和len,即我们要写入的数据。

之后,该方法又调用了call_write_iter方法,传入上面初始化好的新参数。

代码语言:javascript
复制
// include/linux/fs.h
static inline ssize_t call_write_iter(struct file *file, struct kiocb *kio,
              struct iov_iter *iter)
{
  return file->f_op->write_iter(kio, iter);
}

该方法又调用了file->f_op->write_iter指向的方法,由上面的socket_file_ops变量我们可以知道,这个方法就是sock_write_iter。

代码语言:javascript
复制
// net/socket.c
static ssize_t sock_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
  struct file *file = iocb->ki_filp;
  struct socket *sock = file->private_data;
  struct msghdr msg = {.msg_iter = *from,
           .msg_iocb = iocb};
  ...
  if (file->f_flags & O_NONBLOCK)
    msg.msg_flags = MSG_DONTWAIT;
  ...
  res = sock_sendmsg(sock, &msg);
  ...
  return res;
}

该方法又把参数iocb和from包装成了一个类型为struct msghdr的变量msg,之后又调用sock_sendmsg方法,传入这个新变量。

代码语言:javascript
复制
// net/socket.c
int sock_sendmsg(struct socket *sock, struct msghdr *msg)
{
  ...
  return err ?: sock_sendmsg_nosec(sock, msg);
}
EXPORT_SYMBOL(sock_sendmsg);

该方法又调用了sock_sendmsg_nosec方法。

代码语言:javascript
复制
// net/socket.c
static inline int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg)
{
  int ret = sock->ops->sendmsg(sock, msg, msg_data_left(msg));
  ...
  return ret;
}

该方法又调用了sock_sendmsg_nosec方法。

该方法又调用了sock->ops->sendmsg指向的方法,由第一篇文章我们可以知道,这个方法是inet_sendmsg。

调用这个方法的第三个参数为方法msg_data_left的返回值,该值为我们最开始调用write时,传入的要写的数据长度。

代码语言:javascript
复制
// net/ipv4/af_inet.c
int inet_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
  struct sock *sk = sock->sk;
  ...
  return sk->sk_prot->sendmsg(sk, msg, size);
}
EXPORT_SYMBOL(inet_sendmsg);

该方法又调用了sk->sk_prot->sendmsg指向的方法,由第一篇文章我们可以知道,这个方法是tcp_sendmsg。

代码语言:javascript
复制
// net/ipv4/tcp.c
int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
{
  ...
  ret = tcp_sendmsg_locked(sk, msg, size);
  ...
  return ret;
}
EXPORT_SYMBOL(tcp_sendmsg);

该方法又调用了tcp_sendmsg_locked方法。

代码语言:javascript
复制
// net/ipv4/tcp.c
int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
{
  struct tcp_sock *tp = tcp_sk(sk);
  ...
  struct sk_buff *skb;
  ...
  int flags, err, copied = 0;
  int mss_now = 0, size_goal, copied_syn = 0;
  ...
  /* Ok commence sending. */
  copied = 0;

restart:
  mss_now = tcp_send_mss(sk, &size_goal, flags);

  err = -EPIPE;
  if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
    goto do_error;
  ...
  while (msg_data_left(msg)) {
    int copy = 0;
    int max = size_goal;

    skb = tcp_write_queue_tail(sk);
    if (skb) {
      ...
      copy = max - skb->len;
    }

    if (copy <= 0 || !tcp_skb_can_collapse_to(skb)) {
      ...
      skb = sk_stream_alloc_skb(sk,
              select_size(sk, sg, first_skb),
              sk->sk_allocation,
              first_skb);
      ...
      copy = size_goal;
      max = size_goal;
      ...
    }

    /* Try to append data to the end of skb. */
    if (copy > msg_data_left(msg))
      copy = msg_data_left(msg);

    /* Where to copy to? */
    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);
      ...
    } else if (!uarg || !uarg->zerocopy) {
      ...
    } else {
      ...
    }
    ...
    copied += copy;
    if (!msg_data_left(msg)) {
      ...
      goto out;
    }
    ...
    continue;
    ...
  }

out:
  if (copied) {
    ...
    tcp_push(sk, flags, mss_now, tp->nonagle, size_goal);
  }
  ...
  return copied + copied_syn;
  
do_fault:
  ...
do_error:
  if (copied + copied_syn)
    goto out;
out_err:
  ...
  err = sk_stream_error(sk, flags, err);
  ...
  return err;
}
EXPORT_SYMBOL_GPL(tcp_sendmsg_locked);

终于到了实现真正写逻辑的方法,我们来看下这个方法。

方法描述

1. 设置copied为0,该变量用于记录我们写成功的字节数。

2. 查看当前的mss值,即tcp包最大可带多少数据,并赋值给mss_now。

3. 检查当前socket是否有错误,或当前socket是否已SEND_SHUTDOWN,如果是则跳转到do_error逻辑。

do_error的逻辑大体上为,如果当前写成功的字节数大于0,则正常返回当前写成功的字节数,如果等于0,则调用sk_stream_error方法,获取当前应该返回给用户的错误码并赋值给err,最后返回err。

4. 进入while循环,循环继续的条件为当前剩余要写的字节数大于0。

5. 设置copy变量的值为0,该变量用于表示这次while循环可拷贝的字节数。

6. 设置max值为size_goal,size_goal变量的值是由上面tcp_send_mss方法中获取的,用于表示一个struct sk_buff最多可放多少数据,以字节表示。

7. 调用tcp_write_queue_tail方法,从sk->sk_write_queue队列尾部拿出一个struct sk_buff实例,并赋值给skb变量。

8. 如果skb不为null,则看该skb还剩余多大的空间可写,把该值赋值给copy变量。

9. 如果skb为null,或者skb没有可写空间了,此时copy为0,则调用sk_stream_alloc_skb方法,创建一个新的struct sk_buff实并赋值给skb,同时将copy和max的值都设置为size_goal。

10. 判断copy是否大于msg中剩余要写字节数,如果是,则修正copy的值。

11. 调用skb_availroom方法,查看skb是否有可写空间,如果有的话,先根据可写空间大小修正copy的值,再调用skb_add_data_nocache方法,将msg中的数据拷贝到skb中。

12. 如果skb没有可写空间,则将数据拷贝到skb间接指向的空间内,具体介绍略。

13. 将这次while循环成功拷贝的字节数累加到copied变量中。

14. 判断msg中是否还有要写的数据,如果有,则继续while循环,如果没有,则跳出while循环,进入到out标签指向的逻辑。

15. 如果while循环拷贝的字节数大于0,则调用tcp_push,将数据发送出去。

16. 最后返回整个方法成功写的字节数。

完。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linux内核及JVM底层相关技术研究 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档