TCP层accept系统调用的实现分析

Posted wanpengcoder

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP层accept系统调用的实现分析相关的知识,希望对你有一定的参考价值。

inet_csk_accept函数实现了tcp协议accept操作,其主要完成的功能是,从已经完成三次握手的队列中取控制块,如果没有已经完成的连接,则需要根据阻塞标记来来区分对待,若非阻塞则直接返回,若阻塞则需要在一定时间范围内阻塞等待;

 1 /*
 2  * This will accept the next outstanding connection.
 3  */
 4 struct sock *inet_csk_accept(struct sock *sk, int flags, int *err, bool kern)
 5 {
 6     struct inet_connection_sock *icsk = inet_csk(sk);
 7     struct request_sock_queue *queue = &icsk->icsk_accept_queue;
 8     struct request_sock *req;
 9     struct sock *newsk;
10     int error;
11 
12     lock_sock(sk);
13 
14     /* We need to make sure that this socket is listening,
15      * and that it has something pending.
16      */
17     error = -EINVAL;
18 
19     /* 不是listen状态 */
20     if (sk->sk_state != TCP_LISTEN)
21         goto out_err;
22 
23     /* Find already established connection */
24 
25     /* 还没有已完成的连接 */
26     if (reqsk_queue_empty(queue)) {
27 
28         /* 获取等待时间,非阻塞为0 */
29         long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
30 
31         /* If this is a non blocking socket don‘t sleep */
32         error = -EAGAIN;
33         /* 非阻塞立即返回错误 */
34         if (!timeo)
35             goto out_err;
36 
37         /* 等待连接到来 */
38         error = inet_csk_wait_for_connect(sk, timeo);
39         if (error)
40             goto out_err;
41     }
42 
43     /* 从已完成连接队列中移除 */
44     req = reqsk_queue_remove(queue, sk);
45 
46     /* 设置新控制块指针 */
47     newsk = req->sk;
48 
49     /* TCP协议 && fastopen */
50     if (sk->sk_protocol == IPPROTO_TCP &&
51         tcp_rsk(req)->tfo_listener) {
52         spin_lock_bh(&queue->fastopenq.lock);
53         if (tcp_rsk(req)->tfo_listener) {
54             /* We are still waiting for the final ACK from 3WHS
55              * so can‘t free req now. Instead, we set req->sk to
56              * NULL to signify that the child socket is taken
57              * so reqsk_fastopen_remove() will free the req
58              * when 3WHS finishes (or is aborted).
59              */
60             req->sk = NULL;
61             req = NULL;
62         }
63         spin_unlock_bh(&queue->fastopenq.lock);
64     }
65 out:
66     release_sock(sk);
67 
68     /* 释放请求控制块 */
69     if (req)
70         reqsk_put(req);
71 
72     /* 返回找到的连接控制块 */
73     return newsk;
74 out_err:
75     newsk = NULL;
76     req = NULL;
77     *err = error;
78     goto out;
79 }

 

如果请求队列中没有已完成握手的连接,并且套接字已经设置了阻塞标记,则需要加入调度队列等待连接的到来,inet_csk_wait_for_connect函数完成了这个功能;

 1 /*
 2  * Wait for an incoming connection, avoid race conditions. This must be called
 3  * with the socket locked.
 4  */
 5 static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
 6 {
 7     struct inet_connection_sock *icsk = inet_csk(sk);
 8     DEFINE_WAIT(wait);
 9     int err;
10 
11     /*
12      * True wake-one mechanism for incoming connections: only
13      * one process gets woken up, not the ‘whole herd‘.
14      * Since we do not ‘race & poll‘ for established sockets
15      * anymore, the common case will execute the loop only once.
16      *
17      * Subtle issue: "add_wait_queue_exclusive()" will be added
18      * after any current non-exclusive waiters, and we know that
19      * it will always _stay_ after any new non-exclusive waiters
20      * because all non-exclusive waiters are added at the
21      * beginning of the wait-queue. As such, it‘s ok to "drop"
22      * our exclusiveness temporarily when we get woken up without
23      * having to remove and re-insert us on the wait queue.
24      */
25     for (;;) {
26 
27         /* 加入等待队列 */
28         prepare_to_wait_exclusive(sk_sleep(sk), &wait,
29                       TASK_INTERRUPTIBLE);
30         release_sock(sk);
31 
32         /* 如果为空计算,进行调度 */
33         if (reqsk_queue_empty(&icsk->icsk_accept_queue))
34             timeo = schedule_timeout(timeo);
35         sched_annotate_sleep();
36         lock_sock(sk);
37         err = 0;
38         /* 队列不为空*/
39         if (!reqsk_queue_empty(&icsk->icsk_accept_queue))
40             break;
41         err = -EINVAL;
42         /* 连接状态不是LISTEN */
43         if (sk->sk_state != TCP_LISTEN)
44             break;
45         /* 信号打断 */
46         err = sock_intr_errno(timeo);
47         if (signal_pending(current))
48             break;
49         err = -EAGAIN;
50         /* 调度超时 */
51         if (!timeo)
52             break;
53     }
54     /* 结束等待 */
55     finish_wait(sk_sleep(sk), &wait);
56     return err;
57 }

 

reqsk_queue_remove函数完成了将完成握手的控制块从请求队列移除的工作;

 1 static inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue,
 2                               struct sock *parent)
 3 {
 4     struct request_sock *req;
 5 
 6     spin_lock_bh(&queue->rskq_lock);
 7 
 8     /* 找到队列头 */
 9     req = queue->rskq_accept_head;
10     if (req) {
11         /* 减少已连接计数 */
12         sk_acceptq_removed(parent);
13         /* 头部指向下一节点 */
14         queue->rskq_accept_head = req->dl_next;
15 
16         /* 队列为空 */
17         if (queue->rskq_accept_head == NULL)
18             queue->rskq_accept_tail = NULL;
19     }
20     spin_unlock_bh(&queue->rskq_lock);
21     return req;
22 }

 

以上是关于TCP层accept系统调用的实现分析的主要内容,如果未能解决你的问题,请参考以下文章

linux网络协议栈源码分析 - 传输层(TCP的输出)

linux网络协议栈源码分析 - 传输层(TCP的输出)

linux网络协议栈源码分析 - 传输层(TCP连接的终止)

linux网络协议栈源码分析 - 传输层(TCP连接的终止)

linux网络协议栈源码分析 - 传输层(TCP连接的终止)

如何在 gen_tcp:accept 上调用(和睡眠)并同时处理系统消息?