泰晓科技 -- 聚焦 Linux - 追本溯源,见微知著!
网站地址:https://tinylab.org

儿童Linux系统,可打字编程学数理化
请稍侯

Pid Namespace 详解

Peng Weilin 创作于 2021/04/14

By pwl999 of TinyLab.org Feb 02, 2021

pid 解析

在 Linux 下获取 pid,会发现有好几种类似的 id(pid、tgid、pgid、sid),这几种的区别在哪里呢?各自的使用场景和用法是什么呢?下面我们就来一一解析。

image

在 kernel 中,进程 process 是最小的调度单位(对应数据结构 task_struct)。Linux 下没有单独线程的概念,只有轻量级进程的概念,如果一个进程它和其他进程共享进程空间 mm 和文件句柄 fd 等一些资源,那它就是轻量级进程相当于线程 thread。多个轻量级进程组成了一个线程组(thread group),线程组中第一个创建的轻量级进程称之为 group_leader,其中的每一个轻量级进程(thread)拥有自己独立的 pid,所有的轻量级进程共享同一个线程组 tgid(即 group_leader 的 pid)。

除了上述 pid 和 tgid 两种,还有更大的进程集合。在系统启动以后,用户使用系统首先需要使用创建会话 session,我们一般看到的 tty(比如键盘、屏幕的 tty1~tty7,或者网络连接的虚拟 tty 即 pty),一个 tty 对应一个 session。在当前 tty 中创建的所有进程都共享一个 sid(即 leader 的 pid)。

在一个 session 中,还存在着前后进程组的概念,这是大型机时为了多个用户使用同一个 tty 来引申出的概念,在一个 session 下可以创建多个 job,每个 job 称为一个进程组(process group),只能有一个前台进程组可以有多个后台进程组。进程组的所有进程都共享一个 pgid(即 leader 的pid)。

相关的数据结构如下:

itemmember
进程 process (pid)task_struct->pid //pid_t
 task_struct->pids[PIDTYPE_PID]->pid //struct pid *
线程组 thread group (tgid)task_struct->tgid //pid_t
 task_struct->group_leader //struct task_struct *
 task_struct->signal->leader_pid //struct pid *
进程组 process group (pgid)task_struct->pids[PIDTYPE_PGID]->pid //struct pid *
会话组 session (sid) 

细心的同学已经发现,保存 pid 有两种结构:一个是保存 pid number 的 pid_t;另外是一个结构体 struct pid,它有一个数组 ->numbers[] 可以保存多个 pid number,还有一个进程链表数组 tasks[]

设计成两个数据结构的原因如下:

  • 因为多个 namespace 的存在,需要在每个名空间都一个 pid number,所以我们需要一个 pid number 数组。而 task_struct->pidtask_struct->tgid 仅仅保存了第 0 层名空间的 pid number。
  • 需要建立起 task_struct 和各种 pid 之间的双向查询关系,所以需要一些链表结构。

另外,为了简化这些复杂的关系,把 struct pid 结构从 task_struct 中抽离出来,而 task_struct 中使用的是 struct pid_link 类型的成员。

下面具体分析每个场景下,pid 的链接关系。

process

image

每一个进程 process(包括轻量级进程/thread)创建时,都会创建一个对应的 struct pid 数据结构,struct pid 创建了一个 pid number 数组,在每一层名空间中都分配了一个 pid number。

process 的 struct task_structstruct pid 数据结构之间的双向查询关系:

directiondescription
task_struct → pid通过 task->pids[PIDTYPE_PID].pid 指针指向 struct pid
pid → task_struct通过 pid->tasks[PIDTYPE_PID] 链表找到 task_struct,理论上该链表只有一个成员

thread group

image

我们看到线程组其实是一个异类,就它没有使用 pid_link 而是用了一堆的私有结构。为什么会形成这种局面?估计是历史原因造成的。

普通轻量级进程(线程)和线程组leader线程之间的双向查询关系:

directiondescription
thread → group leader普通线程 task->group_leader 存放线程组 leader 线程的 task_struct 结构
 普通线程 task->signal->leader_pid 存放线程组 leader 线程的 struct pid
group leader → thread线程组 leader 线程的 task->thread_group 链表,链接了本线程组所有线程的 task_struct

