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

Linux tcp/ip 源码分析 - close

作者头像
KINGYT
修改2019-06-11 15:23:26
2.2K0
修改2019-06-11 15:23:26
举报

上一篇文章我们分析了shutdown方法的实现,这里我们再看下close方法。

代码语言:javascript
复制
// fs/open.c
SYSCALL_DEFINE1(close, unsigned int, fd)
{
  int retval = __close_fd(current->files, fd);
  ...
  return retval;
}
EXPORT_SYMBOL(sys_close);

该方法调用了__close_fd方法。

代码语言:javascript
复制
// fs/file.c
int __close_fd(struct files_struct *files, unsigned fd)
{
  struct file *file;
  struct fdtable *fdt;
  ...
  fdt = files_fdtable(files);
  ...
  file = fdt->fd[fd];
  ...
  return filp_close(file, files);
  ...
}

该方法先通过fd找到对应的file,再调用filp_close方法对file进行close。

代码语言:javascript
复制
// fs/open.c
int filp_close(struct file *filp, fl_owner_t id)
{
  int retval = 0;
  ...
  fput(filp);
  return retval;
}

EXPORT_SYMBOL(filp_close);

该方法又调用了fput方法。

代码语言:javascript
复制
// fs/file_table.c
void fput(struct file *file)
{
  if (atomic_long_dec_and_test(&file->f_count)) {
    struct task_struct *task = current;

    if (likely(!in_interrupt() && !(task->flags & PF_KTHREAD))) {
      init_task_work(&file->f_u.fu_rcuhead, ____fput);
      if (!task_work_add(task, &file->f_u.fu_rcuhead, true))
        return;
      ...
    }
    ...
  }
}

方法描述

1. 先将file->f_count字段减1,再判断该字段的值是否为0,如果是,则继续执行if内的逻辑。

2. 调用init_task_work方法,设置file销毁的回调函数为____fput。

3. 调用task_work_add方法,将销毁该文件的task放到待执行的任务队列中。

最终____fput方法会被回调,继续执行文件的close逻辑。

代码语言:javascript
复制
// fs/file_table.c
static void ____fput(struct callback_head *work)
{
  __fput(container_of(work, struct file, f_u.fu_rcuhead));
}

该方法会先根据work指针以及该指针所属字段在struct file中的偏移量,找到对应的file指针,然后再调用__fput方法,传入这个file参数。

代码语言:javascript
复制
// fs/file_table.c
static void __fput(struct file *file)
{
  ...
  eventpoll_release(file);
  ...
  if (file->f_op->release)
    file->f_op->release(inode, file);
  ...
}

该方法先调用eventpoll_release方法,检查该文件是否已被注册到epoll实例中,如果是则从epoll实例中移除。

之后再调用file->f_op->release指向的方法,由第一篇文章可以知道,该方法是sock_close。

代码语言:javascript
复制
// net/socket.c
static int sock_close(struct inode *inode, struct file *filp)
{
  __sock_release(SOCKET_I(inode), inode);
  return 0;
}

该方法又调用了__sock_release方法。

代码语言:javascript
复制
// net/socket.c
static void __sock_release(struct socket *sock, struct inode *inode)
{
  if (sock->ops) {
    ...
    sock->ops->release(sock);
    ...
  }
  ...
}

该方法又调用了sock->ops->release指向的方法,由第一篇文章可以知道,该方法是inet_release。

代码语言:javascript
复制
// net/ipv4/af_inet.c
int inet_release(struct socket *sock)
{
  struct sock *sk = sock->sk;

  if (sk) {
    long timeout;
    ...
    timeout = 0;
    ...
    sk->sk_prot->close(sk, timeout);
  }
  return 0;
}
EXPORT_SYMBOL(inet_release);

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

代码语言:javascript
复制
// net/ipv4/af_inet.c
void tcp_close(struct sock *sk, long timeout)
{
  struct sk_buff *skb;
  int data_was_unread = 0;
  ...
  sk->sk_shutdown = SHUTDOWN_MASK;
  ...
  while ((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) {
    u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq;
    ...
    data_was_unread += len;
    __kfree_skb(skb);
  }
  ...
  if (unlikely(tcp_sk(sk)->repair)) {
    ...
  } else if (data_was_unread) {
    ...
    tcp_set_state(sk, TCP_CLOSE);
    tcp_send_active_reset(sk, sk->sk_allocation);
  } else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {
    ...
  } else if (tcp_close_state(sk)) {
    ...
    tcp_send_fin(sk);
  }
  ...
}
EXPORT_SYMBOL(tcp_close);

方法描述

1. 设置变量data_was_unread的值为0,该变量用于表示tcp recvbuf中还有多少字节未读。

2. 设置sk->sk_shutdown字段值为SHUTDOWN_MASK,表示RCV和SEND都已经shutdown。

3. 清空sk->sk_receive_queue队列中的数据,并统计还有多少字节未读。

4. 如果未读字节数大于0,则直接将sk状态设置为TCP_CLOSE,并发送reset消息给对方。

5. 如果未读字节数等于0,则调用tcp_close_state方法,根据当前sk状态设置sk下一状态,比如,假设当前sk状态为TCP_ESTABLISHED,则下一状态为TCP_FIN_WAIT1,该方法的返回值为,是否要发送fin消息给对方。

6. 如果需要,则调用tcp_send_fin方法,发送fin消息给对方。

本文为了代码上的简便,省略了很多内存释放的逻辑。

与shutdown方法相比,close方法不仅会根据当前状态决定是否要发送fin消息,还会释放该socket涉及到的一系列内存。

所以,当想要彻底关闭socket时,应该使用close方法而不是shutdown方法。

完。

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

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

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

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

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