linux内核哈希查找(1)

在内核中,查找是必不可少的,比如说内核管理这么多用户进程,现在要快速定位
某一个进程,这儿需要查找,还有,一个进程的地址空间中有多个虚存区,内核要快速
定位进程地址空间的某个虚存区,这儿也需要查找,等等。其中用的最多就是基于树的
查找-------->红黑树。和基于计算的查找------->哈希查找。两者的查找的效率高,而且
适应内核的情况,而基于线性表的查找-------->二分查找,尽管效率高但不能适应内核
里面的情况,现在版本的内核几乎不可能使用数组管理一些数据,这太原始了。而二分
查找必须使用数组,所以内核中不用二分查找。
--------------------------------------------------------------------------------
1,内核哈希查找的初始化。(用于进程的快速查找)

  1. /*
  2. * The pid hash table is scaled according to the amount of memory in the
  3. * machine. From a minimum of 16 slots up to 4096 slots at one gigabyte or
  4. * more.
  5. */
  6. void __init pidhash_init(void)
  7. {
  8. int i, pidhash_size;
            //为数组开辟空间
  9. pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 0, 18,
  10. HASH_EARLY | HASH_SMALL,
  11. &pidhash_shift, NULL, 4096);
  12. pidhash_size = 1 << pidhash_shift;//数组的长度
  13. for (i = 0; i < pidhash_size; i )
  14. INIT_HLIST_HEAD(&pid_hash[i]);//将数组中的指针都初始化为NULL
  15. }

初始化思想很简单,就是建立一个哈希数组(自己定义的一个概念,哈希表中的那个数组),
并将该数组进行初始化,该函数在start_kernel中被调用,至于数组的长度,可以写个小的
内核模块很简单就可以把它的大小读出来。pidhash_shift是个全局的变量,没有导出,可以
在内核符号表中找到其地址,cat /proc/kallsyms | grep pidhash_shift 把该值打印出来就
知道数组的大小了。从上面的代码可以看出,这个数组的长度还依赖于机器内存的大小,一般
内存环境该数组的长度都为4096.下面有个小模块可以读出pidhash_shift的值。
--------------------------------------------------------------------------------------------

  1. 1 #include <linux/module.h>
  2. 2 #include <linux/kernel.h>
  3. 3 #include <linux/init.h>
  4. 4 #include <linux/moduleparam.h>
  5. 5 #include <linux/list.h>
  6. 6 #include <linux/hash.h>
  7. 7
  8. //cat /proc/kallsyms | grep pidhash_shift
  9. 8 unsigned int *p_shift = (unsigned int *)0xc176ab0c;
  10. 9
  11. 10 static int __init hash_size_init(void)
  12. 11 {
  13. 12 ---printk("pidhash_shift----------------->%u\n",*p_shift);
  14. 13 ---return 0;
  15. 14 }
  16. 15
  17. 16 static void __exit hash_size_exit(void)
  18. 17 {
  19. 18 printk("<1>exit ---------------------!\n");
  20. 19 }
  21. 20
  22. 21 module_init(hash_size_init);
  23. 22 module_exit(hash_size_exit);
  24. 23 MODULE_LICENSE("GPL");

--------------------------------------------------------------------------------------------
2,哈希函数的建立。
     哈希查找肯定离不开哈希函数,其实哈希函数就是一个数学函数,用来将实体分布在
哈希表中,前少冲突的发生。

  1. 40 #define pid_hashfn(nr, ns) -\
  2. 41 ---hash_long((unsigned long)nr (unsigned long)ns, pidhash_shift)
  1. 26 #define hash_long(val, bits) hash_32(val, bits)
  1. 19 /* 2^31 2^29 - 2^25 2^22 - 2^19 - 2^16 1 */
     20 #define GOLDEN_RATIO_PRIME_32 0x9e370001UL
  2. 57 static inline u32 hash_32(u32 val, unsigned int bits)
  3. 58 {
  4. 59 ---/* On some cpus multiply is faster, on others gcc will do shifts */
  5. 60 ---u32 hash = val * GOLDEN_RATIO_PRIME_32;
  6. 61
  7. 62 ---/* High bits are more random, so use them. */
  8. 63 ---return hash >> (32 - bits);
  9. 64 }

由此看出,内核将进程分布到哈希表的索引是由进程号和命名空间两个决定的,使用的
数学函数也比较复杂。我想这也是为了减少冲突的发生。
--------------------------------------------------------------------------------------------
3,如何避免哈希冲突。
     内核里面的哈希一般都是使用连地址法解决冲突,这种方式在应用上很可行,使用
双向链表把冲突的节点都链接起来。

-----------------------------------------------------------------------------------------
4,在创建进程的过程中往哈希表中插入进程的“索引”。
进程在alloc_pid函数中将struct  pid插入到哈希表中。
调用过程见下图:

----------------------------------------------------------------------------------------------
alloc_pid中的代码:

  1. spin_lock_irq(&pidmap_lock);
  2. for ( ; upid >= pid->numbers; --upid)
  3. hlist_add_head_rcu(&upid->pid_chain,
  4. &pid_hash[pid_hashfn(upid->nr, upid->ns)]);
  5. spin_unlock_irq(&pidmap_lock);

进程将创建好的struct pid中的nr和ns作为哈希函数的输入,从而计算出哈希数组的下标,
然后使用头插法,将struct hlist_node *pid_chain节点插入到链表中。
----------------------------------------------------------------------------------------------
5,使用哈希查找快速定位struct pid
在内核函数中,有个find_vpid函数,可以通过进程的pid快速找到进程的struct pid。
其中就是在建立好的哈希表中进行查找。

  1. struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
  2. {
  3. struct hlist_node *elem;
  4. struct upid *pnr;
  5. hlist_for_each_entry_rcu(pnr, elem,
  6. &pid_hash[pid_hashfn(nr, ns)], pid_chain)
  7. if (pnr->nr == nr && pnr->ns == ns)
  8. return container_of(pnr, struct pid,
  9. numbers[ns->level]);
  10. return NULL;
  11. }

find_vpid就是直接调用find_pid_ns,该函数的两个参数,一个是进程pid,一个是命名空间ns
通过这两个就可以使用哈希函数定位到哈希数组的下标索引,然后遍历该数组单元的链表即可找到
进程的struct pid。

时间: 2024-08-03 07:01:29

linux内核哈希查找(1)的相关文章

Linux内核哈希表分析与应用

目录(?)[+] Author:tiger-johnTime:2012-12-20mail:[email protected]Blog:http://blog.csdn.NET/tigerjb/article/details/8450995 转载请注明出处. 前言: 1.基本概念: 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的

Linux内核哈希表中的bucket桶

哈希表 哈希表(Hashtable)又称为“散列”,Hashtable是会根据索引键的哈希程序代码组织成的索引键(Key)和值(Value)配对的集合.Hashtable 对象是由包含集合中元素的哈希桶(Bucket)所组成的.而Bucket是Hashtable内元素的虚拟子群组,可以让大部分集合中的搜寻和获取工作更容易.更快速. 哈希函数(Hash Function)为根据索引键来返回数值哈希程序代码的算法.索引键(Key)是被存储对象的某些属性值(Value).当对象加入至 Hashtabl

Linux内核中的哈希表

Author:tiger-john Time:2012-12-20mail:[email protected]Blog:http://blog.csdn.net/tigerjb/article/details/8450995 转载请注明出处. 前言: 1.基本概念: 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的数组叫做散列表.

操作系统 之 哈希表 Linux 内核 应用浅析

1.基本概念         散列表(Hash  table.也叫哈希表).是依据关键码值(Key  value)而直接进行訪问的数据结构. 也就是说,它通过把关键码值映射到表中一个位置来訪问记录.以加快查找的速度. 这个映射函数叫做散列函数.存放记录的数组叫做散列表. 2. 经常使用的构造散列函数的方法 散列函数能使对一个数据序列的訪问过程更加迅速有效.通过散列函数.数据元素将被更快地定位.散列表的经常使用构造方法有: (1)直接定址法 (2)数字分析法 (3)平方取中法 (4)折叠法 (5)

Linux内核协议栈的socket查找缓存路由机制

是查路由表快呢?还是查socket哈希表快?这不是问题的根本.问题的根本是怎么有效利用这两者,让两者成为合作者而不是竞争者.这是怎么回事?       我们知道,如果一个数据包要到达本地,那么它要经过两次查找过程(暂时不考虑conntrack):IP层查找路由和传输层查找socket.怎么合并这两者.       Linux内核协议栈采用了一种办法:在socket中增加一个dst字段作为缓存路由的手段,skb在查找路由之前首先查找socket,找到的话,就将缓存的dst设置到skb,接下来在查找

linux内核中的哈希散列表

    介绍一下linux内核中的哈希散列表的实现,在linux内核中哈希散列表(hash list)用的非常的多, 并且自己以后在做软件设计的时候,也非常有可能用到.毕竟,哈希散列表在数据的查找过程中非常的方便.      linux内核对哈希散列表的实现非常的完美,所以非常有必要学习一下. 在哈希散列表的实现过程中,用到的两个非常有用的结构体:      哈希散列表头结构体 :                          struct hlist_head               

20135201李辰希《Linux内核分析》第六周 进程的描述与创建

李辰希 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.进程的描述 操作系统的三大管理功能: 进程管理(最重要的) 内存管理 文件系统 为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需了解的进程信息. 进程控制块PCB task_struct: 进程状态 进程打开的文件 进程优先级信息 task_struct总体数据结构的抽象: tty:控制台 fs:文件系统

20135201李辰希《Linux内核分析》第一周 计算机是如何工作的?

计算机是如何工作的 1冯诺依曼体系结构:即具有存储程序的计算机体系结构 目前大多数拥有计算和存储功能的设备(智能手机.平板.计算机等)其核心构造均为冯诺依曼体系结构 从硬件来看:CPU与内存通过主线连接,CPU上的IP(可能是16.32.64位)总指向内存的某一块区域:IP指向的CS(代码段)也在内存中:CPU总是执行IP指向的指令. 从软件来看:API(应用程序编程接口,与编程人员)与ABI(程序与CPU的借口界面) 是两个比较重要的软件接口 2. x86(32位)的寄存器中,低16位作为16

20135201李辰希 《Linux内核分析》第七周 可执行程序的装载

李辰希  原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.预处理.编译.链接和目标文件的格式 1.可执行程序是怎么得来的 编译链接的过程 1.预处理阶段 gcc -E -o XX.cpp XX.c -m32 XX.cpp是预处理文件 2.编译器生成汇编代码阶段 gcc -x cpp-output -S -o hello.s hello.cpp -m32 XX.s是汇编代码 3.汇编器