免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1036 | 回复: 0
打印 上一主题 下一主题

内核中的TCP的追踪分析-7-TCP(IPV4)的socket接收连接 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-11-11 09:37 |只看该作者 |倒序浏览

这一节我们开始分析如何接收TCP的socket的连接请求,象在Unix的socket分析章节一样我们先看练习中的用户界面
accept(server_sockfd, (struct sockaddr *)&client_address, client_len);
然后进入内核的系统调用函数中,这个过程请朋友们参考
http://blog.chinaunix.net/u2/64681/showart_1329029.html
 的详细过程,我们直接从
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);
进而进入了钩子函数中执行,参考
http://blog.chinaunix.net/u2/64681/showart_1360583.html
 那里的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);
}
上面函数我们已经在
http://blog.chinaunix.net/u2/64681/showart_1329029.html
 那一节中看到过了比较详细的分析了,这个函数与我们所说的等待队列部分内容是密切相关的,我们只简单的叙述一下,函数中主要是将我们上面建立的等待队列插入到这里的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连接过程完全一样了,我们不重复了,请朋友们看
http://blog.chinaunix.net/u2/64681/showart_1329029.html
 结尾部分。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/64681/showart_1404746.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP