Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1309428
  • 博文数量: 107
  • 博客积分: 10155
  • 博客等级: 上将
  • 技术积分: 2166
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-25 16:57
文章分类

全部博文(107)

文章存档

2010年(1)

2009年(1)

2008年(105)

分类: LINUX

2008-12-25 10:14:35

前面三篇文章对路由的设置过程分析已经完成了,所以我们继续回到http://blog.chinaunix.net/u2/64681/showart.php?id=1724837 那篇文章的开始处继续我们的分析过程。我们从那里的fn_hash_lookup()函数向上返回到__inet_dev_addr_type()函数中继续往下看,那节中我们讲到了从local_table->tb_lookup()进而调用了fn_hash_lookup()函数。所以我们接着从此处的代码处继续往下看

 

                   if (!local_table->tb_lookup(local_table, &fl, &res)) {

                            if (!dev || dev == res.fi->fib_dev)

                                     ret = res.type;

                            fib_res_put(&res);

                   }

这里我们看到要用到从我们在查找返回的res,即fib_result结构中的路由表信息结构,在路由表信息结构中记录着我们的net_device网络设备结构信息,这里就是检查是否与我们上边传递下来的net_device是同一个网络设备结构,我们在__inet_dev_addr_type()函数的上一级调用函数inet_addr_type()中看到,其实传递下来的这个net_device结构变量dev指针是空,所以代码处会返回我们在http://blog.chinaunix.net/u2/64681/showart.php?id=1724837 那节中看到将路由别名结构中的fa_type赋值给restype,所以这里就会返回路由别名中fa_type记录的路由类型值。我们跟着函数继续往上返回到inet_bind()函数中。此时chk_addr_ret就记录着路由类型值。下面接着是判断是否地址类型是否正确,然后

snum = ntohs(addr->sin_port);

取得地址的端口号我们在练习中设置的是9266检查是否小于1024及是否有绑定权限。然后对sock结构加锁,接着

if (sk->sk_state != TCP_CLOSE || inet->num)
        goto out_release_sock;

检查一下sock的状态是否处于关闭或者已经绑定端口

inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr;

接着把我们设定的ip地址192.168.1.1绑定给ipv4sock结构变量inet的接收地址和源地址。如果地址类型是多播或者是广播的话就将起源地址设置为0,表示使用设备。

 

if (sk->sk_prot->get_port(sk, snum)) {
        inet->saddr = inet->rcv_saddr = 0;
        err = -EADDRINUSE;
        goto out_release_sock;
    }

这里朋友们接合第2tcp_prot结构中的钩子函数

http://blog.chinaunix.net/u2/64681/showart.php?id=1685663

在那里有这样一句

.get_port        = inet_csk_get_port,

所以我们这里进入inet_csk_get_port函数()

sys_socketcall()-->sys_bind()-->inet_bind()-->inet_csk_get_port()

int inet_csk_get_port(struct sock *sk, unsigned short snum)
{
    struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
    struct inet_bind_hashbucket *head;
    struct hlist_node *node;
    struct inet_bind_bucket *tb;
    int ret;
    struct net *net = sock_net(sk);

    local_bh_disable();
    if (!snum) {
        int remaining, rover, low, high;

        inet_get_local_port_range(&low, &high);
        remaining = (high - low) + 1;
        rover = net_random() % remaining + low;

        do {
            head = &hashinfo->bhash[inet_bhashfn(rover, hashinfo->bhash_size)];
            spin_lock(&head->lock);
            inet_bind_bucket_for_each(tb, node, &head->chain)
                if (tb->ib_net == net && tb->port == rover)
                    goto next;
            break;
        next:
            spin_unlock(&head->lock);
            if (++rover > high)
                rover = low;
        } while (--remaining > 0);

        
/* Exhausted local port range during search? It is not
         * possible for us to be holding one of the bind hash
         * locks if this test triggers, because if 'remaining'
         * drops to zero, we broke out of the do/while loop at
         * the top level, not from the 'break;' statement. wumingxiaozu
         */

        ret = 1;
        if (remaining <= 0)
            goto fail;

        
/* OK, here is the one we will use. HEAD is
         * non-NULL and we hold it's mutex.
         */

        snum = rover;
    } else {
        head = &hashinfo->bhash[inet_bhashfn(snum, hashinfo->bhash_size)];
        spin_lock(&head->lock);
        inet_bind_bucket_for_each(tb, node, &head->chain)
            if (tb->ib_net == net && tb->port == snum)
                goto tb_found;
    }
    tb = NULL;
    goto tb_not_found;
tb_found:
    if (!hlist_empty(&tb->owners)) {
        if (tb->fastreuse > 0 &&
         sk->sk_reuse && sk->sk_state != TCP_LISTEN) {
            goto success;
        } else {
            ret = 1;
            if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb))
                goto fail_unlock;
        }
    }
