linux pid名字空間

最近看《深入理解linux內核架構》中pid名字空間一塊,略有感悟。這裏開門見山,直接來講pid名字空間是如何實現的,以及如何使用。先看一幅圖,圖中描述了所使用的數據結構和他們的關係。

linux pid名字空間圖
首先強調圖中的幾個比較容易搞混的字段。看task_struct中的struct pid_link *pids數組中,每個元素裏有一個struct hlist_node node字段。也就是說,可以利用這個字段把task_struct加入到一個哈希表中。再看右邊的struct pid結構中的struct hlist_head tasks[]數組,明顯這個hlist_head,也就是表頭,這個地方的每個數組元素都可以是一個鏈表。由於這裏數據結構比較多,所以一定要注意是一個內結點,還是一個鏈表的頭結點。

圖中綠線(無箭頭)表示頭結點把中間結點鏈接成了一個鏈。而藍色單箭頭線表示,是隻有一個指向目標結點的指針。藍色雙箭頭線表示是雙向鏈表。

爲什麼要搞這麼複雜的結構呢?因爲滿足一些需求。
搞pid名字空間就是爲了把一些進程隔離開來,讓他們以爲他們獨佔了整個系統,在虛擬化等中有應用。
還有,內核中經常需要由PID的數字值得到進程task_struct。(struct hlist_head *pid_hash的作用)。
需要由PID數字值得到進程組ID,會話ID。
當一個會話結束了,需要終止屬於這個會話的所有進程。也就是要由會話ID找到所有屬於這個會話的進程task_struct。(看看struct pid中有個SID字段,這裏鏈接着屬於這個PID的所有會話進程)。
linux用輕量級進程模擬線程,所以需要由線程組長ID找到所有屬於這個進程的線程ID。

新創建一個進程是怎麼影響名字空間的?

新建進程時的調用棧如下圖。可以看出這是在創建內核線程。創建用戶線程是調用fork()系統調用,這fork裏也調用了do_fork(),也就是圖中的do_fork(). do_fork中調用了copy_process函數,在這個函數中子進程複製了父進程的task_struct,其中就複製了task_struct中的struct pid_link pids[]數組。也就是說這時子進程與父進程有一樣的PID,SID,GID。不過不用擔心,這時子進程沒有運行,在copy_process後邊會重新給pids[PID].pid賦值。
新建進程調用棧
在kernel/fork.c:copy_process函數中,依次調用了下列函數:

p = dup_task_struct(current);複製了父進程的PID,GID,SID。
if (pid != &init_struct_pid) {
    retval = -ENOMEM;
    pid = alloc_pid(p->nsproxy->pid_ns);//分配新的PID,包括在每個名字空間中看到的不同PID。
    if (!pid)
        goto bad_fork_cleanup_io;
    if (clone_flags & CLONE_NEWPID) {
        retval = pid_ns_prepare_proc(p->nsproxy->pid_ns);
        if (retval < 0)
            goto bad_fork_free_pid;
    }
}
p->pid = pid_nr(pid);
p->tgid = p->pid;
if (clone_flags & CLONE_THREAD)
    p->tgid = current->tgid;
//自己成了新的進程組長,但是在真正獲取進程組ID時:task->group_leader->pids[PIDTYPE_PGID];
p->group_leader = p; 
INIT_LIST_HEAD(&p->thread_group);
if (likely(p->pid)) {
    list_add_tail(&p->sibling, &p->real_parent->children);
    tracehook_finish_clone(p, clone_flags, trace);
    if (thread_group_leader(p)) {
        if (clone_flags & CLONE_NEWPID)
            p->nsproxy->pid_ns->child_reaper = p;

            p->signal->leader_pid = pid;
            tty_kref_put(p->signal->tty);
            p->signal->tty =tty_kref_get(current->signal->tty);
            attach_pid(p, PIDTYPE_PGID, task_pgrp(current));

            **//加入會話PID的task[PIDTYPE_SID]鏈表。**
            attach_pid(p, PIDTYPE_SID, task_session(current));
            list_add_tail_rcu(&p->tasks, &init_task.tasks);
            __get_cpu_var(process_counts)++;
        }
        **//將自己加入struct pid的tasks[PIDTYPE_PID]鏈表。**
        attach_pid(p, PIDTYPE_PID, pid);
        nr_threads++;
    }

