开发平台:基于虚拟机的Ubuntu 11.04

内核源码:linux-2.6.38.8.tar.bz2

目标平台:ARM体系结构

参考文献:《深入Linux内核架构》

关于散列表的概念可以参考博文《散列表的基本概念及其运算》。

1、PID散列表

PID散列表是在系统启动过程中通过pidhash_init函数(被start_kernel函数所调用)所创建的。

PID散列表实际上就是一个像struct hlist_head pid_hash[i]这样的数组,其中i的大小由alloc_large_system_hash函数所确定,最大取值为4096,最小值为16。

[cpp] view plaincopy
  1. /* linux-2.6.38.8/kernel/pid.c */
  2. void __init pidhash_init(void)
  3. {
  4. int i, pidhash_size;
  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. for (i = 0; i < pidhash_size; i++)
  10. INIT_HLIST_HEAD(&pid_hash[i]);
  11. }

其中的alloc_large_system_hash函数能够根据机器物理内存的大小灵活地分配散列表的存储空间,以及改变pidhash_shift变量的默认值(默认值为4),从而确定pid_hash数组的大小(pidhash_size)。

最后,pidhash_init函数通过宏INIT_HLIST_HEAD把pid_hash数组的每个元素(struct hlist_head类型的变量)都初始化为空指针。

alloc_large_system_hash函数的实现比较复杂,没有时间也没有必要去分析它的每个语句,可以通过其中的打印语句来查看系统中的PID散列表到底有多大。

[cpp] view plaincopy
  1. /* linux-2.6.38.8/mm/page_alloc.c */
  2. printk(KERN_INFO "%s hash table entries: %ld (order: %d, %lu bytes)\n",
  3. tablename,
  4. (1UL << log2qty),
  5. ilog2(size) - PAGE_SHIFT,
  6. size);

例如,在基于虚拟机的Ubuntu 11.04中,当它的物理内存为512MB时,PID散列表的表项为2048个;当把物理内存修改为1GB时,PID散列表的表项提升到最大值4096个。

[cpp] view plaincopy
  1. //512MB物理内存
  2. $ dmesg | grep "PID hash table entries"
  3. [    0.000000] PID hash table entries: 2048 (order: 1, 8192 bytes)
  4. //1GB物理内存
  5. $ dmesg | grep "PID hash table entries"
  6. [    0.000000] PID hash table entries: 4096 (order: 2, 16384 bytes)

2、散列函数

PID散列表的散列函数为pid_hashfn,定义在linux-2.6.38.8/kernel/pid.c文件中。

[cpp] view plaincopy
  1. #define pid_hashfn(nr, ns)  \
  2. hash_long((unsigned long)nr + (unsigned long)ns, pidhash_shift)
  3. /* linux-2.6.38.8/include/linux/hash.h */
  4. #define hash_long(val, bits) hash_32(val, bits)
  5. #define GOLDEN_RATIO_PRIME_32 0x9e370001UL
  6. static inline u32 hash_32(u32 val, unsigned int bits)
  7. {
  8. /* On some cpus multiply is faster, on others gcc will do shifts */
  9. u32 hash = val * GOLDEN_RATIO_PRIME_32;
  10. /* High bits are more random, so use them. */
  11. return hash >> (32 - bits);
  12. }

散列函数pid_hashfn先使关键字(nr和ns的和)乘以0x9e370001UL,然后取乘积的低pidhash_shift位(即bit[0]到bit[pidhash_shift-1])。例如,对于拥有2048个表项的PID散列表,散列函数pid_hashfn的返回值(取乘积的低11位)最终都会落在0到2047之间。

3、处理冲突

PID散列表采用链地址法来处理冲突。

4、PID散列表的运算函数

在介绍PID散列表的运算函数之前,先介绍一下相关的结构体。

struct pid是内核对PID的内部表示,而struct upid则表示特定的命名空间中可见的信息。

[cpp] view plaincopy
  1. /* linux-2.6.38.8/include/linux/pid.h */
  2. enum pid_type
  3. {
  4. PIDTYPE_PID,
  5. PIDTYPE_PGID,
  6. PIDTYPE_SID,
  7. PIDTYPE_MAX
  8. };
  9. struct upid {
  10. /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
  11. int nr;
  12. struct pid_namespace *ns;
  13. struct hlist_node pid_chain;
  14. };
  15. struct pid
  16. {
  17. atomic_t count;
  18. unsigned int level;
  19. /* lists of tasks that use this pid */
  20. struct hlist_head tasks[PIDTYPE_MAX];
  21. struct rcu_head rcu;
  22. struct upid numbers[1];
  23. };