tb_not_found:
    ret = 1;
    if (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep,
                    net, head, snum)) == NULL)
        goto fail_unlock;
    if (hlist_empty(&tb->owners)) {
        if (sk->sk_reuse && sk->sk_state != TCP_LISTEN)
            tb->fastreuse = 1;
        else
            tb->fastreuse = 0;
    } else if (tb->fastreuse &&
         (!sk->sk_reuse || sk->sk_state == TCP_LISTEN))
        tb->fastreuse = 0;
success:
    if (!inet_csk(sk)->icsk_bind_hash)
        inet_bind_hash(sk, tb, snum);
    BUG_TRAP(inet_csk(sk)->icsk_bind_hash == tb);
    ret = 0;

fail_unlock:
    spin_unlock(&head->lock);
fail:
    local_bh_enable();
    return ret;
}

函数感觉非常复杂,我们逐层分析,上面代码中首先从struct proto tcp_prot结构中的.h.hashinfo        = &tcp_hashinfo,取得tcp_hashinfo,这是个struct inet_hashinfo结构变量

 

struct inet_hashinfo {
    
/* This is for sockets with full identity only. Sockets here will
     * always be without wildcards and will have the following invariant:
     *
     * TCP_ESTABLISHED <= sk->sk_state < TCP_CLOSE
     *
     * TIME_WAIT sockets use a separate chain (twchain).
     */

    struct inet_ehash_bucket    *ehash;
    rwlock_t            *ehash_locks;
    unsigned int            ehash_size;
    unsigned int            ehash_locks_mask;

    
/* Ok, let's try this, I give up, we do need a local binding
     * TCP hash as well as the others for fast bind/connect.
     */

    struct inet_bind_hashbucket    *bhash;

    unsigned int            bhash_size;
    /* Note : 4 bytes padding on 64 bit arches */

    
/* All sockets in TCP_LISTEN state will be in here. This is the only
     * table where wildcard'd TCP sockets can exist. Hash function here
     * is just local port number wumingxiaozu.
     */

    struct hlist_head        listening_hash[INET_LHTABLE_SIZE];

    
/* All the above members are written once at bootup and
     * never written again _or_ are predominantly read-access.
     *
     * Now align to a new cache line as all the following members
     * are often dirty.
     */

    rwlock_t            lhash_lock ____cacheline_aligned;
    atomic_t            lhash_users;
    wait_queue_head_t        lhash_wait;
    struct kmem_cache            *bind_bucket_cachep;
};

这个结构是为了维护tcp中的hash表使用的

struct inet_bind_hashbucket {
    spinlock_t        lock;
    struct hlist_head    chain;
};

这个数据结构是为了维护端口使用的hash表桶。我们还看到struct hlist_node *node;这个是声明的hash表的链头,还有一个数据结构是

struct inet_bind_bucket {
    struct net        *ib_net;
    unsigned short        port;
    signed short        fastreuse;
    struct hlist_node    node;
    struct hlist_head    owners;
};

这个是绑定端口使用的hash节点的数据结构,我们看了这几个数据结构但是不用担心是否能够记忆,只需要在代码的分析过程中来了解他的具体的作用。我们继续看inet_csk_get_port()函数,首先是使net指向我们的init_net网络空间结构,然后根据参数snum是否为空来执行一段代码,我们知道snum是从前边传递过来的端口号,我们应用程序传递过来当然不为空,我们看if代码,如果我们没有指定端口号的话,就会执行if语句的上半段代码分配一个端口号给snum,先执行inet_get_local_port_range()函数

 

