简单谈一点linux内核中套接字的bind机制--2.6.30内核代码的改进

在2.6.30之前的内核版本中,如果一个要求绑定随机端口的套接字在遍历了所有的哈希表中的元素之后发现没有合适的,那么直接出错返回,这里有几个问题,第一就是如果当前系统的绑定套接字已经足够多了,那么很明显的事实就是等待漫长的遍历结束之后仍然会无功而返,第二个问题就是如果付出了这么大的代价无功而返显然对调用者不公平,于是2.6.30做了改进,部分代码如下:

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, attempts = 5;

struct net *net = sock_net(sk);

int smallest_size = -1, smallest_rover;

local_bh_disable();

if (!snum) {

int remaining, rover, low, high;

again:

...//smallest_size = -1;之外和老版本相同,注意括号

inet_bind_bucket_for_each(tb, node, &head->chain)

if (ib_net(tb) == net && tb->port == rover) {

if (tb->fastreuse > 0 && sk->sk_reuse && sk->sk_state != TCP_LISTEN &&(tb->num_owners < smallest_size || smallest_size == -1)) { //如果当前套接字没有设置reuse或者状态是listen,或者...那么就不做下一步的抉择,因为那样没有意义,所谓下一步的抉择主要就是寻找一个拥有最小使用者的inet_bind_bucket,然后如果绑定套接字的数量已经足够多,那么就用这个找到的inet_bind_bucket进行抉择

smallest_size = tb->num_owners;

smallest_rover = rover;

if (atomic_read(&hashinfo->bsockets) > (high - low) + 1) {//如果绑定的套接字数量已经超过了设置的最大数量,那么就不再继续搜索了,为何不在遍历的时候直接进行这个判断而非要等到这里呢?我想这里面做了一个权衡,就是说如果遍历前就进行这个判断的话,那么一旦套接字数量超越保持哈希查找高效率界限的话,对新的当前套接字不公平,毕竟它还是可以重用一些端口的,如果不进行这个调用的话,那么遍历一遍哈希会很耗时,为了使得程序逻辑尽量满足每一个消费者的需求同时又保持高效,那么就用这里的处理方式,当查找到一个可以公用端口的bucket的时候再进行数量判断,一旦超越界限马上跳出遍历,但是在下面的bind_conflict中很可能会冲突失败,当失败的时候程序流程回到again继续查找,此时的smallest_size被重新赋值,然后重新选定随机的起始端口。这个过程看起来在最坏情况下也会导致全部遍历,但是无论怎样在这最坏的情况下遍历期间插入了一些抉择点,绕过来说最坏情况就是这些抉择点都失败的情况,然而都失败的可能性是非常小的,这又是一个权衡

spin_unlock(&head->lock);

snum = smallest_rover;

goto have_snum;

}

...//同老版本代码,注意括号

} while (--remaining > 0);

ret = 1;

if (remaining <= 0) { //这里给了随机绑定需求一次机会,只要找到了合适的可以重用的端口,那么就尽可能的使用它,为了兼顾其它的绑定,尽量使用共享者比较少的绑定

if (smallest_size != -1) {

snum = smallest_rover;

goto have_snum;

}

goto fail;

}

snum = rover;

} else {

have_snum:

head = &hashinfo->bhash[inet_bhashfn(net, snum, hashinfo->bhash_size)];

...//同老版本代码,注意括号

tb_found:

if (!hlist_empty(&tb->owners)) {

if (tb->fastreuse > 0 && sk->sk_reuse && //想成功必须保证smallest_size等于-1

sk->sk_state != TCP_LISTEN && smallest_size == -1) {

goto success;

} else {

ret = 1; //如果是随机绑定需求,并且套接字数量已经足够多,或者找了一圈只找到可以重用的inet_bind_bucket,那么就有机会使用这些可以重用的inet_bind_bucket,而且会到达这里并且下面的bind_conflict往往会返回NULL,也就是意味着没有冲突,接下来随机绑定的需求就可以满足了,就是重用这个bucket

if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb)) {

if (sk->sk_reuse && sk->sk_state != TCP_LISTEN &&

smallest_size != -1 && --attempts >= 0) {

spin_unlock(&head->lock);

goto again;

}

goto fail_unlock;

...//接下来的逻辑和以前的几乎相同,注意括号

}

可以看到,新的2.6.30内核解决了以上提出的两个问题,首先,随机绑定套接字的需求可以使用可重用的套接字了,其次,当已经绑定的套接字数量达到一定量的时候不会再进行全部遍历了,节省了很多的时间。这就是说,2.6.30的改进在代码上就可以看出来,不过具体是否性能提升了,还要看具体的测试环境,用眼静态的看代码只能得出代码在理想情况下执行的一般结论,程序的实际执行环境是很复杂的,比如说,有些极端情况几乎是不会出现的,不采用一些大压力测试方案很难理解代码的意图,这也可以说,以上的版本静态观看看似很不错,但是可能真的要到绑定套接字达到一定数量的时候优势才会体现出来。行文最后可以看到套接字在绑定的时候所做的可否共享判断,一共是两个层次的判断,首先在所谓的协议族的底层容器中遍历先找到有可能共享的bucket,该bucket其实也是一个容器,这个有可能的判断是以当前sock为中心的,就是判断当前的sock是否允许reuse以及是否处于listen状态等等,单方面通过判断以后,还有进行另一个层次的判断,所谓双方合作一个巴掌拍不响,这另一个方面的判断是以这个找到的bucket中的各个元素为中心的,如果有一个元素不同意reuse,那么最终还是会失败的,2.6.30之前的内核版本完全是按照这个逻辑来的,2.6.30的改进在于在第一层判断的期间插入了第二层的很容易进行的判断,同时进行了一些信息搜集,该信息储备作为后期抉择之用,比如在第一层的判断中进行第二层的tb->fastreuse值的判断,搜集的信息就是smallest_size和smallest_rover,一旦遍历结束或者中途终止,那么就可以用这些信息进行抉择,不管怎样没有白遍历一场,这个效率的提升是很明显的。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页