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

Linux tcp/ip 源码分析 - accept

作者头像
KINGYT
修改2019-06-11 16:09:54
1.8K0
修改2019-06-11 16:09:54
举报

accept方法对应的内核源码为

代码语言:javascript
复制
// net/socket.c
SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
    int __user *, upeer_addrlen)
{
  return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
}

该方法调用了名为accept4的系统调用

代码语言:javascript
复制
// net/socket.c
SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
    int __user *, upeer_addrlen, int, flags)
{
  struct socket *sock, *newsock;
  struct file *newfile;
  ...
  sock = sockfd_lookup_light(fd, &err, &fput_needed);
  ...
  newsock = sock_alloc();
  ...
  newsock->type = sock->type;
  newsock->ops = sock->ops;
  ...
  newfd = get_unused_fd_flags(flags);
  ...
  newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
  ...
  err = sock->ops->accept(sock, newsock, sock->file->f_flags, false);
  ...
  fd_install(newfd, newfile);
  err = newfd;
  ...
  return err;
  ...
}

方法描述

1. 根据fd找到对应的listening socket。

2. 调用sock_alloc方法新分配一个socket实例。

3. 将sock->type赋值给newsock->type,type值为SOCK_STREAM。

4. 将sock->ops赋值给newsock->ops,ops值为&inet_stream_ops。

5. 找一个未使用的文件描述符赋值给newfd。

6. 为newsock创建一个新的struct file实例,并赋值给newfile。

7. 调用sock->ops->accept方法继续执行accept逻辑,将获取到的struct sock赋值到newsock->sk字段。

8. 调用fd_install方法建立newfd到newfile的映射关系。

9. 返回newfd给用户。

在继续看sock->ops->accept方法之前,我们先说下struct socket和struct sock的关系。

1. struct socket是给用户使用的,struct sock是内核内部使用的。

2. struct socket是struct sock的一层wrapper,struct socket通过sk字段持有struct sock实例。

3. 使用逻辑一般是,先通过fd找到struct file, 再通过file->private_data找到struct socket,再通过sock->sk找到struct sock。

4. struct sock存放的是各种sock的通用数据,用面向对象的语言来说,它是一个基类,它的子类很多,比如struct tcp_sock等,当内核要访问子类数据时,先会把struct sock强转成子类类型,比如struct tcp_sock,再访问其内部字段。

我们继续来看上面的sock->ops->accept方法。

由第一篇文章我们可以知道,sock->ops->accept指向的方法为inet_accept。

代码语言:javascript
复制
// net/ipv4/af_inet.c
int inet_accept(struct socket *sock, struct socket *newsock, int flags,
    bool kern)
{
  struct sock *sk1 = sock->sk;
  ...
  struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err, kern);
  ...
  sock_graft(sk2, newsock);

  newsock->state = SS_CONNECTED;
  err = 0;
  ...
  return err;
}
EXPORT_SYMBOL(inet_accept);

方法描述

1. 调用sk1->sk_prot->accept方法,从建立连接成功的sock队列中拿出一个sock,赋值给sk2。

2. 调用sock_graft方法,将sk2赋值到newsock->sk字段。

3. 设置newsock->state为SS_CONNECTED。

4. return 0 给上层,表示没有错误。

继续看下sk1->sk_prot->accept方法,由第一篇文章我们可以知道,sk1->sk_prot->accept指向的是inet_csk_accept方法。

代码语言:javascript
复制
// net/ipv4/inet_connection_sock.c
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err, bool kern)
{
  struct inet_connection_sock *icsk = inet_csk(sk);
  struct request_sock_queue *queue = &icsk->icsk_accept_queue;
  struct request_sock *req;
  struct sock *newsk;
  ...
  /* Find already established connection */
  if (reqsk_queue_empty(queue)) {
    long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);

    /* If this is a non blocking socket don't sleep */
    error = -EAGAIN;
    if (!timeo)
      goto out_err;

    error = inet_csk_wait_for_connect(sk, timeo);
    ...
  }
  req = reqsk_queue_remove(queue, sk);
  newsk = req->sk;
  ...
  return newsk;
  ...
}
EXPORT_SYMBOL(inet_csk_accept);

方法描述

1. 将icsk->icsk_accept_queue赋值给queue变量。

由上一篇文章我们可以知道,当tcp三次握手完成之后,会将连接建立成功的struct request_sock实例放到icsk->icsk_accept_queue队列中。

2. 判断queue指向的队列是否为空,如果不为空,则移除第一个struct request_sock实例,并赋值给req变量。

3. 将req->sk指向的struct sock实例赋值给newsk,并返回给上层。

4. 当queue指向的队列为空时,则根据flags中是否有O_NONBLOCK标志,设置timeo的值。

如果有O_NONBLOCK标志,timeo被赋值为0,如果没有,则被赋值为一个大于0的值。

5. 当timeo为0时,表示用户设置了socket的状态为nonblocking,返回-EAGAIN错误码。

6. 当timeo不为0时,调用inet_csk_wait_for_connect方法,等待有新的连接建立成功,并加入到icsk->icsk_accept_queue队列中。

看下inet_csk_wait_for_connect方法

代码语言:javascript
复制
// net/ipv4/inet_connection_sock.c
static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
{
  struct inet_connection_sock *icsk = inet_csk(sk);
  DEFINE_WAIT(wait);
  ...
  for (;;) {
    prepare_to_wait_exclusive(sk_sleep(sk), &wait,
            TASK_INTERRUPTIBLE);
    ...
    if (reqsk_queue_empty(&icsk->icsk_accept_queue))
      timeo = schedule_timeout(timeo);
    ...
    if (!reqsk_queue_empty(&icsk->icsk_accept_queue))
      break;
    ...
    err = -EAGAIN;
    if (!timeo)
      break;
  }
  ...
  return err;
}

方法描述

1. 通过宏DEFINE_WAIT,定义一个类型为struct wait_queue_entry的wait变量,用于阻塞时事件通知。

2. 调用prepare_to_wait_exclusive方法,将wait变量加入到事件变动通知队列sk_sleep(sk)中。

3. 调用schedule_timeout方法,堵塞线程timeo时间。

4. 当新连接建立好并放入到icsk->icsk_accept_queue队列,或者timeo超时,线程会从阻塞状态中退出。

5. 检查是否有新连接,如果有则返回。

6. 检查timeo剩余时间是否为0,如果是,则说明已经等待超时,返回错误码-EAGAIN给上层。

完。

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

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

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

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

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