Linux源码-sys_accept()

(本文部分参考《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 fastcall
prepare_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()函数(将套接口与文件描述符绑定):
这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值