隔离文件系统挂载点对隔离文件系统提供支持,/proc/{pid}/mounts,/proc/{pid}/mountstats查看文件设备统计信息,包括挂载文件的名字,文件系统类型,挂载位置等。进程在创建mount namespace时,会把当前的文件结构复制给新的namespace,新namespace中的所有mount操作都只影响自身的文件系统
网络资源的隔离,包括网络设备,协议栈,IP路由表,防火墙 /proc/net /sys/class/net socket,一个物理网络设备最多存在于一个network namespace中
docker使用veth pair,一端绑定docker0,另一端绑定network namespace进程中,
Linux Namespaces机制提供一种资源隔离方案。PID,IPC,Network等系统资源不再是全局性的,而是属于某个特定的Namespace。每个namespace下的资源对于其他namespace下的资源都是透明,不可见的。系统中可以同时存在两个进程号为0,1,2的进程,由于属于不同的namespace,所以它们之间并不冲突。而在用户层面上只能看到属于用户自己namespace下的资源,例如使用ps命令只能列出自己namespace下的进程。
在Linux内核中,一个进程可以属于多个namesapce,既然namespace和进程相关,那么在task_struct结构体中就会包含和namespace相关联的变量
在task_struct 结构中有一个指向namespace结构体的指针nsproxy。
struct task_struct {
……..
/* namespaces */
struct nsproxy *nsproxy;
…….
}
定义了5个命名空间结构体,多个进程可以使用同一个namespace,所以nsproxy可以共享使用,count字段是该结构的引用计数
struct nsproxy {
atomic_t count;
struct uts_namespace *uts_ns;
struct ipc_namespace *ipc_ns;
struct mnt_namespace *mnt_ns;
struct pid_namespace *pid_ns_for_children;
struct net *net_ns;
};
系统中有一个默认的nsproxy,init_nsproxy,该结构在task初始化是也会被初始化。
#define INIT_TASK(tsk)
{
.nsproxy = &init_nsproxy,
}
static struct kmem_cache *nsproxy_cachep;
struct nsproxy init_nsproxy = {
.count = ATOMIC_INIT(1),
.uts_ns = &init_uts_ns,
#if defined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)
.ipc_ns = &init_ipc_ns,
#endif
.mnt_ns = NULL,
.pid_ns_for_children = &init_pid_ns,
#ifdef CONFIG_NET
.net_ns = &init_net,
#endif
};
可以使用系统调用clone()创建命名空间
int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...
/* pid_t *ptid, void *newtls, pid_t *ctid */ );
flags namespace相关的参数如下
- CLONE_FS:子进程与父进程共享相同的文件系统,包括root、当前目录、umask
- CLONE_NEWNS:当clone需要自己的命名空间时设置这个标志
在Linux内核中看到的实现函数,是经过libc库进行封装过的,在Linux内核中的fork.c文件中,有下面的定义,最终调用的都是do_fork()函数
#ifdef __ARCH_WANT_SYS_CLONE
#ifdef CONFIG_CLONE_BACKWARDS
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int, tls_val,
int __user *, child_tidptr)
#elif defined(CONFIG_CLONE_BACKWARDS2)
SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
int __user *, parent_tidptr,
int __user *, child_tidptr,
int, tls_val)
#elif defined(CONFIG_CLONE_BACKWARDS3)
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
int, stack_size,
int __user *, parent_tidptr,
int __user *, child_tidptr,
int, tls_val)
#else
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int __user *, child_tidptr,
int, tls_val)
#endif
{
return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
}
#endif
在clone()函数中调用do_fork,do_fork函数中调用copy_process
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
struct task_struct *p;
int trace = 0;
long nr;
/*
* Determine whether and which event to report to ptracer. When
* called from kernel_thread or CLONE_UNTRACED is explicitly
* requested, no event is reported; otherwise, report if the event
* for the type of forking is enabled.
*/
if (!(clone_flags & CLONE_UNTRACED)) {
if (clone_flags & CLONE_VFORK)
trace = PTRACE_EVENT_VFORK;
else if ((clone_flags & CSIGNAL) != SIGCHLD)
trace = PTRACE_EVENT_CLONE;
else
trace = PTRACE_EVENT_FORK;
if (likely(!ptrace_event_enabled(current, trace)))
trace = 0;
}
p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace);
/*
* Do this prior waking up the new thread - the thread pointer
* might get invalid after that point, if the thread exits quickly.
*/
if (!IS_ERR(p)) {
struct completion vfork;
struct pid *pid;
trace_sched_process_fork(current, p);
pid = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid);
if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, parent_tidptr);
if (clone_flags & CLONE_VFORK) {
p->vfork_dOne= &vfork;
init_completion(&vfork);
get_task_struct(p);
}
wake_up_new_task(p);
/* forking complete and child started to run, tell ptracer */
if (unlikely(trace))
ptrace_event_pid(trace, pid);
if (clone_flags & CLONE_VFORK) {
if (!wait_for_vfork_done(p, &vfork))
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
}
put_pid(pid);
} else {
nr = PTR_ERR(p);
}
return nr;
}
在copy_process函数中调用copy_namespaces函数。
static 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)
{
int retval;
struct task_struct *p;
对clone_flag标志进行检查,有部分表示是互斥的,例如CLONE_NEWNS和CLONENEW_FS
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL);
if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
return ERR_PTR(-EINVAL);
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL);
if ((clone_flags & CLONE_PARENT) &&
current->signal->flags & SIGNAL_UNKILLABLE)
return ERR_PTR(-EINVAL);
retval = copy_namespaces(clone_flags, p);
if (retval)
goto bad_fork_cleanup_mm;
retval = copy_io(clone_flags, p);
if (retval)
goto bad_fork_cleanup_namespaces;
retval = copy_thread(clone_flags, stack_start, stack_size, p);
if (retval)
goto bad_fork_cleanup_io;
do_fork中调用copy_process函数,该函数中pid参数为NULL,所以这里的if判断是成立的。为进程所在的namespace分配pid,在3.0的内核之前还有一个关键函数,就是namespace创建后和cgroup的关系,
if (current->nsproxy != p->nsproxy) {
retval = ns_cgroup_clone(p, pid);
if (retval)
goto bad_fork_free_pid;
但在3.0内核以后给删掉了,具体请参考remove the ns_cgroup*/
if (pid != &init_struct_pid) {
retval = -ENOMEM;
pid = alloc_pid(p->nsproxy->pid_ns_for_children);
if (!pid)
goto bad_fork_cleanup_io;
}…..
}
在kernel/nsproxy.c文件中定义了copy_namespaces函数。
int copy_namespaces(unsigned long flags, struct task_struct *tsk)
{
struct nsproxy *old_ns = tsk->nsproxy;
struct user_namespace *user_ns = task_cred_xxx(tsk, user_ns);
struct nsproxy *new_ns;
/*首先检查flag,如果flag标志不是下面的五种之一,就会调用get_nsproxy对old_ns递减引用计数,然后直接返回0*/
if (likely(!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
CLONE_NEWPID | CLONE_NEWNET)))) {
get_nsproxy(old_ns);
return 0;
}
/*当前进程是否有超级用户的权限*/
if (!ns_capable(user_ns, CAP_SYS_ADMIN))
return -EPERM;
/*
* CLONE_NEWIPC must detach from the undolist: after switching
* to a new ipc namespace, the semaphore arrays from the old
* namespace are unreachable. In clone parlance, CLONE_SYSVSEM
* means share undolist with parent, so we must forbid using
* it along with CLONE_NEWIPC.
对CLONE_NEWIPC进行特殊的判断,*/
if ((flags & (CLONE_NEWIPC | CLONE_SYSVSEM)) ==
(CLONE_NEWIPC | CLONE_SYSVSEM))
return -EINVAL;
/*为进程创建新的namespace*/
new_ns =create_new_namespaces(flags, tsk, user_ns, tsk->fs);
if (IS_ERR(new_ns))
return PTR_ERR(new_ns);
tsk->nsproxy = new_ns;
return 0;
}
create_nsproxy函数为新的nsproxy分配内存空间,并对其引用计数设置为初始1
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;
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;
}
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;
}
static inline struct nsproxy *create_nsproxy(void)
{
struct nsproxy *nsproxy;
nsproxy = kmem_cache_alloc(nsproxy_cachep, GFP_KERNEL);
if (nsproxy)
atomic_set(&nsproxy->count, 1);
return nsproxy;
}
创建容器(进程)主要用到三个系统调用:
clone()
– 实现线程的系统调用,用来创建一个新的进程,并可以通过上述参数达到隔离unshare()
– 使某进程脱离某个namespacesetns()
– 把某进程加入到某个namespace
功能是创建一个新的进程,创建一个新的namespace的进程(Docker使用namespace的方法)
NAME
clone, __clone2 - create a child process
SYNOPSIS
/* Prototype for the glibc wrapper function */
#define _GNU_SOURCE
#include
int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...
/* pid_t *ptid, void *newtls, pid_t *ctid */ );
/* For the prototype of the raw system call, see NOTES */
将进程加入到一个已经存在的namespace中,在一个Docker容器中用exec运行一个新命令,就是将该命令在该容器的namespace中运行
NAME
setns - reassociate thread with a namespace
SYNOPSIS
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include
int setns(int fd, int nstype);
unshare()是在原进程上作隔离,参数flags是标志位,选择需要隔离的资源。与clone()的falgs参数基本相同
NAME
unshare - run program with some namespaces unshared from parent
SYNOPSIS
unshare [options] program [arguments]
pid namespace的flag为CLONE_NEWPID
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#define STACK_SIZE (1024*1024)
static char child_stack[STACK_SIZE];
char * const child_args[] = {
"/bin/bash",
NULL
};
int child_main(void *args) {
printf("子进程begin...n");
execv(child_args[0],child_args);
return 1;
}
int main() {
printf("主程序begin...:n");
int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWPID|SIGCHLD, NULL);
waitpid(child_pid, NULL, 0);
printf("主程序退出...n");
return 0;
}
编译并运行如下,可以看到使用clone()创建了一个进程并进行隔离,当前进程的pid为1,但是ps可以看到一大堆进程,理论上是不应该看到的,因为ps命令或者top命令都是从Linux系统中的/proc目录下取值的
pid namespace的flag为CLONE_NEWIPC
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#define STACK_SIZE (1024*1024)
static char child_stack[STACK_SIZE];
char * const child_args[] = {
"/bin/bash",
NULL
};
int child_main(void *args) {
printf("子进程...n");
execv(child_args[0],child_args);
return 1;
}
int main() {
printf("主程序开始...n");
int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWIPC|SIGCHLD, NULL);
waitpid(child_pid, NULL, 0);
printf("主程序已退出...n");
return 0;
}
使用ipcmk -Q创建了一个消息队列,clone()创建了一个新进程。使用ipcs -q命令查看该namespace下的消息队列,创建的消息队列未在该namespace。说明了IPC namespace将进程间通信消息队列隔离了
pid namespace的flag为CLONE_NEWUTS
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#define STACK_SIZE (1024*1024)
static char child_stack[STACK_SIZE];
char * const child_args[] = {
"/bin/bash",
NULL
};
int child_main(void *args) {
printf("子进程...n");
sethostname("IamZhu", 12);
execv(child_args[0],child_args);
return 1;
}
int main() {
printf("主程序开始...n");
int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWUTS|SIGCHLD, NULL);
waitpid(child_pid, NULL, 0);
printf("主程序已退出...n");
return 0;
}
编译并运行如下,主机名改了,说明新的UTS namespace下,主机名被隔离了。每个容器拥有自己独立的主机名和域名
pid namespace的flag为CLONE_NEWNET
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#define STACK_SIZE (1024*1024)
static char child_stack[STACK_SIZE];
char * const child_args[] = {
"/bin/bash",
NULL
};
int child_main(void *args) {
printf("子进程...n");
execv(child_args[0],child_args);
return 1;
}
int main() {
printf("主程序开始...n");
int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWNET|SIGCHLD, NULL);
waitpid(child_pid, NULL, 0);
printf("主程序已退出...n");
return 0;
}
编译运行如下,可以看到该namespace下只有loopback网卡,可以在该namespace添加网络设备,建立veth pair,设置ip路由等操作