Linux内核中的PID散列表实例

开发平台:基于虚拟机的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。 

  1. /* linux-2.6.38.8/kernel/pid.c */  
  2. void __init pidhash_init(void)  
  3. {  
  4.     int i, pidhash_size;  
  5.   
  6.     pid_hash = alloc_large_system_hash("PID"sizeof(*pid_hash), 0, 18,  
  7.                        HASH_EARLY | HASH_SMALL,  
  8.                        &pidhash_shift, NULL, 4096);  
  9.     pidhash_size = 1 << pidhash_shift;  
  10.   
  11.     for (i = 0; i < pidhash_size; i++)  
  12.         INIT_HLIST_HEAD(&pid_hash[i]);  
  13. }  

    其中的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散列表到底有多大。 

  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个。 

  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文件中。 

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

    散列函数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则表示特定的命名空间中可见的信息。 

  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.   
  10. struct upid {  
  11.     /* Try to keep pid_chain in the same cacheline as nr for find_vpid */  
  12.     int nr;  
  13.     struct pid_namespace *ns;  
  14.     struct hlist_node pid_chain;  
  15. };  
  16.   
  17. struct pid  
  18. {  
  19.     atomic_t count;  
  20.     unsigned int level;  
  21.     /* lists of tasks that use this pid */  
  22.     struct hlist_head tasks[PIDTYPE_MAX];  
  23.     struct rcu_head rcu;  
  24.     struct upid numbers[1];  
  25. };  

    在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函数来建立它们之间的链接。 

  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.   
  8. /* linux-2.6.38.8/include/linux/sched.h */  
  9. struct task_struct {  
  10.     ...  
  11.   
  12.     struct pid_link pids[PIDTYPE_MAX];  
  13.   
  14.     ...  
  15. }  
  16.   
  17. /* linux-2.6.38.8/kernel/pid.c */  
  18. void attach_pid(struct task_struct *task, enum pid_type type,  
  19.         struct pid *pid)  
  20. {  
  21.     struct pid_link *link;  
  22.   
  23.     link = &task->pids[type];  
  24.     link->pid = pid;  
  25.     hlist_add_head_rcu(&link->node, &pid->tasks[type]);  
  26. }  

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

 

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

    (1)、插入运算

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

  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.   
  10.     pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);  
  11.     if (!pid)  
  12.         goto out;  
  13.   
  14.     tmp = ns;  
  15.     for (i = ns->level; i >= 0; i--) {  
  16.         nr = alloc_pidmap(tmp);  
  17.         if (nr < 0)  
  18.             goto out_free;  
  19.   
  20.         pid->numbers[i].nr = nr;  
  21.         pid->numbers[i].ns = tmp;  
  22.         tmp = tmp->parent;  
  23.     }  
  24.   
  25.     get_pid_ns(ns);  
  26.     pid->level = ns->level;  
  27.     atomic_set(&pid->count, 1);  
  28.     for (type = 0; type < PIDTYPE_MAX; ++type)  
  29.         INIT_HLIST_HEAD(&pid->tasks[type]);  
  30.   
  31.     upid = pid->numbers + ns->level;  
  32.     spin_lock_irq(&pidmap_lock);  
  33.     for ( ; upid >= pid->numbers; --upid)  
  34.         hlist_add_head_rcu(&upid->pid_chain,  
  35.                 &pid_hash[pid_hashfn(upid->nr, upid->ns)]);  
  36.     spin_unlock_irq(&pidmap_lock);  
  37.   
  38. out:  
  39.     return pid;  
  40.   
  41. out_free:  
  42.     while (++i <= ns->level)  
  43.         free_pidmap(pid->numbers + i);  
  44.   
  45.     kmem_cache_free(ns->pid_cachep, pid);  
  46.     pid = NULL;  
  47.     goto out;  
  48. }  

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

    (2)、查找运算

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

  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.   
  7.     hlist_for_each_entry_rcu(pnr, elem,  
  8.             &pid_hash[pid_hashfn(nr, ns)], pid_chain)  
  9.         if (pnr->nr == nr && pnr->ns == ns)  
  10.             return container_of(pnr, struct pid,  
  11.                     numbers[ns->level]);  
  12.   
  13.     return NULL;  
  14. }  

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

阅读更多

没有更多推荐了,返回首页