全面解析Linux 內核 3.10.x - Pid hash 鏈表

From: 全面解析Linux 內核 3.10.x - 進程管理

不管千山萬水,時間流逝,我們始終是有關係的 - 某某言情劇

何謂進程之間的關係?

在前面作總結的時候,說進程有一個標識ID,我們稱之爲進程描述符,描述符描述了進程的一些註冊優先級,狀態等一些值,其實這裏也有給出字段描述了進程的一些關係。
程序創建的進程具體父/子關係,如果一個進程創建多個子進程,則子進程之間具有兄弟關係,在進程描述符中引入幾個字段來表示這些關係,表示給定進程P的這些字段。進程0(swapper)和進程1是由內核創建的。而進程1(init)是所有進程的祖先!
進程0在很早的時候我就已經講過了,關於1號進程我會在後面和文件系統銜接的時候來描述([]可以戳這裏]())!
下面我重點去研究進程之間的各種親屬關係,到底在內核中是一張怎樣的複雜關係表?
我們知道的最多是進程之間的父子關係,Ps.一個進程可能是一個進程組或登錄會話的領頭進程,也可能是一個線程組的領頭進程,它還可能跟蹤其它進程的執行。
五個親屬間的親屬關係 -

內核中的pid hash鏈表

內核在初始化的時候有一個函數爲pidhash_init,這個函數要做什麼呢?
我們都知道,我們可能在很多情況下,都想知道進程的一些狀態,在這種情況,內核必須能夠從進程的PID導出對應的進程描述符指針,這樣我們才能獲取我們想要的進程信息!
譬如.kill 這個動作,當進程P1希望對進程P2發送一個信號時,P1調用kill調用,參數爲P2的PID,內核從這個PID導出其對應的進程描述符,取出描述符記錄掛起信號的數據結構指針。這裏提到了內核從PID,好,內核要去尋找PID?
我們在前面也說過,進程有自己單獨的鏈表叫進程鏈表,其原理是一個雙向鏈表!行,找PID,就是鏈表遍歷麼,咱們順序遍歷?No,因爲這樣做效率太低,而且浪費CPU,就這樣我們的pidhash就出來,我們將散列表的方式來查找!
看看pidhash_init 的源碼:

void __init pidhash_init(void)
{
    unsigned int i, pidhash_size;

    pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 0, 18,
                       HASH_EARLY | HASH_SMALL,
                       &pidhash_shift, NULL,
                       0, 4096); /*這裏爲項的大小設置和桶中含有的entry個數設置*/
    pidhash_size = 1U << pidhash_shift;

    for (i = 0; i < pidhash_size; i++)
        INIT_HLIST_HEAD(&pid_hash[i]);
}

上述代碼闡述爲2個步驟
第一:分配系統支持最大的hash_table,以及長度,在此期間內核動態的爲4個散列表分配空間,分別是PID(進程ID),TGID(線程組領頭進程的PID),PGID(進程組領頭進程的PID),SID(會話領頭進程的PID)。
第二:根據得到的長度初始化pid_hash.
好,至此效率問題已然解決。
但是你以爲問題結束了嗎? No.. 新的問題又出現了,我們都知道hash函數並不能總是確保PID與表的索引一一對應(會重複),我們使用hash當然是爲了效率,但是我們的目地是導出進程描述符對應的指針,那麼假如兩個PID同時對應一個指針?那豈不是本末倒置麼?效率倒是高了,可是問題沒解決呀?
上述問題已然表明PID對應的指針並不是唯一,我們稱他爲衝突。
內核中使用鏈表來處理衝突的PID,每一個表項是由衝突的進程描述符的進程描述組成的雙向鏈表。
爲了解決此問題,內核中給出了一種方法,我們稱它爲pid hash 鏈表結構。
根據上述源碼我們有幾個疑惑:
a.爲什麼pid hash表只定義2048或者4096項?爲何不能直接指定PID的大小?
這個其實主要是因爲內核要考慮到世紀的情況,不能那麼的任性,不能說你要多少,我就給你分配多少。假設可使用的內存有限,我還給你分配那麼多,腦子又沒有秀逗。
b.之前我說內核初始化的時候就已經將pid的最大值給確定了?這個是在哪裏呢?

    void __init pidmap_init(void)
    {
        /* Veryify no one has done anything silly */
        BUILD_BUG_ON(PID_MAX_LIMIT >= PIDNS_HASH_ADDING);

        /* bump default and minimum pid_max based on number of cpus */
        pid_max = min(pid_max_max, max_t(int, pid_max,
                    PIDS_PER_CPU_DEFAULT * num_possible_cpus()));
        pid_max_min = max_t(int, pid_max_min,
                    PIDS_PER_CPU_MIN * num_possible_cpus());
        pr_info("pid_max: default: %u minimum: %u\n", pid_max, pid_max_min);

        init_pid_ns.pidmap[0].page = kzalloc(PAGE_SIZE, GFP_KERNEL);
        /* Reserve PID 0. We never call free_pidmap(0) */
        set_bit(0, init_pid_ns.pidmap[0].page);
        atomic_dec(&init_pid_ns.pidmap[0].nr_free);
        init_pid_ns.nr_hashed = PIDNS_HASH_ADDING;

        init_pid_ns.pid_cachep = KMEM_CACHE(pid,
                SLAB_HWCACHE_ALIGN | SLAB_PANIC);
    }

pid_max: default: 32768 minimum: 301
上述代碼的意思利用kzalloc函數爲進程位圖提供一個物理上的頁框,並且此頁框被實現初始化爲0,這樣,一個大小爲4K的物理頁框,可以表示的進程號個數爲=4*1024*8=32768.但是由於進程號0的特殊性,我們事先將其設置爲不可用,也就是將位圖的0號位設置爲1,並把相應表示目前可用的pid號再-1.

待續..


By: Keven - 點滴積累

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