遍历线程组的实例代码:

while (t) {
    t = next_thread(t);
}

process group

image

我们来看看进程组的关系图,进程组也是使用 pid_link 来进行链接的。每个进程的进程组 pgid 指向同一个 leader,需要注意反向由进程组 leader 查询进程时的只能查询到线程组 leader,因为只把线程组 leader 链接到一起,而线程组下的普通线程由线程组 leader 自己来组织。

线程组 leader 和进程组 leader 之间的双向查询关系:

directiondescription
thread group leader → process group leader线程组learder的task->pids[PIDTYPE_PGID].pid指针指向进程组leader的struct pid
process group leader → thread group leader进程组leader的pid->tasks[PIDTYPE_PGID]链表链接了所有线程组learder的task_struct结构

遍历进程组的实例代码:

struct pid *pgrp;
struct task_struct *p = NULL;

do_each_pid_task(pgrp, PIDTYPE_PGID, p) {
    ...
} while_each_pid_task(pgrp, PIDTYPE_PGID, p);


#define do_each_pid_task(pid, type, task)                \
do {                                \
    if ((pid) != NULL)                    \
        hlist_for_each_entry_rcu((task),        \
            &(pid)->tasks[type], pids[type].node) {

        /*
         * Both old and new leaders may be attached to
         * the same pid in the middle of de_thread().
         */
#define while_each_pid_task(pid, type, task)                \
            if (type == PIDTYPE_PID)        \
                break;                \
        }                        \
} while (0)

session

image

我们来看看会话的关系图,会话也是使用 pid_link 来进行链接的。每个进程的会话 sid 指向同一个 leader,需要注意反向由会话 leader 查询进程时的只能查询到线程组 leader,因为只把线程组 leader 链接到一起,而线程组下的普通线程由线程组 leader 自己来组织。

线程组 leader 和会话 leader 之间的双向查询关系:

directiondescription
thread group leader → process group leader线程组learder的task->pids[PIDTYPE_SID].pid指针指向会话leader的struct pid
process group leader → thread group leader会话leader的pid->tasks[PIDTYPE_SID]链表链接了所有线程组learder的task_struct结构

遍历会话的实例代码:

struct pid *session
struct task_struct *p;

do_each_pid_task(session, PIDTYPE_SID, p) {
    ...
} while_each_pid_task(session, PIDTYPE_SID, p);

pid 的初始化

在进程的创建的时候,有对各种 pid 的初始化:

_do_fork() → copy_process():

