网络函数accept源码分析

2019-01-10 10:10:41 浏览数 (1)

代码语言:c复制
static int sock_accept(int fd, struct sockaddr *upeer_sockaddr, int *upeer_addrlen)
{
    struct file *file;
    struct socket *sock, *newsock;
    int i;
    char address[MAX_SOCK_ADDR];
    int len;
    if (fd < 0 || fd >= NR_OPEN || ((file = current->files->fd[fd]) == NULL))
        return(-EBADF);
    // 根据文件描述符找到对应的file结构体和socket结构
      if (!(sock = sockfd_lookup(fd, &file))) 
        return(-ENOTSOCK);
    if (sock->state != SS_UNCONNECTED) 
    {
        return(-EINVAL);
    }
    // socket没有调用过listen,报错,该标记位在listen中设置
    if (!(sock->flags & SO_ACCEPTCON)) 
    {
        return(-EINVAL);
    }
    // 分配一个新的socket结构体
    if (!(newsock = sock_alloc())) 
    {
        printk("NET: sock_accept: no more socketsn");
        return(-ENOSR);    /* Was: EAGAIN, but we are out of system
                   resources! */
    }
    newsock->type = sock->type;
    newsock->ops = sock->ops;
    // 创建一个底层的sock结构体和socket结构体互相关联
    if ((i = sock->ops->dup(newsock, sock)) < 0) 
    {
        sock_release(newsock);
        return(i);
    }
    i = newsock->ops->accept(sock, newsock, file->f_flags);
    if ( i < 0) 
    {
        sock_release(newsock);
        return(i);
    }
    // 返回一个新的文件描述符
    if ((fd = get_fd(SOCK_INODE(newsock))) < 0) 
    {
        sock_release(newsock);
        return(-EINVAL);
    }
    // 是否需要获取socket对应的地址
    if (upeer_sockaddr)
    {
        newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 1);
        move_addr_to_user(address,len, upeer_sockaddr, upeer_addrlen);
    }
    return(fd);
}

accept函数主要的两个操作是

1 sock->ops->dup,该函数的是inet_create函数的封装,就是新建一个sock结构体并且和socket结构体互相关联,前面的文章已经分析过。

2 sock->ops->accept,该函数底层是inet_accept函数

代码语言:javascript复制
static int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
    struct sock *sk1, *sk2;
    int err;
    sk1 = (struct sock *) sock->data;
    /*
     * We've been passed an extra socket.
     * We need to free it up because the tcp module creates
     * its own when it accepts one.
     */
    // 销毁新socket结构体中的socke结构体,d调用底层的accept会返回一个新的sock
    if (newsock->data)
    {
          struct sock *sk=(struct sock *)newsock->data;
          newsock->data=NULL;
          sk->dead = 1;
          destroy_sock(sk);
    }
    if (sk1->prot->accept == NULL) 
        return(-EOPNOTSUPP);
    /* Restore the state if we have been interrupted, and then returned. */
    if (sk1->pair != NULL ) 
    {
        sk2 = sk1->pair;
        sk1->pair = NULL;
    } 
    else
    {    // 返回一个新的sock结构体
        sk2 = sk1->prot->accept(sk1,flags);
        if (sk2 == NULL) 
        {
            if (sk1->err <= 0)
                printk("Warning sock.c:sk1->err <= 0.  Returning non-error.n");
            err=sk1->err;
            sk1->err=0;
            return(-err);
        }
    }
    // 互相关联
    newsock->data = (void *)sk2;
    // 复制socket结构的wait字段,用于控制进程的阻塞和唤醒
    sk2->sleep = newsock->wait;
    sk2->socket = newsock;
    newsock->conn = NULL;
    if (flags & O_NONBLOCK) 
        return(0);
    cli(); /* avoid the race. */
    // sock是接收syn状态则阻塞当前进程
    while(sk2->state == TCP_SYN_RECV) 
    {
        interruptible_sleep_on(sk2->sleep);
        if (current->signal & ~current->blocked) 
        {
            sti();
            sk1->pair = sk2;
            sk2->sleep = NULL;
            sk2->socket=NULL;
            newsock->data = NULL;
            return(-ERESTARTSYS);
        }
    }
    sti();
    if (sk2->state != TCP_ESTABLISHED && sk2->err > 0) 
    {
        err = -sk2->err;
        sk2->err=0;
        sk2->dead=1;    /* ANK */
        destroy_sock(sk2);
        newsock->data = NULL;
        return(err);
    }
    // 设置sock为已经建立连接状态
    newsock->state = SS_CONNECTED;
    return(0);
}

可以看到inet_accept函数的两个核心逻辑是

1 sk1->prot->accept

2 interruptible_sleep_on

代码语言:javascript复制
static struct sock *tcp_accept(struct sock *sk, int flags)
{
    struct sock *newsk;
    struct sk_buff *skb;
  /*
   * We need to make sure that this socket is listening,
   * and that it has something pending.
   */
    if (sk->state != TCP_LISTEN) 
    {
        sk->err = EINVAL;
        return(NULL); 
    }
    /* Avoid the race. */
    cli();
    sk->inuse = 1;
    // 从sock的receive_queue队列摘取已建立连接的节点,
    while((skb = tcp_dequeue_established(sk)) == NULL) 
    {    
        // 没有已经建立连接的节点,但是设置了非阻塞模式,直接返回
        if (flags & O_NONBLOCK) 
        {
            sti();
            release_sock(sk);
            sk->err = EAGAIN;
            return(NULL);
        }
        release_sock(sk);
        //阻塞进程,如果后续建立了连接,则进程被唤醒的时候,就会跳出while循环
        interruptible_sleep_on(sk->sleep);
        if (current->signal & ~current->blocked) 
        {
            sti();
            sk->err = ERESTARTSYS;
            return(NULL);
        }
        sk->inuse = 1;
      }
    sti();
    /*
     *    Now all we need to do is return skb->sk. 
     */
    // 拿到一个新的sock结构,由建立连接的时候创建的
    newsk = skb->sk;
    kfree_skb(skb, FREE_READ);
    // 队列节点数减一
    sk->ack_backlog--;
    release_sock(sk);
    // 返回新的sock结构体
    return(newsk);
}

interruptible_sleep_on的核心代码是

代码语言:javascript复制
// struct wait_queue wait = { current, NULL };p就是socket中的wait,下面的代码就是把当前进程挂到wait队列里
if (!*p) {
    wait->next = wait;
    *p = wait;
} else {
    wait->next = (*p)->next;
    (*p)->next = wait;
}

上面的全部说明了如果调用accept的时候,如果已经有已经完成了连接的节点,则直接返回一个节点,否则就进入阻塞。

接下分析一下什么会被唤醒,在tcp_rcv中有一个分支

代码语言:javascript复制
if(sk->state==TCP_SYN_SENT){
        if(th->ack)
        {
            if(!tcp_ack(sk,th,saddr,len))
            {
                //...
            }
        }
    }

tcp_ack中有一个分支

代码语言:javascript复制
if(sk->state==TCP_SYN_RECV)
    {
        tcp_set_state(sk, TCP_ESTABLISHED);
        tcp_options(sk,th);
        sk->dummy_th.dest=th->source;
        sk->copied_seq = sk->acked_seq;
        if(!sk->dead)
            sk->state_change(sk);
        if(sk->max_window==0)
        {
            sk->max_window=32;    /* Sanity check */
            sk->mss=min(sk->max_window,sk->mtu);
        }
    }

其中state_change对应的代码是

代码语言:javascript复制
static void def_callback1(struct sock *sk)
{
    if(!sk->dead)
        wake_up_interruptible(sk->sleep);
    // 获取sleep队列中的所有进程,sleep即socket的wait,即inode的wait
}

最后accept函数返回。

0 人点赞