内核中的TCP的追踪分析-7-TCP(IPV4)的socket接收连接

2020-05-25 00:00:00 函数 队列 等待 结构 为空

这一节我们开始分析如何接收TCP的socket的连接请求,象在Unix的socket分析章节一样我们先看练习中的用户界面
accept(server_sockfd, (struct sockaddr *)&client_address, client_len);
然后进入内核的系统调用函数中,
我们直接从
err = sock->ops->accept(sock, newsock, sock->file->f_flags);
这部分开始入手分析TCP的socket是如何执行的,这里会进入inet_stream_ops中执行,可能有些朋友是直接阅读本文的,好是看一下前面的章节理清是如何进入这个函数的,我们这里不再重复了。
const struct proto_ops inet_stream_ops = {
。。。。。。
.accept = inet_accept,
。。。。。。
};
我们再次看一下af_inet.c中的这个数据结构,很显然进入了inet_accept()函数
int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
struct sock *sk1 = sock->sk;
int err = -EINVAL;
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;
}
进入这个函数的时候已经找到了我们前面建立的socket结构,而newsock是我们新分配建立的socket结构,我们看到上面函数中执行了
struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);
进而进入了钩子函数中执行
 那里的struct proto tcp_prot结构变量可以看到
struct proto tcp_prot = {
。。。。。。
.accept = inet_csk_accept,
。。。。。。
};
很显然是执行的inet_csk_accept()函数
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct sock *newsk;
int error;
lock_sock(sk);
/* We need to make sure that this socket is listening,
* and that it has something pending.
*/
error = -EINVAL;
if (sk->sk_state != TCP_LISTEN)
goto out_err;
/* Find already established connection */
if (reqsk_queue_empty(&icsk->icsk_accept_queue)) {
long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
/* If this is a non blocking socket don't sleep */
error = -EAGAIN;
if (!timeo)
goto out_err;
error = inet_csk_wait_for_connect(sk, timeo);
if (error)
goto out_err;
}
newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);
BUG_TRAP(newsk->sk_state != TCP_SYN_RECV);
out:
release_sock(sk);
return newsk;
out_err:
newsk = NULL;
*err = error;
goto out;
}
象往常叙述的一样首先是在sock中取得struct inet_connection_sock结构,然后判断一下sock的状态是否已经处于监听状态,如果没有处于监听状态的话就不能接收了,只好出错返回了。接着是检查icsk中的icsk_accept_queue请求队列是否为空,因为我们练习中还未启动客户端程序,所以此时还没有连接请求到来,这个队列现在是空的,所以进入if语句,sock_rcvtimeo()是根据是否允许“阻塞”即等待,而取得sock结构中的sk_rcvtimeo时间值,然后根据这个值进入inet_csk_wait_for_connect()函数中
static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
{
struct inet_connection_sock *icsk = inet_csk(sk);
DEFINE_WAIT(wait);
int err;
/*
* True wake-one mechanism for incoming connections: only
* one process gets woken up, not the 'whole herd'.
* Since we do not 'race & poll' for established sockets
* anymore, the common case will execute the loop only once.
*
* Subtle issue: "add_wait_queue_exclusive()" will be added
* after any current non-exclusive waiters, and we know that
* it will always _stay_ after any new non-exclusive waiters
* because all non-exclusive waiters are added at the
* beginning of the wait-queue. As such, it's ok to "drop"
* our exclusiveness temporarily when we get woken up without
* having to remove and re-insert us on the wait queue.
*/
for (;;) {
prepare_to_wait_exclusive(sk->sk_sleep, &wait,
TASK_INTERRUPTIBLE);
release_sock(sk);
if (reqsk_queue_empty(&icsk->icsk_accept_queue))
timeo = schedule_timeout(timeo);
lock_sock(sk);
err = 0;
if (!reqsk_queue_empty(&icsk->icsk_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;
}
函数首先是调用了宏来声明一个等待队列
#define DEFINE_WAIT(name) \
wait_queue_t name = { \
.private = current, \
.func = autoremove_wake_function, \
.task_list = LIST_HEAD_INIT((name).task_list), \
}
关于等待队列的具体概念我们留在以后专门的章节中论述,这里可以看出是根据当前进程而建立的名为wait的等待队列,接着函数中调用了
void
prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
unsigned long flags;
wait->flags |= WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
if (list_empty(&wait->task_list))
__add_wait_queue_tail(q, wait);
/*
* don't alter the task state if this is just going to
* queue an async wait queue callback
*/
if (is_sync_wait(wait))
set_current_state(state);
spin_unlock_irqrestore(&q->lock, flags);
}

这个函数与我们所说的等待队列部分内容是密切相关的,我们只简单的叙述一下,函数中主要是将我们上面建立的等待队列插入到这里的sock结构中的sk_sleep所指定的等待队列头中,此后再次调用reqsk_queue_empty()函数检查一下icsk_accept_queue是否为空,如果还为空就说明没有连接请求到来,开始睡眠等待了,schedule_timeout()我们先放一放,这个函数与时钟密切相关,所以在以后再看,这里是根据我们上面得到的定时时间来进入睡眠的。当从这个函数返回时,再次锁住sock防止其他进程打扰,然后这里还是判断一下icsk_accept_queue是否为空,如果还为空的话就要跳出for循环了,醒来后还要检查一下是否是因为信号而醒来的,如果有信号就要处理信号signal_pending(),这个函数在以后的信号内容叙述,后如果睡眠的时间已经用完了也会跳出循环,跳出循环后就要将这里的等待队列从sock中的sk_sleep中摘链。
我们回到inet_csk_accept()函数中继续往下看,如果这时队列icsk_accept_queue不为空,即有连接请求到来怎么办呢,继续看下面的代码

newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);
这里看到是进入了reqsk_queue_get_child函数中
static inline struct sock *reqsk_queue_get_child(struct request_sock_queue *queue,
struct sock *parent)
{
struct request_sock *req = reqsk_queue_remove(queue);
struct sock *child = req->sk;
BUG_TRAP(child != NULL);
sk_acceptq_removed(parent);
__reqsk_free(req);
return child;
}
函数中首先是调用了reqsk_queue_remove()从队列中摘下一个已经到来的request_sock结构
static inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue)
{
struct request_sock *req = queue->rskq_accept_head;
BUG_TRAP(req != NULL);
queue->rskq_accept_head = req->dl_next;
if (queue->rskq_accept_head == NULL)
queue->rskq_accept_tail = NULL;
return req;
}
很明显上面函数中是从队列的rskq_accept_head摘下一个已经到来的request_sock这个结构是从客户端请求连接时挂入的,reqsk_queue_get_child()函数在这里把request_sock中载运的sock结构返回到inet_csk_accept中的局部变量newsk使用。而sk_acceptq_removed是递减我们服务器端sock中的sk_ack_backlog。然后__reqsk_free释放掉request_sock结构。回到inet_csk_accept函数中,然后返回我们获得的客户端送来的sock结构。就象我们在unix的socket章节叙述的那样,接着返回到sys_accept()函数中,具体的过程请看
http://blog.chinaunix.net/u2/64681/showart_1329029.html
 我们在练习中看到需要获得客户端的地址,在那个章节中我们又走到了
