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);
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值