進程表示之進程ID號


UNIX進程總是會分配一個號碼用於在其命名空間總唯一地標識它們,該號碼稱作進程ID號,簡稱PID

1、進程ID

 但每個進程除了PID外,還有其他的ID,有下列幾種可能的類型

(1)處於某個線程組中的所有進程都有統一的線程組ID(TGID)。若進程沒有使用線程,則其PID和TGID相同。線程組中主進程被稱作組長(group leader)。通過clone創建的所有線程的task_struct的group_leader成員,會指向組長task_struct實例。

(2)獨立進程可以合並為進程組(使用setpgrp系統調用)。進程組簡化了向組的所有成員發送信號的操作,有助於各種系統程序設計應用,用管道連接的進程包含在同一個進程組。

(3)幾個進程組可以合並為一個會話。會話中所有進程都有同樣的會話ID,保存在task_struct的session中。SID可通過setsid系統調用設置。用於終端程序設計。

2、全局ID和局部ID

PID Namespace使得父命名空間可以看見所有子命名空間PID,但子命名空間無法看到父命名空間的PID,這意味着某些進程具有多個PID,凡可以看到該進程的命名空間,都會為其分配一個PID,由此需要區分全局ID和局部ID:

(1)全局ID:在內核本身和初始命名空間的唯一ID號,在系統啟動期間開始的init進程即屬於初始命名空間。對每個ID類型,都有一個給定的全局ID,保證在整個系統中是唯一的。

(2)局部ID:屬於某個特定命名空間,不具備全局有效性。

3、數據結構

 首先給出一個總圖,其中紅線是結構描述,黑線是指向。其中進程A,B,C是一個進程組的,A是組長進程,所以B和C的task_struct結構體中的pid_link成員的node字段就被鄰接到進程A對應的struct pid中的tasks[1]。

 

 

以下用ID指代提到的任何進程ID。

struct task_struct {
    ...
    pid_t pid;  //全局PID
    pid_t tgid;  //線程組ID
    struct task_struct *group_leader;  //指向線程組組長task_struct實例
    struct pid_link pids[PIDTYPE_MAX];  //PID和PID散列表的聯系,將所有共享同一ID的task_struct實例都按進程存儲在一個散列表中
    ...
};

 其中PIDTYPE_MAX表示ID類型的數目,枚舉類型中定義的ID類型不包括線程組ID,因為線程組ID即為線程組組長的PID:

enum pid_type
{
    PIDTYPE_PID,
    PIDTYPE_PGID,
    PIDTYPE_SID,
    PIDTYPE_MAX
};

task_struct中的輔助結構pid_link可以將task_struct連接到表頭在struct pid中的散列表上:

struct pid_link
{
    struct hlist_node node;  //散列表元素
    struct pid *pid;  //指向進程所屬pid結構實例
};

為了在給定命名空間中查找對應於指定PID數值的pid結構實例,使用了一個散列表:static struct hlist_head *pid_hash; hlist_head是內核標准數據結構,用於建立雙鏈散列表。

假如已經分配了struct pid的一個新實例,並設置用於給定的ID類型。它會通過如下附加到task_struct(kernel/pid.c):

void attach_pid(struct task_struct *task, enum pid_type type,
        struct pid *pid)
{
    struct pid_link *link;

    link = &task->pids[type];
    link->pid = pid;
    hlist_add_head_rcu(&link->node, &pid->tasks[type]);
}

這里建立雙向鏈表:task_struct可以通過task_struct->pids[type]->pid訪問pid實例;而從pid實例開始,可以遍歷task[type]散列表找到task_struct。hlist_add_head_rcu是遍歷散列表的標准函數。

 

PID的管理圍繞兩個數據結構:struct pid是內核對PID的內部表示,struct upid表示特定的命名空間中可見的信息:

struct pid
{  //內核對PID的內部表示
    atomic_t count;  //引用計數
    unsigned int level;  //這個pid所在的層級
    /* 使用該pid的進程的列表 */
    struct hlist_head tasks[PIDTYPE_MAX];  //每個數組項都是一個散列表表頭,對應一個ID類型。因為一個ID可能用於幾個進程,所有共享同一給定ID的task_struct實例,都通過該列表連接起來
    struct rcu_head rcu;
    struct upid numbers[1];  //這個pid對應的命名空間,一個pid不僅要包含當前的pid,還要包含父命名空間,默認大小為1,所以就處於根命名空間中,可添加附加項擴充
};
struct upid {  //包裝命名空間所抽象出來的一個結構體
    /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
    int nr;  //pid在該命名空間中的pid數值
    struct pid_namespace *ns;  //對應的命名空間
    struct hlist_node pid_chain;  //通過pidhash將一個pid對應的所有的命名空間連接起來(所有upid實例被保存在一個散列表中)
};
struct pid_namespace {
    struct kref kref;
    struct pidmap pidmap[PIDMAP_ENTRIES];  //一個pid命名空間應該有其獨立的pidmap
    int last_pid;  //上次分配的pid
    unsigned int nr_hashed;
    struct task_struct *child_reaper;  //每個PID命名空間都需要一個作用相當於全局init進程的進程,init的一個目的是對孤兒進程調用wait4,此保存了指向該進程的task_struct的指針
    struct kmem_cache *pid_cachep;
    unsigned int level;  //所在的命名空間層次,初始為0,子空間level為1。level較高命名空間的PID對level較低的命名空間的PID是可見的,從給定level設置,可知進程會關聯多少個PID
    struct pid_namespace *parent;  //指向父命名空間,構建命名空間的層次關系
        ...
};