static __latent_entropy struct task_struct *copy_process(
    unsigned long clone_flags,
    unsigned long stack_start,
    unsigned long stack_size,
    int __user *child_tidptr,
    struct pid *pid,
    int trace,
    unsigned long tls,
    int node)
{

    /* (1) 分配一个新的task_struct结构,并且拷贝父进程task中所有内容
            所以接下来如果没有给新task_struct中的成员赋新值,那么它的值就是和父进程一样的
     */
    p = dup_task_struct(current, node);

    /* (2) 拷贝名空间 */
    retval = copy_namespaces(clone_flags, p);

    /* (3) 在名空间中分配一个`struct pid *`结构 */
    if (pid != &init_struct_pid) {
        pid = alloc_pid(p->nsproxy->pid_ns_for_children);
        if (IS_ERR(pid)) {
            retval = PTR_ERR(pid);
            goto bad_fork_cleanup_thread;
        }
    }

    /* (4.1) 取出`struct pid`中最顶层名空间的pid number,赋值给task_struct->pid
            task_struct->pid相当于是一个快捷方式,取最顶层的pid number不用每次去查询struct pid->numbers[].nr数组
     */
    p->pid = pid_nr(pid);
    /* (4.2) 线程组tgid的赋值,创建线程时:
            group_leader等于父进程的group_leader
            tgid等于父进程的tgid
     */
    if (clone_flags & CLONE_THREAD) {
        p->exit_signal = -1;
        p->group_leader = current->group_leader;
        p->tgid = current->tgid;
    } else {
    /* (4.3) 线程组tgid的赋值,创建进程(group_leader)时:
            group_leader等于本进程的group_leader
            tgid等于本进程的tgid
     */
        if (clone_flags & CLONE_PARENT)
            p->exit_signal = current->group_leader->exit_signal;
        else
            p->exit_signal = (clone_flags & CSIGNAL);
        p->group_leader = p;
        p->tgid = p->pid;
    }

    if (likely(p->pid)) {
        ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);

        /* (5.1) 赋值新结构task_struct->pids[PIDTYPE_PID].pid,将新分配的`struct pid`结构赋值给它 */
        init_task_pid(p, PIDTYPE_PID, pid);
        /* (5.2) 如果本进程是线程组的group leader进行pgid和sid的新赋值和链接操作
                如果本进程是线程组中一个普通线程,它的pgid和sid从group leader复制继承,并且不会加入到pgid和sid的链表当中
         */
        if (thread_group_leader(p)) {
            /* (5.2.1) 赋值新结构task_struct->pids[PIDTYPE_PGID].pid,
                        将当前进程的所在线程组的group_leader的pgid赋值给他:task->group_leader->pids[PIDTYPE_PGID].pid
             */
            init_task_pid(p, PIDTYPE_PGID, task_pgrp(current));
            /* (5.2.2) 赋值新结构task_struct->pids[PIDTYPE_SID].pid,
                        将当前进程的所在线程组的group_leader的sid赋值给他:task->group_leader->pids[PIDTYPE_SID].pid
             */
            init_task_pid(p, PIDTYPE_SID, task_session(current));

            if (is_child_reaper(pid)) {
                ns_of_pid(pid)->child_reaper = p;
                p->signal->flags |= SIGNAL_UNKILLABLE;
            }

            /* (5.2.3) 给线程组group_leader的leader_pid赋值`struct pid`结构
                        p->signal保存的是这个线程组公共的信号
                        p->pending保存的是每个进程私有的信号
             */
            p->signal->leader_pid = pid;
            p->signal->tty = tty_kref_get(current->signal->tty);
            /*
             * Inherit has_child_subreaper flag under the same
             * tasklist_lock with adding child to the process tree
             * for propagate_has_child_subreaper optimization.
             */
            p->signal->has_child_subreaper = p->real_parent->signal->has_child_subreaper ||
                             p->real_parent->signal->is_child_subreaper;
            list_add_tail(&p->sibling, &p->real_parent->children);
            list_add_tail_rcu(&p->tasks, &init_task.tasks);
            /* (5.2.4) 当前是线程组的group_leader
                        将新进程的task_struct结构加入到进程所在pgid的pid->tasks[PIDTYPE_PGID]链表当中
             */
            attach_pid(p, PIDTYPE_PGID);
            /* (5.2.5) 当前是线程组的group_leader
                        将新进程的task_struct结构加入到进程所在pgid的pid->tasks[PIDTYPE_SID]链表当中
             */
            attach_pid(p, PIDTYPE_SID);
            __this_cpu_inc(process_counts);
        } else {
            current->signal->nr_threads++;
            atomic_inc(&current->signal->live);
            atomic_inc(&current->signal->sigcnt);
            /* (5.3.1) 将线程加入group_leader的thread_group链表 */
            list_add_tail_rcu(&p->thread_group,
                      &p->group_leader->thread_group);
            list_add_tail_rcu(&p->thread_node,
                      &p->signal->thread_head);
        }
        /* (5.4) 将新进程的task_struct结构加入到pid->tasks[PIDTYPE_PID]链表当中
        */
        attach_pid(p, PIDTYPE_PID);
        nr_threads++;
    }

}

pid namespace

Namespace 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程,对其他 namespace 中的进程没有影响。Linux 内核支持的 namespaces 如下:

名称宏定义隔离内容
CgroupCLONE_NEWCGROUPCgroup root directory (since Linux 4.6)
IPCCLONE_NEWIPCSystem V IPC, POSIX message queues (since Linux 2.6.19)
NetworkCLONE_NEWNETNetwork devices, stacks, ports, etc. (since Linux 2.6.24)
MountCLONE_NEWNSMount points (since Linux 2.4.19)
PIDCLONE_NEWPIDProcess IDs (since Linux 2.6.24)
UserCLONE_NEWUSERUser and group IDs (started in Linux 2.6.23 and completed in Linux 3.8)
UTSCLONE_NEWUTSHostname and NIS domain name (since Linux 2.6.19)

