前面三篇文章对路由的设置过程分析已经完成了,所以我们继续回到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赋值给res的type,所以这里就会返回路由别名中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
绑定给
ipv4
的
sock
结构变量
inet
的接收地址和
源地址。如果地址类型是多播或者是广播的话就将起源地址设置为0,表示使用设备。
if (sk->sk_prot->get_port(sk, snum)) { inet->saddr = inet->rcv_saddr = 0; err = -EADDRINUSE; goto out_release_sock; }
|
这里朋友们接合第2节
tcp_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 };
这个数组是为了端口的号的范围在32768-61000期间,然后下边根据这个范围和随机数设置一个hash用的参考值rover,inet_bhashfn()是将rover与hash表的大小进行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;
|
根据我们对tcp的sock的是否确定了本地接收地址和端口号来对sock的sk_userlocks用户锁标志增加相应的锁。最后将tcp的sock的sport本地端口指定为我们设置的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); }
|