每個進程有一個stuct pid結構,在這個結構中,tasks[]數組中有三個鏈表,第一個是PIDTYPE_PID鏈表,這個鏈表中只有一個進程x,也就是說每個進程有唯一一個struct PID.
第二個是PIDTYPE_PGID,這個鏈表中可能多於一個進程,多個進程可能屬於同一個進程組,由此可見,組長進程退出後,這個struct結點不應該釋放,直到進程組中所有進程退出。第三個是PIDTYPE_SID,這個與PGID一樣,屬於同一個會話的進程都在這個鏈表中。有了這些知識,上面的問題都可以迎刃而解了。

分配struct pid

下面代碼在kernel/pid.c中,在分配struct pid結點時調用。

struct pid *alloc_pid(struct pid_namespace *ns)
{
    struct pid *pid;
    enum pid_type type;
    int i, nr;
    struct pid_namespace *tmp;
    struct upid *upid;
    //從slab中獲得struct pid結構
    pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
    if (!pid)
        goto out;
    tmp = ns;
    for (i = ns->level; i >= 0; i--) {
        nr = alloc_pidmap(tmp);//從pid名字空間的位圖中分配一個數字PID號。
        if (nr < 0)
            goto out_free;

        pid->numbers[i].nr = nr;
        pid->numbers[i].ns = tmp;
        tmp = tmp->parent;
    }
    get_pid_ns(ns);
    pid->level = ns->level;
    atomic_set(&pid->count, 1); //引用計數。計數爲0就釋放struct pid結構。
    for (type = 0; type < PIDTYPE_MAX; ++type)
        INIT_HLIST_HEAD(&pid->tasks[type]);

    spin_lock_irq(&pidmap_lock);
    for (i = ns->level; i >= 0; i--) {
        upid = &pid->numbers[i];
        hlist_add_head_rcu(&upid->pid_chain,
                &pid_hash[pid_hashfn(upid->nr, upid->ns)]);
    }
    spin_unlock_irq(&pidmap_lock);

out:
    return pid;

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

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

*
 * attach_pid() must be called with the tasklist_lock write-held.
 */
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]);
}

最初的struct pid是如何建立起來的?

一開始,內核用靜態變量初始化了一個struct pid結構給swap(0號進程)使用。還靜態初始化了一個pid名字空間。

這是在kernel/pid.ck上定義的。
struct pid_namespace init_pid_ns = {
    .kref = {
        .refcount       = ATOMIC_INIT(2),
    },
    .pidmap = {
        [ 0 ... PIDMAP_ENTRIES-1] = { ATOMIC_INIT(BITS_PER_PAGE), NULL }
    },
    .last_pid = 0,
    .level = 0,
    .child_reaper = &init_task, //每個pid名字空間有一個init進程,用來收養孤兒進程
};
//全局pid_hash中。鏈接的是struct upid結構,哈希值是由nr(pid)和ns(pid所在名字空間)算出來的.由pid和ns就能得到struct upid結構,再用container_of得到struct pid結構,再從struct pid結構中tasks[]獲取task_struct。
static struct hlist_head *pid_hash; 
static unsigned int pidhash_shift = 4;
struct pid init_struct_pid = INIT_STRUCT_PID;//一個全局的初始pid結構。也是系統中最初始的PID。其他PID都是由alloc_pid函數從slab中分配的。

下面代碼在include/linux/init_task.h中:
extern struct nsproxy init_nsproxy;//名字空間根結點,靜態定義
#define INIT_NSPROXY(nsproxy) {                     \
    .pid_ns     = **&init_pid_ns,**                 \
    .count      = ATOMIC_INIT(1),               \
    .uts_ns     = **&init_uts_ns,**                 \
    .mnt_ns     = NULL,                     \
    INIT_NET_NS(net_ns)                                             \
    INIT_IPC_NS(ipc_ns)                     \
}

#define INIT_STRUCT_PID {                       \
    .count      = ATOMIC_INIT(1),               \
    .tasks      = {                     \
    下邊三個字段就是初始PID的初始化。可以看到0號進程是自己的組長,會話組長。
        **{ .first = &init_task.pids[PIDTYPE_PID].node },       \
        { .first = &init_task.pids[PIDTYPE_PGID].node },    \
        { .first = &init_task.pids[PIDTYPE_SID].node },     \**
    },                              \
    .rcu        = RCU_HEAD_INIT,                \
    .level      = 0,                        \
    .numbers    = { {                       \
        .nr     = 0,                    \
        **.ns       = &init_pid_ns,**               \
        .pid_chain  = { .next = NULL, .pprev = NULL },  \
    }, }                                \
}

