Linux源码-sys_accept()

来源:互联网 发布:安卓软件打包 编辑:程序博客网 时间:2024/06/14 06:58

(本文部分参考《Linux源代码情景分析》)
操作SYS_ACCEPT接受连接请求,通过sys_accept()实现。
“有连接”模式的插口一旦通过 listen()设置成 server 插口以后,就只能被动地通过 accept()接受来自client 插口的连接请求。进程对 accept()的调用是阻塞性的,就是说如果没有连接请求就会进入睡眠等待,直到有连接请求到来,接受了请求以后(或者超过了预定的等待时间)才会返回。所以,在已经有连接请求的情况下是“接受连接请求”,而在尚无连接请求的情况下是“等待连接请求”。在内核中,accept()是通过 net/socket.c 中的函数 sys_accept()实现的。
函数整个调用过程如下:
这里写图片描述

下面是完整代码:

函数 sys_accept()代码如下:

asmlinkage long sys_accept(int fd, struct sockaddr __user *upeer_sockaddr, int __user *upeer_addrlen){    struct socket *sock, *newsock;    int err, len;    char address[MAX_SOCK_ADDR];    sock = sockfd_lookup(fd, &err);/* 获得侦听端口的socket */    if (!sock)        goto out;    err = -ENFILE;    if (!(newsock = sock_alloc()))/* 分配一个新的套接口,用来处理与客户端的连接 */         goto out_put;    /* 根据侦听套接口来初始化新连接的类型和回调表 */    newsock->type = sock->type;    newsock->ops = sock->ops;...    /* accept系统调用在套接口层的实现,对TCP来说,是inet_accept */    err = sock->ops->accept(sock, newsock, sock->file->f_flags);    if (err < 0)        goto out_release;    if (upeer_sockaddr) {/* 调用者需要获取对方套接口地址和端口 */        /* *调用传输层回调获得对方的地址和端口*这个函数既可以用来获取对方插口的地址,也可以用来获取本插口的地址,具体由参数 peer 决定。在这里由于调用时将参数的值设成 2,所以取的是对方地址。 */        if(newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 2)<0) {            err = -ECONNABORTED;            goto out_release;        }        /* 成功后复制到用户态 */        err = move_addr_to_user(address, len, upeer_sockaddr, upeer_addrlen);        if (err < 0)            goto out_release;    }    if ((err = sock_map_fd(newsock)) < 0)/* 为新连接分配文件描述符并返回*/        goto out_release;...}

代码中先通过 sockfd_lookup()找到 server 插口的 socket 结构,然后通过 sock_alloc()分配一个新的socket 结构,再通过相应函数指针调用具体的回调函数inet_accept()。
值得注意的是:一个插口经 listen()设置成 server 插口以后永远不会与任何插口建立起连接。因为一旦接受了一个连接请求之后就会创建出另一个插口,连接就建立在这个新的插口与请求连接的那个插口之间,而原先的 server 插口则并无改变,并且还可再次通过 accept()接受下一个连接请求,server 插口永远保持接受新的连接请求的能力,但是其本身从来不成为某个连接的一端。正因为这样, sys_accept()返回的是新插口的打开文件号,同时会把对方(cli)的插口地址从参数中带出来。

inet_accept()代码如下:

/* accept系统调用在套接口层的实现 */int inet_accept(struct socket *sock, struct socket *newsock, int flags){    struct sock *sk1 = sock->sk;/* 根据套口获得传输层控制块 */    int err = -EINVAL;    /* 调用传输层的函数tcp_accept获得已经完成的连接的传输控制块,即子传输控制块 */    struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);    if (!sk2)/* 失败返回 */        goto do_err;    lock_sock(sk2);    BUG_TRAP((1 << sk2->sk_state) &         (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_CLOSE));    /* 将子传输控制块与传输控制块关联起来 */    sock_graft(sk2, newsock);    /* 设置套接口的状态 */    newsock->state = SS_CONNECTED;    err = 0;    release_sock(sk2);do_err:    return err;}