在struct upid中,nr表示ID(这里ID的类型有三种,定义在pid_type枚举中)的数值,ns是指向该ID所属命名空间的指针,所有的upid实例都通过pid_chain成员链接到pid_hash散列表中。

在struct pid中,count表示一个引用计数器,level表示该进程的命名空间在命名空间层次结构中的深度,而numbers是一个struct upid实例的数组,每个数组项(形式上只有一个数组项,但实际上可以根据需要进行扩展)都对应着一个命名空间。Tasks是共享此struct pid实例的所有进程的链表表头,其中的进程通过它的pids[type]成员来构建链接。在Linux内核中,通过attach_pid函数来建立它们之间的链接。

[cpp] view plaincopy
  1. /* linux-2.6.38.8/include/linux/pid.h */
  2. struct pid_link
  3. {
  4. struct hlist_node node;
  5. struct pid *pid;
  6. };
  7. /* linux-2.6.38.8/include/linux/sched.h */
  8. struct task_struct {
  9. ...
  10. struct pid_link pids[PIDTYPE_MAX];
  11. ...
  12. }
  13. /* linux-2.6.38.8/kernel/pid.c */
  14. void attach_pid(struct task_struct *task, enum pid_type type,
  15. struct pid *pid)
  16. {
  17. struct pid_link *link;
  18. link = &task->pids[type];
  19. link->pid = pid;
  20. hlist_add_head_rcu(&link->node, &pid->tasks[type]);
  21. }

以上所述各种结构体的关系如下图所示(图片修改自《professional linux kernel architecture》):

在Linux内核中,使用过散列函数pid_hashfn的只有alloc_pid和find_pid_ns两个函数而已。

(1)、插入运算

alloc_pid函数用于创建struct pid结构体实例。

[cpp] view plaincopy
  1. /* linux-2.6.38.8/kernel/pid.c */
  2. struct pid *alloc_pid(struct pid_namespace *ns)
  3. {
  4. struct pid *pid;
  5. enum pid_type type;
  6. int i, nr;
  7. struct pid_namespace *tmp;
  8. struct upid *upid;
  9. pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
  10. if (!pid)
  11. goto out;
  12. tmp = ns;
  13. for (i = ns->level; i >= 0; i--) {
  14. nr = alloc_pidmap(tmp);
  15. if (nr < 0)
  16. goto out_free;
  17. pid->numbers[i].nr = nr;
  18. pid->numbers[i].ns = tmp;
  19. tmp = tmp->parent;
  20. }
  21. get_pid_ns(ns);
  22. pid->level = ns->level;
  23. atomic_set(&pid->count, 1);
  24. for (type = 0; type < PIDTYPE_MAX; ++type)
  25. INIT_HLIST_HEAD(&pid->tasks[type]);
  26. upid = pid->numbers + ns->level;
  27. spin_lock_irq(&pidmap_lock);
  28. for ( ; upid >= pid->numbers; --upid)
  29. hlist_add_head_rcu(&upid->pid_chain,
  30. &pid_hash[pid_hashfn(upid->nr, upid->ns)]);
  31. spin_unlock_irq(&pidmap_lock);
  32. out:
  33. return pid;
  34. out_free:
  35. while (++i <= ns->level)
  36. free_pidmap(pid->numbers + i);
  37. kmem_cache_free(ns->pid_cachep, pid);
  38. pid = NULL;
  39. goto out;
  40. }

起始于建立进程的命名空间,一直到初始的全局命名空间,内核会为其中的每个命名空间分别创建一个局部PID,并把它们(用struct upid表示)都添加到pid_hash散列表中。

(2)、查找运算

find_pid_ns函数根据PID和命名空间指针来查找相应的struct pid实例。

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

其中,首先使用散列函数pid_hashfn确定所查找结点在散列表pid_hash中的表头,然后遍历表头所指向的链表,当nr和ns都匹配时,即找到了所需的struct upid结点,最后根据struct upid结点通过container_of宏获得包含它的struct pid实例。

