Linux ns 2. Pid Namespace 详解

1. pid

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

在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)。

这种集合关系见下图:
在这里插入图片描述

相关的数据结构如下:

item member
进程 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) task_struct->pids[PIDTYPE_SID]->pid //struct pid *

1.1 pid_link

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

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

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

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

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

1.1.1 process

在这里插入图片描述

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

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

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

1.1.2 thread group

在这里插入图片描述

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

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

dir descript
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);
        
    }

1.1.3 process group

在这里插入图片描述

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

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

dir descript
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)

1.1.4 session

在这里插入图片描述

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

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

dir descript
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);

1.2 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++;
	}

}

2. pid namespace

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

Linux内核支持的namespaces如下:

名称 宏定义 隔离内容
Cgroup CLONE_NEWCGROUP Cgroup root directory (since Linux 4.6)
IPC CLONE_NEWIPC System V IPC, POSIX message queues (since Linux 2.6.19)
Network CLONE_NEWNET Network devices, stacks, ports, etc. (since Linux 2.6.24)
Mount CLONE_NEWNS Mount points (since Linux 2.4.19)
PID CLONE_NEWPID Process IDs (since Linux 2.6.24)
User CLONE_NEWUSER User and group IDs (started in Linux 2.6.23 and completed in Linux 3.8)
UTS CLONE_NEWUTS Hostname and NIS domain name (since Linux 2.6.19)

Ps:其中,cgroup namespace在4.6的内核中才实现,并且和cgroup v2关系密切,现在普及程度还不高,比如docker现在就还没有用它,所以在namespace系列文章中暂时不会介绍cgroup namespace。

系统中的每个进程都有/proc/[pid]/ns/这样一个目录,里面包含了这个进程所属namespace的信息,里面每个文件的描述符都可以用来作为setns函数(后文会介绍)的参数。

查看当前bash进程所属的namespace信息:

myc@myc-virtual-machine:~/data/scara$ ll /proc/3366/ns/ 
总用量 0
dr-x--x--x 2 myc myc 0 12月  3 14:35 ./
dr-xr-xr-x 9 myc myc 0 11月 25 11:16 ../
lrwxrwxrwx 1 myc myc 0 12月  3 16:21 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 myc myc 0 12月  3 16:21 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 myc myc 0 12月  3 16:21 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 myc myc 0 12月  3 16:21 net -> net:[4026531957]
lrwxrwxrwx 1 myc myc 0 12月  3 16:21 pid -> pid:[4026531836]
lrwxrwxrwx 1 myc myc 0 12月  3 16:21 user -> user:[4026531837]
lrwxrwxrwx 1 myc myc 0 12月  3 16:21 uts -> uts:[4026531838]

和namespace相关的函数只有三个,如下所示:

一、clone: 创建一个新的进程并把他放到新的namespace中。

int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);

其中:flags用于指定一个或者多个上面的CLONE_NEW*宏定义(当然也可以包含跟namespace无关的flags,多个flags 用|进行分隔),这样就会创建一个或多个新的不同类型的namespace,并把新创建的子进程加入新创建的这些namespace中。

二、setns: 将当前进程加入到已有的namespace中。

int setns(int fd, int nstype);

其中:

  • fd:指向/proc/[pid]/ns/目录里相应namespace对应的文件,表示要加入哪个namespace
  • nstype:指定namespace的类型(上面的任意一个CLONE_NEW*),具体分为两种情况:1. 如果当前进程不能根据fd得到它的类型,如fd由其他进程创建,并通过UNIX domain socket传给当前进程,那么就需要通过nstype来指定fd指向的namespace的类型。2. 如果进程能根据fd得到namespace类型,比如这个fd是由当前进程打开的,那么nstype设置为0即可。

三、unshare: 使当前进程退出指定类型的namespace,并加入到新创建的namespace(相当于创建并加入新的namespace)。

int unshare(int flags);

其中:flags用于指定一个或者多个上面的CLONE_NEW*宏定义(当然也可以包含跟namespace无关的flags,多个flags 用|进行分隔),这样就会创建一个或多个新的不同类型的namespace,并把新创建的子进程加入新创建的这些namespace中。
clone和unshare的区别

clone和unshare的功能都是创建并加入新的namespace, 他们的区别是:

  • unshare是使当前进程加入新的namespace。
  • clone是创建一个新的子进程,然后让子进程加入新的namespace,而当前进程保持不变。

注意事项:

当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。

2.1 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;
	}

    ...
}

从上述的分析中可以看到,pid_namespace的核心思想就是struct pid中的number[]数组,每个名空间拥有自己的pid number:
在这里插入图片描述

2.2 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;
}

2.3 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;
}

2.4 相关函数

  • 获取当前进程的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)

猜你喜欢

转载自blog.csdn.net/pwl999/article/details/109784301
今日推荐