tcp_accept()函数代码如下:

/* accept调用的传输层实现 */struct sock *tcp_accept(struct sock *sk, int flags, int *err){    struct tcp_sock *tp = tcp_sk(sk);    struct open_request *req;    struct sock *newsk;    int error;    lock_sock(sk);    error = -EINVAL;    if (sk->sk_state != TCP_LISTEN)/* 本调用仅仅针对侦听套口,不是此状态的套口则退出 */        goto out;    if (!tp->accept_queue) {/* accept队列为空,说明还没有收到新连接 */        long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);/* 如果套口是非阻塞的,或者在一定时间内没有新连接,则返回 *//*标志位 O_NONBLOCK在文件操作中常用,表示如果有报文就接收,但是若没有也得马上返回,而不是睡眠等待,这个标志位也可用于 accept()。*/        error = -EAGAIN;        if (!timeo)/* 超时时间到,没有新连接,退出 */            goto out;        /* 运行到这里,说明有新连接到来(返回值为0),则等待新的传输控制块 */        error = wait_for_connect(sk, timeo);        if (error)            goto out;    }    req = tp->accept_queue;    if ((tp->accept_queue = req->dl_next) == NULL)        tp->accept_queue_tail = NULL;    newsk = req->sk;    sk_acceptq_removed(sk);    tcp_openreq_fastfree(req);    BUG_TRAP(newsk->sk_state != TCP_SYN_RECV);    release_sock(sk);    return newsk;out:    release_sock(sk);    *err = error;    return NULL;}

wait_for_connect()函数代码如下;

static int wait_for_connect(struct sock *sk, long timeo){    struct tcp_sock *tp = tcp_sk(sk);    DEFINE_WAIT(wait);    int err;    for (;;) {        prepare_to_wait_exclusive(sk->sk_sleep, &wait,                      TASK_INTERRUPTIBLE);        release_sock(sk);        if (!tp->accept_queue)            timeo = schedule_timeout(timeo);        lock_sock(sk);        err = 0;        if (tp->accept_queue)            break;        err = -EINVAL;        if (sk->sk_state != TCP_LISTEN)            break;        err = sock_intr_errno(timeo);        if (signal_pending(current))            break;        err = -EAGAIN;        if (!timeo)            break;    }    finish_wait(sk->sk_sleep, &wait);    return err;}void fastcallprepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state){    unsigned long flags;    /*参数 WQ_FLAG_EXCLUSIVE 表示如果有多个进程在睡眠等待就只唤醒其中一个*/    wait->flags |= WQ_FLAG_EXCLUSIVE;    spin_lock_irqsave(&q->lock, flags);    if (list_empty(&wait->task_list))        __add_wait_queue_tail(q, wait);    if (is_sync_wait(wait))        set_current_state(state);    spin_unlock_irqrestore(&q->lock, flags);}

当中一些重要的数据结构的创建过程如下:
这里写图片描述

注:
1、
server 插口必须有个地址,这样 client 一方才能通过地址来找到 server 插口的数据结构,所以在创建了插口以后一定要调用 bind()将其“捆绑”到一个地址上。可是,对于 client 一方的插口来说,它只能主动地去寻找某个 server 插口,别的插口是不会来寻找它的。所以实际上没有必要让 client 插口也有个地址,当然有也无妨。
2、
在 sys_accept()中已经调用了 sock_alloc(),分配了一个新的 socket 结构(也就是inode 结构)。但 sock_alloc(),它并不分配与 socket 结构配对的 sock结构,从这个意义上讲,这个新的插口还不完整。那么什么时候才分配所需的 sock 数据结构呢?这是由client 一方在 connect()的过程中分配,并且将其指针通过用作连接请求报文的 sk_buff 结构带过来的,通过 sock_graft()将 client 一方提供的 sock结构 与 server 一方提供的 newsock(socket结构) 挂上钩。
3、
简单介绍一下sock_map_fd()函数(将套接口与文件描述符绑定):
这里写图片描述

原创粉丝点击