image

上图可以看到 pid namespace 的组织结构,pid namespace 使用父子关系组成了树形。在分配新 pid 结构,会从当前的 pid ns(task->nsproxy->pid_ns_for_children->pid_cachep) 中分配一个 struct pid 结构,struct pid 是一个数组包含了向上的所有 pid ns,在每个 pid ns 中都分配了一个 pid number。

注意事项:

当 unshare PID namespace 时,调用进程会为它的子进程分配一个新的 PID Namespace,但是调用进程本身不会被移到新的 Namespace 中。而且调用进程第一个创建的子进程在新 Namespace 中的 PID 为 1,并成为新 Namespace 中的 init 进程。setns() 系统调用也是类似的,调用者进程并不会进入新的 PID Namespace,而是随后创建的子进程会进入。

为什么创建其他的 Namespace 时 unshare() 和 setns() 会直接进入新的 Namespace,而唯独 PID Namespace 不是如此呢?

因为调用 getpid() 函数得到的 PID 是根据调用者所在的 PID Namespace 而决定返回哪个 PID,进入新的 PID namespace 会导致 PID 产生变化。而对用户态的程序和库函数来说,他们都认为进程的 PID 是一个常量,PID 的变化会引起这些进程奔溃。换句话说,一旦程序进程创建以后,那么它的 PID namespace 的关系就确定下来了,进程不会变更他们对应的 PID namespace。

下面从三个 API(clone/setns/unshare)切入来解读。

clone(CLONE_NEWPID)

我们来分析系统调用 clone()CLONE_NEWPID 的处理:

__do_fork() → copy_process() → copy_namespaces() → create_new_namespaces() → copy_pid_ns():

static struct nsproxy *create_new_namespaces(unsigned long flags,
    struct task_struct *tsk, struct user_namespace *user_ns,
    struct fs_struct *new_fs)
{
    struct nsproxy *new_nsp;
    int err;

    /* (1) 分配一个进程的名空间代理 */
    new_nsp = create_nsproxy();
    if (!new_nsp)
        return ERR_PTR(-ENOMEM);

    new_nsp->mnt_ns = copy_mnt_ns(flags, tsk->nsproxy->mnt_ns, user_ns, new_fs);
    if (IS_ERR(new_nsp->mnt_ns)) {
        err = PTR_ERR(new_nsp->mnt_ns);
        goto out_ns;
    }

    new_nsp->uts_ns = copy_utsname(flags, user_ns, tsk->nsproxy->uts_ns);
    if (IS_ERR(new_nsp->uts_ns)) {
        err = PTR_ERR(new_nsp->uts_ns);
        goto out_uts;
    }

    new_nsp->ipc_ns = copy_ipcs(flags, user_ns, tsk->nsproxy->ipc_ns);
    if (IS_ERR(new_nsp->ipc_ns)) {
        err = PTR_ERR(new_nsp->ipc_ns);
        goto out_ipc;
    }

    /* (2) 根据flags,判断当前pid名空间是引用旧的,还是创建一个新的
     */
    new_nsp->pid_ns_for_children =
        copy_pid_ns(flags, user_ns, tsk->nsproxy->pid_ns_for_children);
    if (IS_ERR(new_nsp->pid_ns_for_children)) {
        err = PTR_ERR(new_nsp->pid_ns_for_children);
        goto out_pid;
    }

    new_nsp->cgroup_ns = copy_cgroup_ns(flags, user_ns,
                        tsk->nsproxy->cgroup_ns);
    if (IS_ERR(new_nsp->cgroup_ns)) {
        err = PTR_ERR(new_nsp->cgroup_ns);
        goto out_cgroup;
    }

    new_nsp->net_ns = copy_net_ns(flags, user_ns, tsk->nsproxy->net_ns);
    if (IS_ERR(new_nsp->net_ns)) {
        err = PTR_ERR(new_nsp->net_ns);
        goto out_net;
    }

    return new_nsp;
}

↓

struct pid_namespace *copy_pid_ns(unsigned long flags,
    struct user_namespace *user_ns, struct pid_namespace *old_ns)
{
    /* (2.1) 如果没有设置CLONE_NEWPID标志,引用旧的pid_namespace */
    if (!(flags & CLONE_NEWPID))
        return get_pid_ns(old_ns);
    if (task_active_pid_ns(current) != old_ns)
        return ERR_PTR(-EINVAL);
    /* (2.2) 如果设置了CLONE_NEWPID标志,创建一个新的pid_namespace并引用 */
    return create_pid_namespace(user_ns, old_ns);
}

↓

static struct pid_namespace *create_pid_namespace(struct user_namespace *user_ns,
    struct pid_namespace *parent_pid_ns)
{
    struct pid_namespace *ns;
    /* (2.2.1) 在父名空间的基础上,将level加1 */
    unsigned int level = parent_pid_ns->level + 1;
    struct ucounts *ucounts;
    int err;

    err = -EINVAL;
    if (!in_userns(parent_pid_ns->user_ns, user_ns))
        goto out;

    err = -ENOSPC;
    if (level > MAX_PID_NS_LEVEL)
        goto out;
    ucounts = inc_pid_namespaces(user_ns);
    if (!ucounts)
        goto out;

    err = -ENOMEM;
    ns = kmem_cache_zalloc(pid_ns_cachep, GFP_KERNEL);
    if (ns == NULL)
        goto out_dec;

    idr_init(&ns->idr);

    /* (2.2.2) 根据名空间level层级,创建对应的struct pid的slub内存池
                因为level不一样,struct pid中包含的pid number数组大小也不一样,所以实际struct pid大小是根据level动态变化的
     */
    ns->pid_cachep = create_pid_cachep(level + 1);
    if (ns->pid_cachep == NULL)
        goto out_free_idr;

    err = ns_alloc_inum(&ns->ns);
    if (err)
        goto out_free_idr;
    ns->ns.ops = &pidns_operations;

    kref_init(&ns->kref);
    ns->level = level;
    ns->parent = get_pid_ns(parent_pid_ns);
    ns->user_ns = get_user_ns(user_ns);
    ns->ucounts = ucounts;
    ns->pid_allocated = PIDNS_ADDING;
    INIT_WORK(&ns->proc_work, proc_cleanup_work);

    return ns;

out_free_idr:
    idr_destroy(&ns->idr);
    kmem_cache_free(pid_ns_cachep, ns);
out_dec:
    dec_pid_namespaces(ucounts);
out:
    return ERR_PTR(err);
}

进程的 pid_namespace 存储在 task->nsproxy->pid_ns_for_children 中,在进程创建时就会从对应的名空间中分配 struct pid 结构。因为 struct pid 的 number 数组大小和名空间 level 是一致的,所以在每个 level 层级名空间,都会分配一个 pid number。

copy_process()
{

    if (pid != &init_struct_pid) {
        pid = alloc_pid(p->nsproxy->pid_ns_for_children);
        if (IS_ERR(pid)) {
            retval = PTR_ERR(pid);
            goto bad_fork_cleanup_thread;
        }
    }

}

↓

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;
    int retval = -ENOMEM;

    /* (1) 从名空间中分配一个对应的struct pid数据 */
    pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
    if (!pid)
        return ERR_PTR(retval);

    tmp = ns;
    pid->level = ns->level;

    /* (2) 在每个层级名空间,分配一个对应的pid number,存储到struct pid中的number数组 */
    for (i = ns->level; i >= 0; i--) {
        int pid_min = 1;

        idr_preload(GFP_KERNEL);
        spin_lock_irq(&pidmap_lock);

        /*
         * init really needs pid 1, but after reaching the maximum
         * wrap back to RESERVED_PIDS
         */
        if (idr_get_cursor(&tmp->idr) > RESERVED_PIDS)
            pid_min = RESERVED_PIDS;

        /*
         * Store a null pointer so find_pid_ns does not find
         * a partially initialized PID (see below).
         */
        /* (2.1) 分配pid number */
        nr = idr_alloc_cyclic(&tmp->idr, NULL, pid_min,
                      pid_max, GFP_ATOMIC);
        spin_unlock_irq(&pidmap_lock);
        idr_preload_end();

        if (nr < 0) {
            retval = (nr == -ENOSPC) ? -EAGAIN : nr;
            goto out_free;
        }

        pid->numbers[i].nr = nr;
        pid->numbers[i].ns = tmp;
        tmp = tmp->parent;
    }

    if (unlikely(is_child_reaper(pid))) {
        if (pid_ns_prepare_proc(ns))
            goto out_free;
    }

    ...
}

