linux内核的数据结构:2 散列表


http://blog.csdn.net/fengtaocat/article/details/7025488 


Linux内核中实现了一种简单的开散列表。散列表中的每个项都是一个hlist_head结构,hlist_head结构是一个链表的头结点,它的大小是固定的。而在链表中具体保存我们需要的信息。这个链表的实现方式与前文所讲的双向循环链表有所不同。

Linux内核中很多地方都使用了散列的技术,比如为了加快查询特定pid的进程描述符的过程,Linux内核中建立了一个pid_hash的结构,类似的我们还可以发现tcp_hash,inode_hashtable等。

下面我们以pid_hash为例具体说明一下hash的实现细节:

pid_hash的声明:

[cpp]  view plain copy
  1. static struct hlist_head *pid_hash;  
[cpp]  view plain copy
  1. struct hlist_head {  
  2.     struct hlist_node *first;  
  3. };  

我们可以看到pid_hash的每个项都是一个链表头,大小为一个指针的size,一般为4字节。我们来看一下链表节点的定义:

[cpp]  view plain copy
  1. struct hlist_node {  
  2.     struct hlist_node *next, **pprev;  
  3. };  
注意,这里面向前的指针被定义为了struct hlist_node**类型,实际上pprev指针指向的是前一个结点的next域。为什么这样设计呢?

原因:
首先我们假设一下pprev域修改为struct hlist_node* prev,那么现在这个链表和我们平时所见到的一样了。我们考虑下这个链表的头结点,它一定会是struct hilist_node类型的,否则第二个节点的prev无法指向它;而在看一下我们现在的实现,pprev只需要指向hlist_head类型的域即可(比如hlist_head中的first),这就意味着头结点不需要是hlist_node类型。而hlist_node和hlist_head的区别在哪里,少了一个指针!这就意味着节省了hash表的空间。


pid_hash的初始化是在内核初始化时(即start_kernel函数中)进行的,具体由pidhash_init函数完成。

[cpp]  view plain copy
  1. void __init pidhash_init(void)  
  2. {  
  3.     int i, pidhash_size;  
  4.   
  5.     pid_hash = alloc_large_system_hash("PID"sizeof(*pid_hash), 0, 18,  
  6.                        HASH_EARLY | HASH_SMALL,  
  7.                        &pidhash_shift, NULL, 4096);  
  8.     pidhash_size = 1 << pidhash_shift;  
  9.   
  10.     for (i = 0; i < pidhash_size; i++)  
  11.         INIT_HLIST_HEAD(&pid_hash[i]);  
  12. }  

alloc_large_system_hash是专门用于为hash表分配一块连续的内存的,参数4096意味着最多4K个项,也就是最多占用1页的内存。INIT_HLIST_HEAD是负责链表节点的初始化的宏。

那么下一个重要的问题,hash函数是如何实现的呢?非常简洁。

[cpp]  view plain copy
  1. #define pid_hashfn(nr, ns)  \  
  2.     hash_long((unsigned long)nr + (unsigned long)ns, pidhash_shift)  

其中,nr是pid的值,而ns表示的是pid的命名空间(这个是为了支持轻量级虚拟化而引入的新概念)。我们具体看一下hash_long的实现:

[cpp]  view plain copy
  1. #if BITS_PER_LONG == 32  
  2. #define GOLDEN_RATIO_PRIME GOLDEN_RATIO_PRIME_32  
  3. #define hash_long(val, bits) hash_32(val, bits)  
  4. #elif BITS_PER_LONG == 64  
  5. #define hash_long(val, bits) hash_64(val, bits)  
  6. #define GOLDEN_RATIO_PRIME GOLDEN_RATIO_PRIME_64  
  7. #else  
  8. #error Wordsize not 32 or 64  
  9. #endif  

[cpp]  view plain copy
  1. static inline u32 hash_32(u32 val, unsigned int bits)  
  2. {  
  3.     /* On some cpus multiply is faster, on others gcc will do shifts */  
  4.     u32 hash = val * GOLDEN_RATIO_PRIME_32;  
  5.   
  6.     /* High bits are more random, so use them. */  
  7.     return hash >> (32 - bits);  
  8. }  

hash函数的原理就是乘一个非常大的数GOLDEN_RATIO_PRIME_32(会导致溢出),然后只使用了部分的高位最为结果。这个hash函数非常简洁,计算很快。但是hash结果如何取决于GOLDEN_RATIO_PRIME_32的选取。这个值接近黄金分割数会使得hash的结果最好。实际中Linux内核选取了0x9e370001UL这个值,因为它接近黄金分割数并且容易计算0x9e370001UL =  2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值