博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
linux内核的数据结构:2 散列表
阅读量:4028 次
发布时间:2019-05-24

本文共 2327 字,大约阅读时间需要 7 分钟。

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] 
  1. static struct hlist_head *pid_hash;  
[cpp] 
  1. struct hlist_head {  
  2.     struct hlist_node *first;  
  3. };  

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

[cpp] 
  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] 
  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] 
  1. #define pid_hashfn(nr, ns)  \  
  2.     hash_long((unsigned long)nr + (unsigned long)ns, pidhash_shift)  

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

[cpp] 
  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] 
  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。
你可能感兴趣的文章
非关系型数据库(nosql)介绍
查看>>
移动端自动化测试-Windows-Android-Appium环境搭建
查看>>
Xpath使用方法
查看>>
移动端自动化测试-Mac-IOS-Appium环境搭建
查看>>
Selenium之前世今生
查看>>
Selenium-WebDriverApi接口详解
查看>>
Selenium-ActionChains Api接口详解
查看>>
Selenium-Switch与SelectApi接口详解
查看>>
Selenium-Css Selector使用方法
查看>>
Linux常用统计命令之wc
查看>>
测试必会之 Linux 三剑客之 sed
查看>>
Socket请求XML客户端程序
查看>>
Java中数字转大写货币(支持到千亿)
查看>>
Java.nio
查看>>
函数模版类模版和偏特化泛化的总结
查看>>
VMware Workstation Pro虚拟机不可用解决方法
查看>>
最简单的使用redis自带程序实现c程序远程访问redis服务
查看>>
redis学习总结-- 内部数据 字符串 链表 字典 跳跃表
查看>>
iOS 对象序列化与反序列化
查看>>
iOS 序列化与反序列化(runtime) 01
查看>>