轉自:http://blog.chinaunix.net/uid-24227137-id-3595819.html
系統在初始化期間手動的創建了一個含有PIDTYPE_MAX個元素的指針數組。
用於存放指向hash桶的指針。
並且在start_kernel期間調用pidhash_init函數對hash桶進行初始化。
303 void __init pidhash_init(void)
/* */
304 {
305 int i, j, pidhash_size;
306 unsigned long megabytes = nr_kernel_pages >> (20 - PAGE_SHIFT);
307
308 pidhash_shift = max(10, fls(megabytes * 4));
309 pidhash_shift = min(12, pidhash_shift);
310 pidhash_size = 1 << pidhash_shift;
311
312 printk("PID hash table entries: %d (order: %d, %Zd bytes)\n",
313 pidhash_size, pidhash_shift,
314 PIDTYPE_MAX * pidhash_size * sizeof(struct hlist_head));
315 #######以上爲桶的大小設置和桶中含有的entry個數設置
#######dmesg結果顯示:PID hash table entries: 4096 (order: 12, 16384 bytes)
#######pidhash_size(桶大小):4096*4bytes entries:4096
#######也就是說桶中存有4096個指針,指針指向struct hlist_node結構
316 for (i = 0; i < PIDTYPE_MAX; i++) {
317 pid_hash[i] = alloc_bootmem(pidhash_size *
318 sizeof(*(pid_hash[i])));
#######在系統啓動期間slab和alloc分配器都還沒有建立好
#######採用allocbootmem內存分配器對系統初始化期間的內存進行分配
319 if (!pid_hash[i])
320 panic("Could not alloc pidhash!\n");
321 for (j = 0; j < pidhash_size; j++)
322 INIT_HLIST_HEAD(&pid_hash[i][j]);
#######將桶中指向struct hlist_node 結構的指針first初始化爲NULL
323 }
經過這個函數 pid哈希鏈表 被初始化完成。
系統在初始化期間手動的爲表示進程pid號的位圖分配空間並且初始化。
- 50 typedef struct pidmap
{
- 51 atomic_t nr_free;
- 52 void *page;
- 53 } pidmap_t;
- 55 static pidmap_t pidmap_array[PIDMAP_ENTRIES]
=
- 56 { [ 0 ... PIDMAP_ENTRIES-1 ] = { ATOMIC_INIT(BITS_PER_PAGE), NULL } };
位圖初始化期間:利用get_zeroed_page函數爲位圖提供一個物理上的頁框,並且此頁框被實現初始化爲0.
這樣,一個大小爲4K的物理頁框,可以表示的進程號個數爲:4*1024*8=32768;
但是有一點我們要主要:由於進程號0的特殊性,我們事先將其設置爲不可用,也就是將位圖的0號位設置爲1,並把相應表示目前可用的pid號的個數減1.
- 326 void __init pidmap_init(void)
- /*
*/
- 327 {
- 328 int i;
- 329
- 330 pidmap_array->page
= (void
*)get_zeroed_page(GFP_KERNEL);
- 331 set_bit(0, pidmap_array->page);
- 332 atomic_dec(&pidmap_array->nr_free);
- 333
- 334 /*
- 335 * Allocate PID 0,
and hash it via all PID types:
- 336 */
- 337
- 338 for (i
= 0; i
< PIDTYPE_MAX; i++)
- 339 attach_pid(current, i, 0);
- 340 }
上述代碼通過attach_pid函數將0號進程進行hash,將其hash到上面已經初始化好的hash鏈表桶中。
我們知道每個進程中都有:struct pid pids[PIDTYPE_MAX];而hash鏈表正是通過這個結構進行鏈接。
上述函數中,首先通過find_pid函數在指定類型和nr號的哈希鏈表中查找pid結構。如果找到則返回指向struct pid的指針 否則返回NULL。
接下來的代碼根據find_pid的返回值,做出相應的動作。
1)pid爲NULL,說明新attach的包含struct pid的進程描述符是哈希鏈表的第一個元素,將其插入頭結點之後,並將struct pid的pid_list字段設置爲NULL。struct list_head pid_list是由於連接具有相同nr號的進程描述符的連接件。
2)pid不爲NULL,說明相應的哈希鏈表中已經存在了nr號爲“nr”的進程描述符,我們只需將我們的進程描述符attach到相應的哈希鏈表的進程鏈表中。
通過上面的兩個函數我們還可以得知:所有桶中(一般爲4個)0號索引對應的哈希鏈表的第一個進程描述符元素都爲0號進程的進程描述符。
而與attach_pid函數對應的操作爲dettach_pid:將pid所在的進程描述符從對應的pid哈希鏈表中刪除,這個函數的定義如下:
- 192 void fastcall detach_pid(task_t
*task, enum pid_type type)
- /*
*/
- 193 {
- 194 int tmp, nr;
- 195
- 196 nr = __detach_pid(task, type);
- 197 if (!nr)
- 198 return;
- 199
- 200 for (tmp
= PIDTYPE_MAX;
--tmp
>= 0;
)
- 201 if (tmp
!= type
&& find_pid(tmp, nr))
- 202 return;
- 203
- 204 free_pidmap(nr);
- 205 }
核心函數爲__detach_pid(task,type)
- 166 static fastcall int __detach_pid(task_t
*task, enum pid_type type)
- /*
*/
- 167 {
- 168 struct pid *pid,
*pid_next;
- 169 int nr = 0;
- 170
- 171 pid = &task->pids[type];
- 172 if (!hlist_unhashed(&pid->pid_chain))
{
- 173
- 174 if (list_empty(&pid->pid_list))
{
- 175 nr = pid->nr;
- 176 hlist_del_rcu(&pid->pid_chain);
- 177 } else
{
- 178 pid_next = list_entry(pid->pid_list.next,
- 179 struct pid, pid_list);
- 180 /* insert
next pid from pid_list
to hash */
- 181 hlist_replace_rcu(&pid->pid_chain,
- 182 &pid_next->pid_chain);
- 183 }
- 184 }
- 185
- 186 list_del_rcu(&pid->pid_list);
- 187 pid->nr
= 0;
- 188
- 189 return nr;
- 190 }
函數首先判斷task指向的進程是否已經被hash進pid哈希鏈表,注意有可能是哈希鏈表中的進程鏈表。
如果不是哈希鏈表的節點,直接將其從所在的進程鏈表中刪除。此時返回nr=0,我們後面可以看到,detach_pid函數根據nr值來判斷是否需要將pid位圖中相應的pid位清0,以此來使繼續可以被使用。
如果是hash鏈表中的節點,還要判斷
1)是否此哈希鏈表節點所在的進程鏈表只有這個節點。如果是將nr設置爲pid->nr 並將這個節點刪除
2)如果哈希鏈表節點所在的進程鏈表還有別的節點。那麼用下一個節點replace這個節點。注意此時並未設置nr的值。也就是說返回的nr=0
當從__datach_pid返回後,接下來的任務就是判斷是否需要將pid對應的pid位圖中相應的位清0
1)如果nr=0,不需要清0 函數直接返回。通過上面我們知道,函數在兩種情況下會返回nr=0 :
一,進程鏈表非空(此時暗指的含義就是這個pid還被其餘的進程/線程使用着,不能在位圖中將其釋放;
二,刪除的就是nr=0的進程描述符。這是也不需要在位圖中將其釋放;因爲0號pid比較特殊是不允許其他進程使用的。
2)如果nr不等於0,還要判斷在其他類型的哈希鏈表中是否存在這樣的進程描述符,如果存在也表明其正在使用不能釋放。
如果上述兩步都沒有return那麼的確是需要將pid位圖中想的pid表示位清0,以供系統使用。
上述的代碼中有一處理解容易出現問題:
- 200 for
(tmp = PIDTYPE_MAX;
--tmp
>= 0;
)
- 201 if (tmp
!= type
&& find_pid(tmp, nr))
- 202 return;
爲什麼只搜索其他類型的hash鏈表而不處理我們剛剛將其刪pid的類型的hash鏈表呢?因爲對於剛剛刪除的進程描述符的類型的hash鏈表 我們應經用nr的值來表明是否需要清位圖位了。
如果nr=0那麼刪除的進程描述符所在的進程鏈表是非空的---不必刪除
如果進程鏈表是空的那麼nr=pid-->nr我們不考慮0號pid那麼這個nr一定爲非0就說明我這個類型的hash鏈表不用這個進程號了 你查查其他類型的hash鏈表是否還使用吧。
如果其他有一個在使用,OK 不刪返回。如果都不用,那麼我就清理門戶pidbitmap 相應位設置爲0了。