setns(CLONE_NEWPID)

SYSCALL_DEFINE2(setns, int, fd, int, nstype)
{
    struct task_struct *tsk = current;
    struct nsproxy *new_nsproxy;
    struct file *file;
    struct ns_common *ns;
    int err;

    /* (1) 根据fd找到对应的名空间 */
    file = proc_ns_fget(fd);
    if (IS_ERR(file))
        return PTR_ERR(file);

    err = -EINVAL;
    ns = get_proc_ns(file_inode(file));
    if (nstype && (ns->ops->type != nstype))
        goto out;

    /* (1) 创建新的进程名空间代理结构 */
    new_nsproxy = create_new_namespaces(0, tsk, current_user_ns(), tsk->fs);
    if (IS_ERR(new_nsproxy)) {
        err = PTR_ERR(new_nsproxy);
        goto out;
    }

    /* (2) 安装新的进程名空间代理 */
    err = ns->ops->install(new_nsproxy, ns);
    if (err) {
        free_nsproxy(new_nsproxy);
        goto out;
    }
    /* (3) 把进程名空间代理切换成新的
            需要注意的是pid_namespace的特殊性,只有在子进程创建时才会生效
     */
    switch_task_namespaces(tsk, new_nsproxy);

    perf_event_namespaces(tsk);
out:
    fput(file);
    return err;
}

unshare(CLONE_NEWPID)

SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags)
{

    err = unshare_nsproxy_namespaces(unshare_flags, &new_nsproxy,
                     new_cred, new_fs);

}

↓

int unshare_nsproxy_namespaces(unsigned long unshare_flags,
    struct nsproxy **new_nsp, struct cred *new_cred, struct fs_struct *new_fs)
{
    struct user_namespace *user_ns;
    int err = 0;

    if (!(unshare_flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
                   CLONE_NEWNET | CLONE_NEWPID | CLONE_NEWCGROUP)))
        return 0;

    user_ns = new_cred ? new_cred->user_ns : current_user_ns();
    if (!ns_capable(user_ns, CAP_SYS_ADMIN))
        return -EPERM;

    *new_nsp = create_new_namespaces(unshare_flags, current, user_ns,
                     new_fs ? new_fs : current->fs);
    if (IS_ERR(*new_nsp)) {
        err = PTR_ERR(*new_nsp);
        goto out;
    }

out:
    return err;
}

相关函数

  • 获取当前进程的 pid
SYSCALL_DEFINE0(getpid)
{
    return task_tgid_vnr(current);
}

↓

static inline pid_t task_tgid_vnr(struct task_struct *tsk)
{
    return __task_pid_nr_ns(tsk, __PIDTYPE_TGID, NULL);
}

↓

pid_t __task_pid_nr_ns(struct task_struct *task, enum pid_type type,
            struct pid_namespace *ns)
{
    pid_t nr = 0;

    rcu_read_lock();
    /* (1) 如果ns为NULL,则ns为当前名空间 */
    if (!ns)
        ns = task_active_pid_ns(current);
    if (likely(pid_alive(task))) {
        if (type != PIDTYPE_PID) {
            if (type == __PIDTYPE_TGID)
                type = PIDTYPE_PID;

            task = task->group_leader;
        }
        /* (1) 根据type找到对应的struct pid结构:task->pids[type].pid
                根据名空间的level,找到struct pid中对应名空间的pid number
         */
        nr = pid_nr_ns(rcu_dereference(task->pids[type].pid), ns);
    }
    rcu_read_unlock();

    return nr;
}

↓

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) {
        upid = &pid->numbers[ns->level];
        if (upid->ns == ns)
            nr = upid->nr;
    }
    return nr;
}

参考文档

  1. Linux 系统如何标识进程?
  2. 进程管理和终端驱动:基本概念
  3. Linux kernel Namespace 源码分析
  4. user_namespace 分析(1)
  5. user_namespace 分析(2)
  6. Linux Namespace : 简介


Read Album:

Read Related:

Read Latest: