6.3.11.15. accept

accept函数原型为 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 其中sockfd为套接字的文件描述符,addr指向存放地址信息的结构体的首地址,addrlen存放地址信息的结构体的大小。

accept在socket()、bind()、listen()之后调用,用于监听TCP请求。如果accpet成功,那么其返回值是由内核自动生成的一个的描述字,代表与返回客户的TCP连接。

具体来说,accept的执行流程为:

  • 创建一个socket结构体

  • 获取一个未使用的文件描述符fd

  • 创建一个file结构体,并和socket关联

  • 从全连接队列中获取客户端发来的请求

  • 根据请求获取之前新建的sock结构体返回

  • 将请求中的sock结构体和开始分配的socket结构体关联

  • 将文件描述符fd和文件结构体file关联,并返回fd供用户使用

6.3.11.15.1. 内核实现

accept的内核实现如下:

SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
        int __user *, upeer_addrlen)
{
    return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
}

最终调用的是 SYSCALL_DEFINE4:

SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
        int __user *, upeer_addrlen, int, flags)
{
    struct socket *sock, *newsock;
    struct file *newfile;
    int err, len, newfd, fput_needed;
    struct sockaddr_storage address;

    if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
        return -EINVAL;

    if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
        flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

    // 根据fd获取对应的socket结构
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;

    err = -ENFILE;
    // 分配一个新的socket结构体
    newsock = sock_alloc();
    if (!newsock)
        goto out_put;

    newsock->type = sock->type;
    newsock->ops = sock->ops;

    __module_get(newsock->ops->owner);

    // 获取一个未使用的文件描述符,这个描述符也就是accept()返回的fd
    newfd = get_unused_fd_flags(flags);
    if (unlikely(newfd < 0)) {
        err = newfd;
        sock_release(newsock);
        goto out_put;
    }
    // 创建一个file结构体,同时将这个file结构体和刚刚创建的socket关联
    // file->private_data 指向socket
    newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
    if (IS_ERR(newfile)) {
        err = PTR_ERR(newfile);
        put_unused_fd(newfd);
        sock_release(newsock);
        goto out_put;
    }

    err = security_socket_accept(sock, newsock);
    if (err)
        goto out_fd;

    // 调用inet_accept()执行主处理操作
    err = sock->ops->accept(sock, newsock, sock->file->f_flags);
    if (err < 0)
        goto out_fd;
    // 如果要获取对端连接信息,那么拷贝对应信息到用户空间
    if (upeer_sockaddr) {
        // 调用inet_getname()获取对端信息
        if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
                      &len, 2) < 0) {
            err = -ECONNABORTED;
            goto out_fd;
        }
        err = move_addr_to_user(&address,
                    len, upeer_sockaddr, upeer_addrlen);
        if (err < 0)
            goto out_fd;
    }

    /* File flags are not inherited via accept() unlike another OSes. */
    // 将文件描述符fd和文件结构体file关联到一起
    fd_install(newfd, newfile);
    err = newfd;

out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
out_fd:
    fput(newfile);
    put_unused_fd(newfd);
    goto out_put;
}

inet_accept函数调用传输层的accept操作,并且返回新的连接控制块,新的连接控制块需要与新的socket进行关联,accept完成,将新socket的状态设置为已连接状态;

/*
 *    Accept a pending connection. The TCP layer now gives BSD semantics.
 */

int inet_accept(struct socket *sock, struct socket *newsock, int flags,
        bool kern)
{
    struct sock *sk1 = sock->sk;
    int err = -EINVAL;

    /* 执行传输层的accept操作 */
    struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err, kern);

    if (!sk2)
        goto do_err;

    lock_sock(sk2);

    /* rps处理 */
    sock_rps_record_flow(sk2);
    WARN_ON(!((1 << sk2->sk_state) &
          (TCPF_ESTABLISHED | TCPF_SYN_RECV |
          TCPF_CLOSE_WAIT | TCPF_CLOSE)));

    /* 控制块连接到新的socket */
    sock_graft(sk2, newsock);

    /* 设置新socket的状态为连接 */
    newsock->state = SS_CONNECTED;
    err = 0;
    release_sock(sk2);
do_err:
    return err;
}
EXPORT_SYMBOL(inet_accept);