Linux内核源码-sys_listen()

来源:互联网 发布:戎美女装淘宝店 编辑:程序博客网 时间:2024/06/10 07:42

(本文部分参考了《Linux内核源代码情景分析》)
操作SYS_LISTEN 设定server插口,是由 sys_listen()实现的。

需要了解的是“有连接”模式的插口天生就是按 client/server 的模式运转的,只有在一个 server 插口和一个 client 插口之间建立起连接。区分这两种插口的方法是:只要在插口创建以后为其调用了 listen(),这个插口就成为 server 插口了。凡是 server 插口都不能主动去与别的插口建立连接,而只能被动地通过 accept()接受来自 client 插口的连接请求。而 client 插口则相反,不能调用 accept()来接受连接请求,而只能主动地通过 connect()提出连接请求。
listen系统调用:asmlinkage long sys_listen(int fd, int backlog)的操作与sys_bind()十分类似,都是根据打开文件号 fd 找到插口的 socket 数据结构( inode 的一部分),进而通过结构中的指针 ops执行相应的回调函数。sys_listen的回调函数是:inet_listen(),在此函数中会首先判断传进来的插口类型和状态,合法后会调动tcp_listen_start()函数开始侦听,其实是相当简单的:除状态本身的变化以外,就是设置(或改变几个参数),主要就是最大队列长度sk_max_ack_backlog。

函数调用过程如图所示:
这里写图片描述

这里写图片描述

下面是完整代码:

inet_listen代码如下:

/* 将一个socket转入listen状态 */int inet_listen(struct socket *sock, int backlog){    struct sock *sk = sock->sk;    unsigned char old_state;    int err;    lock_sock(sk);    err = -EINVAL;/* 检测系统调用的状态和类型,如果不合法则退出 *//**只有插口的类型为 SOCK_STREAM,即“有连接”模式的插口,并且已经为其 bind()了插口地址,才允许 listen()。 *对于符合这些条件的插口也不是什么时候都可以调用 listen()的。*插口的 sock结构中有个成分 state,用来实现一种“有限状态机”。只有当这个状态机处于 TCP_CLOSE 或 TCP_LISTEN这两种状态时才可以对其调用 listen()。*在前面 sock_create()的代码中可以看到在创建一个插口时要调用函数 sock_init_data()对分配的sock数据结构进行初始化,在那里state被设置成 TCP_CLOSE。*状态TCP_CLOSE 表示插口只是刚刚建立,尚未宣布成为 server 插口;* TCP_LISTEN 则表示插口已经设置成 server 插口,当尚未建立起连接,并且不是在等待来自 client 一方的连接请求。*只有在这两种状态下才允许改变插口的参数(主要是连接请求队列的容量)。*/    if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)        goto out;    old_state = sk->sk_state;    if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))        goto out;    if (old_state != TCP_LISTEN) {        err = tcp_listen_start(sk);/* 开始侦听 */        if (err)            goto out;    }    /* 设置传输控制块的连接队列长度上限 */    sk->sk_max_ack_backlog = backlog;    err = 0;out:    release_sock(sk);    return err;}

tcp_listen_start()代码如下:

int tcp_listen_start(struct sock *sk){    struct inet_sock *inet = inet_sk(sk);    struct tcp_sock *tp = tcp_sk(sk);    struct tcp_listen_opt *lopt;    /* 初始连接队列长度上限 */    sk->sk_max_ack_backlog = 0;    sk->sk_ack_backlog = 0;    tp->accept_queue = tp->accept_queue_tail = NULL;    /* 初始化传输控制块中与延时发送ACK有关的数据结构 */    rwlock_init(&tp->syn_wait_lock);    tcp_delack_init(tp);    /* 为管理连接请求块的散列表分配存储空间,如果失败则退出 */    lopt = kmalloc(sizeof(struct tcp_listen_opt), GFP_KERNEL);    if (!lopt)        return -ENOMEM;    memset(lopt, 0, sizeof(struct tcp_listen_opt));    for (lopt->max_qlen_log = 6; ; lopt->max_qlen_log++)        if ((1 << lopt->max_qlen_log) >= sysctl_max_syn_backlog)            break;    /* 计算哈希表的哈希种子 */    get_random_bytes(&lopt->hash_rnd, 4);    /* 将散列块与传输控制块绑定 */    write_lock_bh(&tp->syn_wait_lock);    tp->listen_opt = lopt;    write_unlock_bh(&tp->syn_wait_lock);    sk->sk_state = TCP_LISTEN;/* 设置控制块的状态 */    if (!sk->sk_prot->get_port(sk, inet->num)) {/* 进行端口绑定 */        inet->sport = htons(inet->num);/* 设置网络字节序的端口号 */        sk_dst_reset(sk);/* 清除路由缓存 */        sk->sk_prot->hash(sk);/* 将传输控制块添加到侦听散列表中 */        return 0;    }    /* 绑定失败,设置其状态 */    sk->sk_state = TCP_CLOSE;    /* 解除侦听连接请求块与传输控制块的绑定 */    write_lock_bh(&tp->syn_wait_lock);    tp->listen_opt = NULL;    write_unlock_bh(&tp->syn_wait_lock);    kfree(lopt);/* 释放侦听连接请求块 */    return -EADDRINUSE;}