文章目录
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_struct
和struct 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(¤t->signal->live);
atomic_inc(¤t->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)