#define INIT_PID_LINK(type)                     \
{                               \
    .node = {                       \
        .next = NULL,                   \
        .pprev = &init_struct_pid.tasks[type].first,    \
    },                          \
    .pid = &init_struct_pid,                \
}

/*
 *  INIT_TASK is used to set up the first task table, touch at
 * your own risk!. Base=0, limit=0x1fffff (=2MB)
 */
#define INIT_TASK(tsk)  \初始化第一個進程
{                                   \
    .state      = 0,                        \
    .stack      = &init_thread_info,                \
    .usage      = ATOMIC_INIT(2),               \
    .flags      = PF_KTHREAD,                   \
    .lock_depth = -1,                       \
    .prio       = MAX_PRIO-20,                  \
    .static_prio    = MAX_PRIO-20,                  \
    .normal_prio    = MAX_PRIO-20,                  \
    .policy     = SCHED_NORMAL,                 \
    .cpus_allowed   = CPU_MASK_ALL,                 \
    .mm     = NULL,                     \
    .active_mm  = &init_mm,                 \
    .se     = {                     \
        .group_node     = LIST_HEAD_INIT(tsk.se.group_node),    \
    },                              \
    .rt     = {                     \
        .run_list   = LIST_HEAD_INIT(tsk.rt.run_list),  \
        .time_slice = HZ,                   \
        .nr_cpus_allowed = NR_CPUS,             \
    },                              \
    .tasks      = LIST_HEAD_INIT(tsk.tasks),            \
    .pushable_tasks = PLIST_NODE_INIT(tsk.pushable_tasks, MAX_PRIO), \
    .ptraced    = LIST_HEAD_INIT(tsk.ptraced),          \
    .ptrace_entry   = LIST_HEAD_INIT(tsk.ptrace_entry),     \
    .real_parent    = &tsk,                     \
    .parent     = &tsk,                     \
    .children   = LIST_HEAD_INIT(tsk.children),         \
    .sibling    = LIST_HEAD_INIT(tsk.sibling),          \
    **.group_leader = &tsk,**                       \
    .real_cred  = &init_cred,                   \
    .cred       = &init_cred,                   \
    .cred_guard_mutex =                     \
         __MUTEX_INITIALIZER(tsk.cred_guard_mutex),     \
    .comm       = "swapper",                    \
    .thread     = INIT_THREAD,                  \
    .fs     = &init_fs,                 \
    .files      = &init_files,                  \
    .signal     = &init_signals,                \
    .sighand    = &init_sighand,                \
    .nsproxy    = &init_nsproxy,                \
    .pending    = {                     \
        .list = LIST_HEAD_INIT(tsk.pending.list),       \
        .signal = {{0}}},                   \
    .blocked    = {{0}},                    \
    .alloc_lock = __SPIN_LOCK_UNLOCKED(tsk.alloc_lock),     \
    .journal_info   = NULL,                     \
    .cpu_timers = INIT_CPU_TIMERS(tsk.cpu_timers),      \
    .fs_excl    = ATOMIC_INIT(0),               \
    .pi_lock    = __SPIN_LOCK_UNLOCKED(tsk.pi_lock),        \
    .timer_slack_ns = 50000, /* 50 usec default slack */        \
    **將自己加入struct pid的tasks字段中。**
    .pids = {                           \
        [PIDTYPE_PID]  = INIT_PID_LINK(PIDTYPE_PID),        \
        [PIDTYPE_PGID] = INIT_PID_LINK(PIDTYPE_PGID),       \
        [PIDTYPE_SID]  = INIT_PID_LINK(PIDTYPE_SID),        \
    },                              \
    .dirties = INIT_PROP_LOCAL_SINGLE(dirties),         \
    INIT_IDS                            \
    INIT_PERF_EVENTS(tsk)                       \
    INIT_TRACE_IRQFLAGS                     \
    INIT_LOCKDEP                            \
    INIT_FTRACE_GRAPH                       \
    INIT_TRACE_RECURSION                        \
    INIT_TASK_RCU_PREEMPT(tsk)                  \
}

下面代碼在arch/x86/kernel/init_task.c中:
/*
 * Initial task structure.
 *
 * All other task structs will be allocated on slabs in fork.c
 */
struct task_struct init_task = INIT_TASK(init_task);
EXPORT_SYMBOL(init_task);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章