Linux内核中的PID散列表实例相关推荐

  1. Linux内核中makefile有什么作用?深入解析makefile工作过程和原理

    Table of Contents Makefile 中的变量 常用的变量有以下几类: 1) 版本信息 2) CPU 体系结构:ARCH 3) 路径信息:TOPDIR, SUBDIRS 4) 内核组成 ...

  2. Linux 内核中的 GCC 特性(zz)

    from:http://www.ibm.com/developerworks/cn/linux/l-gcc-hacks/ GCC 和 Linux 是出色的组合.尽管它们是独立的软件,但是 Linux ...

  3. Linux内核中的GPIO系统之(3):pin controller driver代码分析

    一.前言 对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道,pin control subsystem也不例外,被它驱动的硬件叫做pin controller(一般ARM soc的datash ...

  4. 什么是Linux系统调用system call?(Linux内核中设置的一组用于实现各种系统功能的子程序)(区别于标准C库函数调用)核心态和用户态的概念、中断的概念、系统调用号、系统调用表

    文章目录 什么是系统调用? 为什么要用系统调用? 系统调用是怎么工作的? 如何使用系统调用? _syscall*()是什么? errno是什么? 调用性能问题 Linux系统调用列表 进程控制 文件系 ...

  5. Linux 内核中的 cdev_alloc和cdev_add

    内核 中每个字符 设备 都对应一个 cdev 结构的变量,下面是它的定义: linux -2.6.22/include/linux/cdev.h struct cdev { struct kobjec ...

  6. linux下IPROTO_TCP,TCP/IP协议栈在Linux内核中的运行时序分析

    可选题目三:TCP/IP协议栈在Linux内核中的运行时序分析 在深入理解Linux内核任务调度(中断处理.softirg.tasklet.wq.内核线程等)机制的基础上,分析梳理send和recv过 ...

  7. Linux 内核中的数据结构:双链表,基数树,位图

    Linux 内核中的数据结构 rtoax 2021年3月 1. 双向链表 Linux 内核自己实现了双向链表,可以在 include/linux/list.h 找到定义.我们将会从双向链表数据结构开始 ...

  8. linux内核中锁有哪些,Linux内核中有哪些锁

    Linux内核中的各种锁 在LInux操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实象多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访问.尤其是在多处理器系统上,更需 ...

  9. KSM(Kernel Samepage Merging) 剖析:Linux 内核中的内存去耦合

    简介:作为一个系统管理程序(hypervisor),Linux® 有几个创新,2.6.32 内核中一个有趣的变化是 KSM(Kernel Samepage Merging)  允许这个系统管理程序通过 ...

  10. Linux内核中的软中断、tasklet和工作队列详解

    本文基于Linux2.6.32内核版本. 引言 软中断.tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的"下半部"(bottom half)演 ...

最新文章

  1. HASH 大量插入与查询
  2. 戏说 Windows GDI (1)
  3. mysql8创建用户并授权_新版mysql8.0.12添加用户并设置权限避免踩坑!
  4. MySQL复习资料(二)——MySQL-DDL语句
  5. 别看360完成私有化 仍有三因素阻碍中概股回归
  6. Ubuntu 安装 samba 实现文件共享和source insight 阅读uboot
  7. python测试系列教程——python+Selenium+chrome自动化测试框架
  8. 8.8LSTM作为元学习器学习梯度下降
  9. 免费的ERP软件哪一款好用?这3款让综合管理更高效
  10. 【Qt】Qt5.14.2配置yaml-cpp
  11. Caffe 框架理解
  12. CentOS 7.9命令行配置有线网卡
  13. nginx 配置https 负载均衡
  14. matlab冲激函数的傅里叶变换,利用MATLAB对正弦,矩形脉冲函数进行傅里叶变换
  15. Android安全论文汇集
  16. python 多线程 代理 爬取 豆果美食app
  17. Ubuntu突然无法解析域名解决
  18. 篮球赛日程表_横县校椅青桐2019春节篮球赛火热开赛!快收好赛程表!
  19. LeetCode 2203. 得到要求路径的最小带权子图(dijkstra算法)
  20. 《Windows不欢迎你,你爱用什么系统就用什么去吧》

热门文章

  1. Go语言实战 - 网站性能优化第一弹“七牛云存储”
  2. linux gcc下实现简单socket套接字小程序
  3. 绘制自己组合的k线图_史上最全K线图大全:搞懂70种K线组合形态,轻松低买高卖不踏空...
  4. intellij安装scala插件
  5. 踩坑事件:windows操作系统下的eclipse中编写SparkSQL不能从本地读取或者保存parquet文件
  6. spring mvc异步操作处理,注解方式
  7. 40条真言,希望对进阶中的程序朋友有所帮助。
  8. 五、谈扩展方法的理解
  9. makefile函数集锦【转】
  10. 【心得】Web设计师应参考的技术