newsock->ops->getname(newsock, (struct sockaddr )address, &len, 2)
这要看我们客户端传送过来的newsock结构中的钩子结构了,很明显我们因为是主要针对的tcp的socket,所以这里仍旧进入
struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.recvmsg = tcp_recvmsg,
.backlog_rcv = tcp_v4_do_rcv,
.hash = inet_hash,
.unhash = inet_unhash,
.get_port = inet_csk_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
.sockets_allocated = &tcp_sockets_allocated,
.orphan_count = &tcp_orphan_count,
.memory_allocated = &tcp_memory_allocated,
.memory_pressure = &tcp_memory_pressure,
.sysctl_mem = sysctl_tcp_mem,
.sysctl_wmem = sysctl_tcp_wmem,
.sysctl_rmem = sysctl_tcp_rmem,
.max_header = MAX_TCP_HEADER,
.obj_size = sizeof(struct tcp_sock),
.twsk_prot = &tcp_timewait_sock_ops,
.rsk_prot = &tcp_request_sock_ops,
.h.hashinfo = &tcp_hashinfo,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_tcp_setsockopt,
.compat_getsockopt = compat_tcp_getsockopt,
#endif
};
但是在tcp_prot结构中我们并没有看到对应的钩子函数,所以需要与客户端的connect连接结合起来看,明天我们将叙述那里的过程,到时再加来看这里是如何把地址赋值给练习中的client_address地址结构变量。sys_accept()函数余下的过程就完全与unix的socket连接过程完全一样了

文章来源CU社区:内核中的TCP的追踪分析-7-TCP(IPV4)的socket接收连接

相关文章