4、函數操作

 本質上內核需要完成兩個任務:

(1)給出局部數字ID和對應命名空間,查找此二元組描述的task_struct。

(2)給出task_struct、ID類型、命名空間,取得命名空間局部數組ID。

對於(1)分解為兩步:

a.由局部PID和ns,確定pid實例。內核采用標准散列方式,首先,根據PID和ns指針計算在pid_hash數組中索引,然后遍歷散列表直至找到所要的upid實例,而由於這些實例直接包含在struct pid中,所以通過使用container_of機制可推斷出pid實例(kernel/pid.c):

struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
    struct upid *pnr;

    hlist_for_each_entry_rcu(pnr,
            &pid_hash[pid_hashfn(nr, ns)], pid_chain)
        if (pnr->nr == nr && pnr->ns == ns)
            return container_of(pnr, struct pid,
                    numbers[ns->level]);

    return NULL;
}

b.pid_task取出pid->task[type]散列表中的第一個task_struct實例(kernel/pid.c):

struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns)
{
    rcu_lockdep_assert(rcu_read_lock_held(),
               "find_task_by_pid_ns() needs rcu_read_lock()"
               " protection");
    return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID);
}

對於(2)也分為兩步:

a.獲得與task_struct關聯的pid實例:

static inline struct pid *task_pid(struct task_struct *task)
{
    return task->pids[PIDTYPE_PID].pid;
}

還可通過task_tgid、task_pgrp和task_session分別用於取得不同類型的ID:

static inline struct pid *task_tgid(struct task_struct *task)
{
    return task->group_leader->pids[PIDTYPE_PID].pid;
}
static inline struct pid *task_pgrp(struct task_struct *task)
{
    return task->group_leader->pids[PIDTYPE_PGID].pid;
}

b.從struct pid的numbers數組中upid信息,即可獲得數字ID:

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)
{
    struct upid *upid;
    pid_t nr = 0;

    if (pid && ns->level <= pid->level) {  //由於父ns可以看到子ns的PID,反過來不行,內核必須確保當前ns的level小於或等於產生局部ID的ns的level
        upid = &pid->numbers[ns->level];
        if (upid->ns == ns)
            nr = upid->nr;
    }
    return nr;
}

以下函數用於返回該ID所屬的ns所看到的局部PID:

pid_t pid_vnr(struct pid *pid)
{
    return pid_nr_ns(pid, task_active_pid_ns(current));
}

5、生成唯一PID

在建立一個新進程時,進程可能在多個ns中可見,對每個這樣的ns,都需要生成一個局部ID:

struct pid *alloc_pid(struct pid_namespace *ns)
{  //pid分配要依賴與pid namespace,也就是說這個pid是屬於哪個pid namespace
    struct pid *pid;
    enum pid_type type;
    int i, nr;
    struct pid_namespace *tmp;
    struct upid *upid;

    pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);  //分配一個pid結構
    if (!pid)
        goto out;

    tmp = ns;
    pid->level = ns->level;  //初始化level
    for (i = ns->level; i >= 0; i--) {   //遞歸到上面的層級進行pid的分配和初始化
        nr = alloc_pidmap(tmp);  //從當前pid namespace開始直到全局pid namespace,每一個層級都分配一個pid
        if (nr < 0)
            goto out_free;

        pid->numbers[i].nr = nr;  //初始化upid結構
        pid->numbers[i].ns = tmp;
        tmp = tmp->parent;  //遞歸到父親pid namespace
    }

    if (unlikely(is_child_reaper(pid))) {  //如果是init進程需要做一些設定,為其准備proc目錄
        if (pid_ns_prepare_proc(ns))
            goto out_free;
    }

    get_pid_ns(ns);
    atomic_set(&pid->count, 1);
    for (type = 0; type < PIDTYPE_MAX; ++type)  //初始化pid中的hlist結構
        INIT_HLIST_HEAD(&pid->tasks[type]);

    upid = pid->numbers + ns->level;  //定位到當前namespace的upid結構
    spin_lock_irq(&pidmap_lock);
    if (!(ns->nr_hashed & PIDNS_HASH_ADDING))
        goto out_unlock;
    for ( ; upid >= pid->numbers; --upid) {
        hlist_add_head_rcu(&upid->pid_chain,
                &pid_hash[pid_hashfn(upid->nr, upid->ns)]);  //建立pid_hash,讓pid和pid namespace關聯起來
        upid->ns->nr_hashed++;
    }
    spin_unlock_irq(&pidmap_lock);

out:
    return pid;

out_unlock:
    spin_unlock_irq(&pidmap_lock);
out_free:
    while (++i <= ns->level)
        free_pidmap(pid->numbers + i);

    kmem_cache_free(ns->pid_cachep, pid);
    pid = NULL;
    goto out;
}

 

參考:

linux-3.10.1內核源碼

《深入Linux內核架構》

https://blog.csdn.net/zhangyifei216/article/details/49926459

很清晰的一個講解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM