Linux進程管理中的hash

     

      Linux內核必須能夠根據進程的PID找出對應的PCB。順序掃描進程鏈表並檢查PCB的PID域是可行但效率相當低的。爲了加速查找,引入了哈希表,於是建立了一個pid_hash的結構。

1、pid_hash的聲明(kernel/pid.c):

static struct hlist_head *pid_hash;
  pid的初始化是在內核初始化(即start_kernel函數中)的時候完成的。由pidhash_init完成:

/*
 * The pid hash table is scaled according to the amount of memory in the
 * machine.  From a minimum of 16 slots up to 4096 slots at one gigabyte or
 * more.
 */
void __init pidhash_init(void)
{
	int i, pidhash_size;
        //爲數組開闢空間
	pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 0, 18,
					   HASH_EARLY | HASH_SMALL,
					   &pidhash_shift, NULL, 4096);
	pidhash_size = 1 << pidhash_shift;//數組的長度

	for (i = 0; i < pidhash_size; i++)
		INIT_HLIST_HEAD(&pid_hash[i]);//將數組中的指針都初始化爲NULL
}
   alloc_large_system_hash是專門用於爲hash表分配一塊連續的內存的,參數4096意味着最多4k個項,也就是最多佔用1頁的內存。哈希數組大小實際上由pidhash_shift決定,默認取4,直接影響了pidhash_size取的大小爲4096。
   pidhash_init 函數通過宏INIT_HLIST_HEAD把pid_hash數組的每個元素(struct hlist_head類型的變量)都初始化爲空指針。

2、hash函數的建立

   Linux用一個叫做pid_hashfn的宏來建立(kernel/pid.c文件中)

/*linux-2.6.32.63/kernel/pid.c*/
#define pid_hashfn(nr, ns)	\
	hash_long((unsigned long)nr + (unsigned long)ns, pidhash_shift)
	
/*linux-2.6.32.63/include/linux/hash.h  分析32bits*/
#define hash_long(val, bits) hash_32(val, bits)

/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */
#define GOLDEN_RATIO_PRIME_32 0x9e370001UL

static inline u32 hash_32(u32 val, unsigned int bits)
{
	/* On some cpus multiply is faster, on others gcc will do shifts */
	u32 hash = val * GOLDEN_RATIO_PRIME_32;

	/* High bits are more random, so use them. */
	return hash >> (32 - bits);
}
  其中,nr是pid的值,而ns表示的是pid的命名空間(這個是爲了支持輕量級虛擬化而引入的新概念)。 散列函數pid_hashfn先使關鍵字(nr和ns的和)乘以0x9e370001UL,然後取乘積的低pidhash_shift位。

   仔細分析一下上面的函數:

   首先,hash的方式是,讓key乘以一個大數,於是結果溢出,就把留在32/64位變量中的值作爲hash值,又由於散列表的索引長度有限,我們就取這hash值的高几爲作爲索引值,之所以取高几位,是因爲高位的數更具有隨機性,能夠減少所謂“衝突”。

   那麼,乘以的這個大數應該是多少呢?從上面的代碼來看,32位系統中這個數是0x9e370001UL。這個數是怎麼得到的呢?
   “Knuth建議,要得到滿意的結果,對於32位機器,2^32做黃金分割,這個大數是最接近黃金分割點的素數,0x9e370001UL就是接近 2^32*(sqrt(5)-1)/2 的一個素數,且這個數可以很方便地通過加運算和位移運算得到,因爲它等於2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1。

3、處理衝突

     Linux利用鏈地址法來處理衝突的PID,也就是說,每一個表項是由衝突的PID組成的雙向鏈表,task_struct結構中由兩個域pidhash_next和pidhash_prev來實現這個鏈表,同一個鏈表中pid由小到大排列。


學習博客:

      Linux內核中的PID散列表實例http://blog.csdn.net/npy_lp/article/details/7331245

      Linux內核中hash函數的實現http://blog.csdn.net/gaopenghigh/article/details/8831312

      linux內核PID管理http://blog.csdn.net/zhanglei4214/article/details/6765913(本文更加深入)

                     

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章