sys_socketcall()-->sys_bind()-->inet_bind()-->inet_csk_get_port()-->inet_get_local_port_range()

void inet_get_local_port_range(int *low, int *high)
{
    unsigned seq;
    do {
        seq = read_seqbegin(&sysctl_port_range_lock);

        *low = sysctl_local_port_range[0];
        *high = sysctl_local_port_range[1];
    } while (read_seqretry(&sysctl_port_range_lock, seq));
}

其内部涉及到了一个数组sysctl_local_port_range

int sysctl_local_port_range[2] = { 32768, 61000 };

这个数组是为了端口的号的范围在3276861000期间,然后下边根据这个范围和随机数设置一个hash用的参考值roverinet_bhashfn()是将roverhash表的大小进行hash,然后其结果做为hash桶表的决定值来取得对应的hash桶表struct inet_bind_hashbucket,然后赋值给head,接着进入一个for循环

inet_bind_bucket_for_each(tb, node, &head->chain)
                if (tb->ib_net == net && tb->port == rover)
                    goto next;
inet_bind_bucket_for_each是一个宏声明
#define inet_bind_bucket_for_each(tb, node, head) \
    hlist_for_each_entry(tb, node, head, node)
#define hlist_for_each_entry(tpos, pos, head, member)             \
    for (pos = (head)->first;                     \
     pos && ({ prefetch(pos->next); 1;}) &&             \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
     pos = pos->next)

结合我们函数中的代码,这个循环的过程其实就是从我们上面得到hash桶表,循环查找是否同样的网络空间下我们要设置的端口是否已经被占用了,如果占用了就增加hash表的参考值rover,进行下一轮的检查,如果通过了这个循环就说明我们的端口还没有被占用此时,struct inet_bind_bucket局部变量指针tb指向了能够使用的hash节点。接着端口号snum就指向了这个参考值。

但是在我们的实践练习中我们指定了端口号为9266,忘记的朋友请看一下相关的文章,在我们这里的追踪会进入if语句的下半段

else {
        head = &hashinfo->bhash[inet_bhashfn(snum, hashinfo->bhash_size)];
        spin_lock(&head->lock);
        inet_bind_bucket_for_each(tb, node, &head->chain)
            if (tb->ib_net == net && tb->port == snum)
                goto tb_found;
    }

  转载请注明出处http://qinjiana0786.cublog.cn 参考if上半段的代码,我们这里是以9266指定的端口号为参考值找到hash桶表再进一步找到,接着在这个hash桶表中找到我们要执行绑定端口节点tb。接着要跳转到tb_found处继续往下执行

tb_found:
    if (!hlist_empty(&tb->owners)) {
        if (tb->fastreuse > 0 &&
         sk->sk_reuse && sk->sk_state != TCP_LISTEN) {
            goto success;
        } else {
            ret = 1;
            if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb))
                goto fail_unlock;
        }
    }

检查tb是否已经有了所有者以及sock的状态和允许复用情况,就跳转到success处,但是如果没有所有者的话就会执行inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb),我们暂且跳过这句代码。先围绕主线继续进行

success:
    if (!inet_csk(sk)->icsk_bind_hash)
        inet_bind_hash(sk, tb, snum);
    BUG_TRAP(inet_csk(sk)->icsk_bind_hash == tb);
    ret = 0;

看一下inet_csk是个inline函数

 

sys_socketcall()-->sys_bind()-->inet_bind()-->inet_csk_get_port()-->inet_csk()

static inline struct inet_connection_sock *inet_csk(const struct sock *sk)
{
    return (struct inet_connection_sock *)sk;
}
struct inet_connection_sock是专门用于tcp和socket相联系的一个数据结构
struct inet_connection_sock {
    /* inet_sock has to be the first member! */
    struct inet_sock     icsk_inet;
    struct request_sock_queue icsk_accept_queue;
    struct inet_bind_bucket     *icsk_bind_hash;
    unsigned long         icsk_timeout;
     struct timer_list     icsk_retransmit_timer;
     struct timer_list     icsk_delack_timer;
    __u32             icsk_rto;
    __u32             icsk_pmtu_cookie;
    const struct tcp_congestion_ops *icsk_ca_ops;
    const struct inet_connection_sock_af_ops *icsk_af_ops;
    unsigned int         (*icsk_sync_mss)(struct sock *sk, u32 pmtu);
    __u8             icsk_ca_state;
    __u8             icsk_retransmits;
    __u8             icsk_pending;
    __u8             icsk_backoff;
    __u8             icsk_syn_retries;
    __u8             icsk_probes_out;
    __u16             icsk_ext_hdr_len;
    struct {
        __u8         pending;     /* ACK is pending            wumingxiaozu */
        __u8         quick;     /* Scheduled number of quick acks     */
        __u8         pingpong;     /* The session is interactive         */
        __u8         blocked;     /* Delayed ACK was blocked by socket lock */
        __u32         ato;         /* Predicted tick of soft clock     */
        unsigned long     timeout;     /* Currently scheduled timeout         */
        __u32         lrcvtime;     /* timestamp of last received data packet */
        __u16         last_seg_size; /* Size of last incoming segment     */
        __u16         rcv_mss;     /* MSS used for delayed ACK decisions     */
    } icsk_ack;
    struct {
        int         enabled;

        /* Range of MTUs to search */
        int         search_high;
        int         search_low;

        /* Information on the current probe. */
        int         probe_size;
    } icsk_mtup;
    u32             icsk_ca_priv[16];
#define ICSK_CA_PRIV_SIZE    (16 * sizeof(u32))
};

我是无名小卒,转载请注明出处http://qinjiana0786.cublog.cn 继续分析,只是这里我们看到将sock直接转换为了struct inet_connection_sock指针,然后判断这个结构中是否指定了icsk_bind_hash,它是我们上面看到的struct inet_bind_bucket节点,如果没有指定就会执行inet_bind_hash函数

 

sys_socketcall()-->sys_bind()-->inet_bind()-->inet_csk_get_port()-->inet_bind_hash()

void inet_bind_hash(struct sock *sk, struct inet_bind_bucket *tb,
         const unsigned short snum)
{
    inet_sk(sk)->num = snum;
    sk_add_bind_node(sk, &tb->owners);
    inet_csk(sk)->icsk_bind_hash = tb;
}

这里首先是将sock的端口号指定为我们的9266,然后进入sk_add_bind_node()函数

 

sys_socketcall()-->sys_bind()-->inet_bind()-->inet_csk_get_port()-->inet_bind_hash()-->sk_add_bind_node()

static __inline__ void sk_add_bind_node(struct sock *sk,
                    struct hlist_head *list)
{
    hlist_add_head(&sk->sk_bind_node, list);
}

将我们的sock中的sk_bind_node链入到了tb中的所有者hash表中。最后将icsk_bind_hash再指定为tb。这样端口方面的工作已经在内核完成了,我们从inet_csk_get_port()返回到inet_bind()函数继续往下看

if (inet->rcv_saddr)
        sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
    if (snum)
        sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
    inet->sport = htons(inet->num);
    inet->daddr = 0;
    inet->dport = 0;
    sk_dst_reset(sk);
    err = 0;
out_release_sock:
    release_sock(sk);
out:
    return err;

根据我们对tcpsock的是否确定了本地接收地址和端口号来对socksk_userlocks用户锁标志增加相应的锁。最后将tcpsocksport本地端口指定为我们设置的9266端口号。将目标地址daddr设为0,目标端口设为0,此后我们再返回到sys_bind()函数中,接下来的fput_light()是递减我们的socket的file结构的使用计数f_count。那是在sockfd_lookup_light函数中调用fget_light()时递增的,并且那里会根据f_count确定fput_needed来标识是否递减文件结构指针file。在fput_light()函数中根据fput_needed来确定是否递减file的计数器,如果file的计数器递减到0,还会进一步释放这个文件指针。

 

sys_socketcall()-->sys_bind()-->fput_light()

 

static inline void fput_light(struct file *file, int fput_needed)
{
    if (unlikely(fput_needed))
        fput(file);
}

 以后的过程就与文件系统相关了,请朋友们参考一下深入理解linux内核第三版查阅有关这个函数的过程。
阅读(5150) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~