我对container原理的一些理解(基于linux kernel 2.6.38)

by kin

2011.04.17

=====================================================

linux中称谓的container在内核层面由两个独立的机制保证,一个保证资源的隔离性,名为namespace;一个进行资源的控制,名为cgroup。

1   namespace

inux现有的namespace有6种: uts, pid, ipc, mnt, net和user 。所有的namespace都和task_struct相关,其中uts, pid, ipc, mnt和net都处于task_struct->ns_proxy中,而user_namespace却是由task_struct相关联的user_struct指向决定的 。

1.1 uts

最简单的主机名,一个namespace结构绑定这样一个字符串,uname的时候去current->nsproxy->uts_namespace下面取字符串就好了

1.2 ipc

ipc维护的是一个id到struct的映射关系,这种映射关系在内核由公用设施idr提供。所谓ipc的namespace就是每个namespace都有自己独立的idr,即独立的key->value的映射集合,不同的namespace通过key找value会在不同的idr中寻找,内核数据结构是公共可见的,隔离的仅仅是索引关系。

1.3 mnt

每个mnt_namespace有自己独立的vfsmount *root, 即根挂载点是互相独立的,同时由vfsmount->mnt_child串接起来的子mnt链表,以及继续往下都是彼此独立的,产生的外在效果就是某个mnt_namespace中的mount, umount不会 对其他namespace产生影响,因为整个mount树是每个namespace各有一份,彼此间无干扰, path lookup也在各自的mount树中进行。这里和chroot之类的又不一样,chroot改变的只是 task_struct相关的fs_struct中的root,影响的是path lookup的起始点,对整个mount树并无关系.

1.4 user

user_namespace的实现方式和ipc类似,每个namespace各自维护一个uid到user_struct的映射,用hash表实现。但是uid会在两个地方体现,一个是user_struct中的uid,还有一个是cred中的uid。user_namespace影响范围局限在user_struct中,虽然clone(NEWUSER)时会把task_struct的cred中的uid,gid都设成0,然后这种关系又可以通过fork等传递下去,但是终究user_namespace并没有影响到cred里面数据,而且vfs里面inode是只有uid的,

不会有user_struct信息,因此某个具体的文件其uid是固定的,具体在某个namespace中如何显示用户名则不关内核层的事了,由/etc/passwd中定义的映射关系决定。

另外还有个问题,比如跨两个namespace的unix套接字通信,有个选项叫PEERCRED,是拿对方节点的ucred结构,因为不同namespace,因此拿到的uid,gid都要进行user_namespace的重映射。这里重映射的策略就是:

1)同user_namespace,OK。不需要

2)不同,则由于每个user_namespace都会记录创建自己的那个user_struct,因此一层层往上索引到init_user_ns,如果发现需要remap的那个user_struct是我们的祖先创建者,则map为0,否则则返回一个表示不存在的MAGIC NUMBER

1.5 pid

pid_namespace是每个namespace有一个单独的pid_map作为bitmap进行pid的分配,因此各个pid namespace的pid互不干扰,独立分配。同一个task_struct会从init_ns开始,到最终它所在的namespace,每一层都会有个单独的pid(也就是深层次的task_struct创建,在每一个层次的namespace都会进行pid的alloc),而所有这些pid信息都是栈式保存在struct pid结构中。

pid是唯一一个底层namespace会把上层namespace信息都保留下来的namespace, pid信息保存在struct pid中,而具体的(pid, ns)信息对则保存在upid中,pid会根据自己的namespace深度扩展一个upid的栈,在这个pid结构中,该task_struct从init_ns到实际所处的namespace整个树路径上的(pid,ns)信息都记录了,于是上面所说的跨namesapce unix socket通信取PEERCRED对pid的remap就很简单了,有父子关系,直接从pid的不同深度取另一个值就行了;如果没父子关系,则MAGIC NUMBER。

同时pid的upid栈中每个upid都会连入对应pid namespace的hash表中,那么该task_struct在每个namespace层次中都是可见的(可在ns对应hash表中找到),且pid号互不相关(看到的是对应栈层次上的upid->nr)。

由于历史因素,task_struct中除了用pid索引pid, ppid, pgid,sid,结构体本身还有pid,tgid等,这里的数据都是 取的init_ns中的对应数值.

1.6 net

net_ns是非常复杂的一块。mainline都做了几个版本来稳定它,主要是各个关键数据结构都加上了net信息,比如sock, 路由查找的fib, netfilter的rules,net_device等, net对于不同net的数据隔离和前面几种每个namespace自己建索引表不同,net的一般都在同一个索引表中,只是数据多加一维net信息,在查询时多加上对这一维的比较。相应的网络层关键处理函数都会多带一个net_namespace的参数. 在net_namespace结构体内部,主要保存了网络各层关键的sysctl信息,用于实现对不同net namespace可以进行不同的内核参数配置,以及不同的proc entry导出。

1.7 task_struct 在不同ns之间的转移

最新的mainline kernel: 2.6.39rc3是没有实现这样的一个系统调用的。lxc内有个lxc-attach依赖一个setns的系统调用,这个系统调用原理就是通过你提供的/proc/pid/ns这样一个proc inode,来找到对应的namespace结构体,然后把current->nsproxy->xx_namespace设为那个,可能还要进行些额外的操作(比如pid的remap,user的remap之类的)就OK了。但是namespace本身就是提供的安全性隔离,这样做有安全风险,所以mainline kernel并没有merge这个patch。

1.8 如何创建新的namespace

创建一个新的进程,man 2 clone

当前进程,man 2 unshare

2. cgroup

cgroup是通过vfs的接口来进行配置的,它本身充当一个resource controller的角色,对一组进程进行资源控制,核心角色便是一组task_struct。

2.1 cgroup 的几个核心概念

cgroup核心的有三个概念:

hierarchy : 就是mount的一个cgroup fs。

subsystem : 一个subsystem对一种资源进行control

cgroup : 一个hierarchy下面对进程的分组

整体的逻辑可以这样来看:

1) 一个hierarchy和多个subsystem绑定,该hierarchy只对这几个subsystem对应的resouce进行control

2)不同的hierarchy绑定的subsystem是互斥的

3)一个hierarchy下面可以通过mkdir方式创建不同的cgroup,这个cgroup下面可以attach一组进程,通过该cgroup指定的参数对这种进程资源使用进 行track和控制

这样分可以使task A和task B在hierarchy A中在一个cgroup中控制CPU, 而task A和task B在hierarchy B中分别在两个cgroup中控制memory,提供 更灵活的策略

 

2.2 cgroup 在内核层次和task_struct的交互

一个cgroup和一个subsystem决定了一个cgroup_subsys_state, 每个进程都以css_set(一个静态数组)的方式保存了所有和它有关的cgroup_subsys_state,同样,每个cgroup也会保存这样一组。 cgroup_subsys_state又会作为每个subsystem自己结构体的内部成员包含,这样通过container_of很容易就可以找到subsystem自己的控制结构体,来进行各子系统自己的控制,于是每个task_struct都可以通过css_set找到它所属的subsystem结构体,来进行后面的操作.

而对于每个css_set, 也会把所有使用它的task连起来,这样cgroup导出所有当前绑定进程也有了依据(每个Cgroup链接一组css_set,每个css_set又会串联一组task_struct,挨个遍历)

2.3 cgroup 当前已有的subsys

kinwin@ustc-king:/data/linux-2.6$ grep "SUBSYS" include/linux/cgroup_subsys.h

/* Add subsystem definitions of the form SUBSYS(<name>) in this

SUBSYS(cpuset)

SUBSYS(debug)

SUBSYS(ns)

SUBSYS(cpu_cgroup)

SUBSYS(cpuacct)

SUBSYS(mem_cgroup)

SUBSYS(devices)

SUBSYS(freezer)

SUBSYS(net_cls)

SUBSYS(blkio)

SUBSYS(perf)

其中ns_cgroup已经被抛弃,通过cgroup.clone_children和手动attach进程来取代

2.4 cpu_cgroup

对CPU的使用率进行控制,根本上利用了CFS中已有的task group概念,最近出来的autogroup也是 利用了这样一个机制。内核中的调度针对的是sched_entity这样一个结构,sched_entity本身有一个run queue,同时它也会在别人的run queue上,以此层层嵌套下来,直至最后的可执行单元(task_struct)。于是一个cgroup的cpu resource controller

便是这样的一个sched_entity,所有attach到这个Cgroup的进程都在这个sched_entity下面 ,调度也都在这个sched_entity下面进行。

这样的一个树形架构下,每个sched_entity只负责进行在该sched_entity runquueu上的sched_entity进行CFS,即从红黑树最左端pick一个sched_entity,由该sched_entity进行下一个层次的CFS,而每次最终pick出来的task_struct的运行对所有它的父sched_entity的运行时间都有贡献,如此实现一个全局的CFS。并且实现task group or task group's group...

2.5 mem_cgroup

memory有全局的per zone lru list来进行page relcaim等,有全局的page数组来进行物理内存管理. memory_cgroup在cgroup层面上实现了per_cgroup lru list, 以及page_cgroup数组,memory_cgroup的lru操作及其他基于page的操作都是以page_cgroup为依据的,而page_cgroup和真正的page之间又会有个映射关系

mem_cgroup对于内存资源的控制,是对每个cgroup的使用内存情况进行记录,主要方式就是在真正分配物理页面的地方(缺页中断,或者分配page cache页等)都进行了hack(可以统计哪些页面取决于在哪些内存分配部分进行了分配物理页的hack, 目前应该是绝大部分都有进行hack),通过current->cgroups->subsys[xx]找到对应的mem_cgroup控制结构,

判断可否分配,可以就加上自己的计数,不行或达到某个limit,就会触发基于per-cgroup lru的reclaim, 再或者就触发cgoup内部的OOM killer等。

但是内存作为一个最复杂的部分,mem_cgroup的发展也是今年Linux filesystem, storage, and memory management summit讨论最多的话题,

有以下几点

1)重复的lru list,全局内存紧张依然会page reclaim,不分cgroup。而且两个lru list两次lru也重复了

2) page_cgroup结构体可以变的没有,现在20 bytes(32bit)/40bytes(64 bit)太大了

3) 全局和单个cgroup的权衡

...

2.6 net_cls

主要利用了内核网络协议栈traffic control的相关东西,实现了一个cgroup的统一标记id,然后实现了一个叫cgoup的filter,这个filter就是根据当前进程所在的cgroup_subsys决定给sk_buff打上何样的id标记,然后这个标记就会被用于匹配相应的traffic control的qdisc和class来进行具体的流量控制策略。

2.7 device

在inode_permission中hook入cgroup的检查,对于inode->st_type为char或block类型的,会与保存在列表中的进行读,写权限匹配,根据匹配结果决定

cgoup的inode检查返回允许否?

在vfs_mknod hook入cgroup对mknod主从设备号的匹配检查,决定允许否?

2.8 freezer

给cgoup中每个task_struct设置下TIF_FREEZING,然后就开始try_to_freeze, 设置个PF_FROZON的flag,进程就开始空转了

for (;;) {

set_current_state(TASK_UNINTERRUPTIBLE);

if (!frozen(current))

break;

schedule();

}

而唤醒如上代码所示,就是去掉PF_FROZON这个flag的过程。

2.9 cpuset

同sched_setaffinity,但是是对于一组进程设置CPU亲和性了。内核在CPU亲和性逻辑跟以前没什么区别,无非是把这些进程只调度到对应CPU的run queue而已。

同时cpuset还提供了一个只在对应cpu间进行负载均衡的特性,就是把对应的cpu作为一个sched doamin,可以在其中负载均衡。不过要求最好各个cgoup设置互斥的cpu,否则就会取cgroup的最大互斥并集作为sched domain,这样跨cgroup的load banlance又会导致其他的复杂因素。

3. lxc

lxc是一个用户空间的管理工具,提供用户友好的接口包装了内核提供的namespace和cgroup机制.

它主要实现原理是这样的:

0)  首先准备创建好网络设备(netlink创建设备),

  • openpty指定数目的pty(创建对应的/dev/ptmx和/dev/pts/xx对),作为lxc_console所用,slave会被mount bind到container的/dev/ttyx,通过master fd可和container中

起来的shell通信

并open一个/dev/tty作为console所用,这个console设备也会被mount bind到container中,这个console fd就是用于lxc_start后获得的那个console与container的通信

1) 开始clone,创建新的namespace :父进程clone一个子进程,clone的时候指定新建所有的namespace,如此一个完全新的namespace就建立了。

子进程:

2) 父子同步 :子进程设置自己死了给父进程发sigkill,防止父进程空等, 同时也保证container中的init死掉同时会导致host中的lxc_start死掉.

父进程:

3) 建立对应的cgroup :父进程设置clone_children,然后创建新的cgroup,并将clone的pid attach到新创建 cgoup中。(等同于以前ns_cgroup所做的工作)

4) 挪移网络设备: 将配置在container中的网络设备通过发送netlink消息,带上IFLA_NET_NS_PID的rtattr,触发内核的dev_change_net_namespace,将net_device的namespace替换掉,

具体网络设备的change namespace涉及到在一个namespace的完全停止,解注册和在另一个namespace中的注册等流程。

子进程:

5) 设置container utsname

6) 设置container网络设备参数 :通过netlink消息给本net namespace 网络设备设置ip addr, hw addr, flags等信息

7) 修改cgroup中resouce control参数 :由于还没有chroot,可以修改自己cgroup中相应的resource control设置

8) 建立根文件系统并建立自己的挂载: mount rootfs, mount fstab等等

8) 建立host和container在终端方面的通讯渠道

把/dev/tty mount bind到container中,这样我们就可以:

pty slave <----> pty master  <----> epoll 两端转发   console fd 来获得刚开始的一个Console

把前面创建的pty的slave mount bind到container中的/dev/ttyx, 这样我们就可以通过对应的pty master fd实现和container中的通信:

stdin,stdou  epoll 两端转发 <--------> pty master <------> mount bind到container中的/dev/ttyx

9) 改变根挂载点 : chroot并把从host中继承过来的mount关系在container中不会用到的umount掉及其他一些保证系统正常运转要干的其他工作

10) exec 开始init: container 1号进程exec创建自己的进程树

父进程,子进程同时运转

11) 父 epoll循环 :父进程醒来 ,开始一个epoll循环,主要处理console和container的两端转发epoll, 以及接收一些外来查询请求的unix套接口epoll

比如查询container init在host中进程pid,这样我们可以kill -SIGKILL pid, 就可以杀掉整个container

通过unix套接字的msgcontrol传递pty的master fd(也就是被mount bind到container中/dev/ttyx的peer fd),用来实现lxc_console取container tty的作用。

unix套接字绑定在一个已知的路径下,在lxc 0.7.4中通过这个unix套接口实现有取container init pid,取tty fd,取container state及停止container的作用。

而基本所有的lxc小工具都是基于这个unix套接字来获取一些关于container的信息。

12)  子独立进程树下的运转: 在一个单独的namespace里面,有自己独立的resource control,单独的基础设施和用户空间进程树,一个隔离并资源控制的新环境,谓之container。便

运转起来了。

1.  Linux内核namespace机制

Linux Namespaces机制提供一种资源隔离方案。PID,IPC,Network等系统资源不再是全局性的,而是属于某个特定的Namespace。每个namespace下的资源对于其他namespace下的资源都是透明,不可见的。因此在操作系统层面上看,就会出现多个相同pid的进程。系统中可以同时存在两个进程号为0,1,2的进程,由于属于不同的namespace,所以它们之间并不冲突。而在用户层面上只能看到属于用户自己namespace下的资源,例如使用ps命令只能列出自己namespace下的进程。这样每个namespace看上去就像一个单独的Linux系统。

2 .  Linux内核中namespace结构体

在Linux内核中提供了多个namespace,其中包括fs (mount),uts, network, sysvipc, 等。一个进程可以属于多个namesapce,既然namespace和进程相关,那么在task_struct结构体中就会包含和namespace相关联的变量。在task_struct 结构中有一个指向namespace结构体的指针nsproxy。

structtask_struct {

……..

/* namespaces */

structnsproxy *nsproxy;

…….

}

再看一下nsproxy是如何定义的,在include/linux/nsproxy.h文件中,这里一共定义了5个各自的命名空间结构体,在该结构体中定义了5个指向各个类型namespace的指针,由于多个进程可以使用同一个namespace,所以nsproxy可以共享使用,count字段是该结构的引用计数。

/* 'count' isthe number of tasks holding a reference.

* The count for each namespace, then, will bethe number

* of nsproxies pointing to it, not the numberof tasks.

* The nsproxy is shared by tasks which shareall namespaces.

* As soon as a single namespace is cloned orunshared, the

* nsproxy is copied

*/

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;

};

(1)     UTS命名空间包含了运行内核的名称、版本、底层体系结构类型等信息。UTS是UNIX Timesharing System的简称。

(2)     保存在structipc_namespace中的所有与进程间通信(IPC)有关的信息。

(3)     已经装载的文件系统的视图,在structmnt_namespace中给出。

(4)     有关进程ID的信息,由structpid_namespace提供。

(5)     struct net_ns包含所有网络相关的命名空间参数。

系统中有一个默认的nsproxy,init_nsproxy,该结构在task初始化是也会被初始化。#defineINIT_TASK(tsk)  \

{

.nsproxy   = &init_nsproxy,

}

其中init_nsproxy的定义为:

static structkmem_cache *nsproxy_cachep;

struct nsproxyinit_nsproxy = {

.count                         =ATOMIC_INIT(1),

.uts_ns                       =&init_uts_ns,

#ifdefined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)

.ipc_ns                        =&init_ipc_ns,

#endif

.mnt_ns                      =NULL,

.pid_ns_for_children        = &init_pid_ns,

#ifdefCONFIG_NET

.net_ns                       =&init_net,

#endif

};

对于         .mnt_ns   没有进行初始化,其余的namespace都进行了系统默认初始。

3. 使用clone创建自己的Namespace

如果要创建自己的命名空间,可以使用系统调用clone(),它在用户空间的原型为

int clone(int (*fn)(void *), void*child_stack, int flags, void *arg)

这里fn是函数指针,这个就是指向函数的指针,, child_stack是为子进程分配系统堆栈空间,flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数也就是fn指向的函数参数。下面是flags可以取的值。这里只关心和namespace相关的参数。

CLONE_FS          子进程与父进程共享相同的文件系统,包括root、当前目录、umask

CLONE_NEWNS     当clone需要自己的命名空间时设置这个标志,不能同时设置CLONE_NEWS和CLONE_FS。

Clone()函数是在libc库中定义的一个封装函数,它负责建立新轻量级进程的堆栈并且调用对编程者隐藏了clone系统条用。实现clone()系统调用的sys_clone()服务例程并没有fn和arg参数。封装函数把fn指针存放在子进程堆栈的每个位置处,该位置就是该封装函数本身返回地址存放的位置。Arg指针正好存放在子进程堆栈中的fn的下面。当封装函数结束时,CPU从堆栈中取出返回地址,然后执行fn(arg)函数。

/* Prototype forthe glibc wrapper function */

#include

int clone(int (*fn)(void *), void*child_stack,

int flags, void *arg, ...

/* pid_t *ptid, structuser_desc *tls, pid_t *ctid */ );

/* Prototype for the raw system call */

long clone(unsigned long flags, void *child_stack,

void *ptid, void *ctid,

struct pt_regs *regs);

我们在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

{

returndo_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);

}

#endif

3.1  do_fork函数

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

{

structtask_struct *p;

inttrace = 0;

longnr;

/*

* Determine whether and which event to reportto ptracer.  When

* called from kernel_thread or CLONE_UNTRACEDis 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;

elseif ((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 - thethread pointer

* might get invalid after that point, if thethread exits quickly.

*/

if(!IS_ERR(p)) {

structcompletion vfork;

structpid *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);

}

returnnr;

}

3.2  copy_process函数

在copy_process函数中调用copy_namespaces函数。

static structtask_struct *copy_process(unsigned long clone_flags,

unsignedlong stack_start,

unsignedlong stack_size,

int__user *child_tidptr,

structpid *pid,

inttrace)

{

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)

gotobad_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)

gotobad_fork_cleanup_io;

}…..

}

3.3  copy_namespaces 函数

在kernel/nsproxy.c文件中定义了copy_namespaces函数。

int copy_namespaces(unsigned long flags,struct task_struct *tsk)

{

structnsproxy *old_ns = tsk->nsproxy;

structuser_namespace *user_ns = task_cred_xxx(tsk, user_ns);

structnsproxy *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);

return0;

}

/*当前进程是否有超级用户的权限*/

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 arraysfrom the old

* namespace are unreachable.  In clone parlance, CLONE_SYSVSEM

* means share undolist with parent, so we mustforbid 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;

return0;

}

3.4  create_new_namespaces函数

create_new_namespaces创建新的namespace

static struct nsproxy*create_new_namespaces(unsigned long flags,

structtask_struct *tsk, struct user_namespace *user_ns,

structfs_struct *new_fs)

{

structnsproxy *new_nsp;

interr;

/*为新的nsproxy分配内存空间,并对其引用计数设置为初始1*/

new_nsp= create_nsproxy();

if(!new_nsp)

returnERR_PTR(-ENOMEM);

/*如果Namespace中的各个标志位进行了设置,则会调用相应的namespace进行创建*/

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

gotoout_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);

gotoout_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);

gotoout_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);

gotoout_pid;

}

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

gotoout_net;

}

returnnew_nsp;

out_net:

if(new_nsp->pid_ns_for_children)

put_pid_ns(new_nsp->pid_ns_for_children);

out_pid:

if(new_nsp->ipc_ns)

put_ipc_ns(new_nsp->ipc_ns);

out_ipc:

if(new_nsp->uts_ns)

put_uts_ns(new_nsp->uts_ns);

out_uts:

if(new_nsp->mnt_ns)

put_mnt_ns(new_nsp->mnt_ns);

out_ns:

kmem_cache_free(nsproxy_cachep,new_nsp);

returnERR_PTR(err);

}

3.4.1 create_nsproxy函数

static inline struct nsproxy*create_nsproxy(void)

{

structnsproxy *nsproxy;

nsproxy= kmem_cache_alloc(nsproxy_cachep, GFP_KERNEL);

if(nsproxy)

atomic_set(&nsproxy->count,1);

returnnsproxy;

}

例子1:namespace pid的例子

#include

#include

#include

#include

#include

#include

#include

static int fork_child(void *arg)

{

inta = (int)arg;

inti;

pid_tpid;

char*cmd  = "ps -el;

printf("Inthe container, my pid is: %d\n", getpid());

/*ps命令是解析procfs的内容得到结果的,而procfs根目录的进程pid目录是基于mount当时的pid namespace的,这个在procfs的get_sb回调中体现的。因此只需要重新mount一下proc, mount-t proc proc /proc*/

mount("proc","/proc", "proc", 0, "");

for(i = 0; i

pid= fork();

if(pid <0)

returnpid;

elseif (pid)

printf("pidof my child is %d\n", pid);

elseif (pid == 0) {

sleep(30);

exit(0);

}

}

execl("/bin/bash","/bin/bash","-c",cmd, NULL);

return0;

}

int main(int argc, char *argv[])

{

intcpid;

void*childstack, *stack;

intflags;

intret = 0;

intstacksize = getpagesize() * 4;

if(argc != 2) {

fprintf(stderr,"Wrong usage.\n");

return-1;

}

stack= malloc(stacksize);

if(stack== NULL)

{

return-1;

}

printf("Outof the container, my pid is: %d\n", getpid());

childstack= stack + stacksize;

flags= CLONE_NEWPID | CLONE_NEWNS;

cpid= clone(fork_child, childstack, flags, (void *)atoi(argv[1]));

printf("cpid:%d\n", cpid);

if(cpid <0) {

perror("clone");

ret= -1;

goto out;

}

fprintf(stderr,"Parent sleeping 20 seconds\n");

sleep(20);

ret= 0;

out:

free(stack);

returnret;

}

}运行结果:

root@ubuntu:~/c_program#./namespace 7

Out of the container, my pid is: 8684

cpid: 8685

Parent sleeping 20 seconds

In the container, my pid is: 1

pid of my child is 2

pid of my child is 3

pid of my child is 4

pid of my child is 5

pid of my child is 6

pid of my child is 7

pid of my child is 8

F S  UID   PID  PPID  CPRI  NI ADDR SZ WCHAN  TTY         TIME CMD

4 R    0     1     0 0  80   0 - 1085 -      pts/0    00:00:00 ps

1 S    0     2     1 0  80   0 -  458 hrtime pts/0    00:00:00namespace

1 S    0     3     1 0  80   0 -  458 hrtime pts/0    00:00:00namespace

1 S    0     4     1 0  80   0 -  458 hrtime pts/0    00:00:00namespace

1 S    0     5     1 0  80   0 -  458 hrtime pts/0    00:00:00namespace

1 S    0     6     1 0  80   0 -  458 hrtime pts/0    00:00:00namespace

1 S    0     7     1 0  80   0 -  458 hrtime pts/0    00:00:00namespace

1 S    0     8     1 0  80   0 -  458 hrtime pts/0    00:00:00namespace

例子2:UTS的例子

#define _GNU_SOURCE

#include

#include

#include

#include

#include

#include

#include

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \

} while (0)

staticint              /* Start function forcloned child */

childFunc(void *arg)

{

structutsname uts;

/*Change hostname in UTS namespace of child */

if(sethostname(arg, strlen(arg)) == -1)

errExit("sethostname");

/*Retrieve and display hostname */

if(uname(&uts) == -1)

errExit("uname");

printf("uts.nodenamein child:  %s\n", uts.nodename);

/*Keep the namespace open for a while, by sleeping.

*              This allows some experimentation--for example, another

*                             process might jointhe namespace. */

sleep(200);

return0;           /* Child terminates now */

}

#define STACK_SIZE (1024 * 1024)    /* Stack size for cloned child */

int

main(int argc, char *argv[])

{

char*stack;                    /* Start ofstack buffer */

char*stackTop;                 /* End ofstack buffer */

pid_tpid;

structutsname uts;

if(argc < 2) {

fprintf(stderr,"Usage: %s \n", argv[0]);

exit(EXIT_SUCCESS);

}

/*Allocate stack for child */

stack= malloc(STACK_SIZE);

if(stack == NULL)

errExit("malloc");

stackTop= stack + STACK_SIZE;  /* Assume stackgrows downward */

/*Create child that has its own UTS namespace;

*              child commences execution in childFunc() */

pid= clone(childFunc, stackTop, CLONE_NEWUTS | SIGCHLD, argv[1]);

if(pid == -1)

errExit("clone");

printf("clone()returned %ld\n", (long) pid);

/*Parent falls through to here */

sleep(1);           /* Give child time to change itshostname */

/*Display hostname in parent's UTS namespace. This will be

*              different from hostname in child's UTS namespace. */

if(uname(&uts) == -1)

errExit("uname");

printf("uts.nodenamein parent: %s\n", uts.nodename);

if(waitpid(pid, NULL, 0) == -1)    /* Waitfor child */

errExit("waitpid");

printf("childhas terminated\n");

exit(EXIT_SUCCESS);

}

root@ubuntu:~/c_program# ./namespace_1 test

clone() returned 4101

uts.nodename in child:  test

uts.nodename in parent: ubuntu

对于网络命名空间可以参考:

http://www.opencloudblog.com/?p=42

http://wenx05124561.blog.163.com/blog/static/124000805201311250241189/

http://man7.org/linux/man-pages/man2/clone.2.html

linux container容器技术框架性理解相关推荐

  1. Linux 学习 -- 容器技术

    Linux 学习 – 容器技术 容器基础概述 容器部署 一.容器基础概述 容器(Container) : 定义:指的是针对应用所需的运行环境资源(依赖库/目录/网络/用户--等)进行整体封装的技术. ...

  2. Linux总线设备驱动框架的理解(非常棒的文章!)

    以下内容源于微信公众号:嵌入式企鹅圈.有格式内容上的修改,如有侵权,请告知删除. Linux的设备驱动框架,即某类设备对应的驱动的框架. 这里是"Linux总线设备驱动框架",应该 ...

  3. 开发时对业务技术框架的理解

    转载于:https://www.cnblogs.com/codechangeword/p/11212265.html

  4. 十分钟明白什么是容器技术

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由宝哥@devops运维 发表于云+社区专栏 一.什么是容器? 容器这个词,当你第一眼看它或许脑子里是这东西:瓶瓶罐罐.装水.装其他东西 ...

  5. 汇总网上资料,一篇文章扫盲“容器技术”

    容器技术:一句话来说明就是"进行了进程隔离和资源限制的进程" 一.什么是容器? 首先,从现实生活上说,容器是用来省东西的,如柴米油盐酱醋茶.洗发水护发素沐浴露等都需要各式各样的容器 ...

  6. 嵌入式Linux容器技术

    嵌入式Linux容器技术 一.Linux容器技术 Linux Container容器是一种内核虚拟化技术,可以提供轻量级的虚拟化,以便隔离进程和资源. LXC为Linux Container的简写.可 ...

  7. Centos7.6部署docker容器虚拟化平台(Container 容器端口映射)

    Docker的概述: 实验环境: Centos 7.6版本 Dcoker 概述 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器 中,然后发布到任何流行的 ...

  8. 前沿|十位顶级大咖为您把脉容器技术大势

    5月18-19日,北京 • 朝阳门悠唐皇冠假日酒店,CSDN主办的中国云计算技术大会(http://cctc.csdn.net/)将围绕当前最热门.最前沿的云计算技术与行业实践进行展开.在这场盛大的技 ...

  9. 容器技术及其应用白皮书(上)-- 容器技术

    内容摘要 近年来,容器技术及相关应用得到了国内外越来越多的关注度,研发和应用推广发展势头迅猛.在国外,容器技术已经形成了较成熟的生态圈:在国内,金融企业.互联网企业.IT企业积极投入容器技术研发和应用 ...

最新文章

  1. Console-算法-冒泡排序法|倒水法
  2. 怎么把快捷键改成eclipse_Java IDE超好用的10个快捷键
  3. String 中的秘密
  4. NAACL 2019 | 一种考虑缓和KL消失的简单VAE训练方法
  5. 用最快速度,打造「最强 Webpack 前端工具链」,强势运行
  6. android WebView的简单使用
  7. 设计模式练习_设计练习是邪恶的
  8. 传智杯Java终端有一天您厌烦了电脑上又丑又没用的终端,打算自己实现一个 Terminal
  9. 《常用控制电路》学习笔记——数控锁相环调速电路
  10. iOS平台经典游戏《合金弹头X》上架 售价25元
  11. qt绘图事件(画刷的填充)
  12. SSM、SSH框架原理
  13. vscode使用教程-开始学习前端开发啦~
  14. 责任链模式的高级用法
  15. java百度地图坐标_腾讯地图坐标与百度地图坐标互相转换(PHP版、Java版)
  16. VSCode添加背景图片
  17. php批量处理图片大小,wps如何批量处理图片大小
  18. 初探Java设计模式------观察者模式
  19. 程序员VS文艺男!!论发型的重要性,堪比整容!
  20. sql server2005完全卸载与重装

热门文章

  1. Less和Sass的使用
  2. 1.2、Android Studio为新设备创建一个模块
  3. Thrift解读(五)——server端RPC接收与返回值的回送
  4. 福特数据总监:汽车业的大数据框架如何构建?
  5. 07-阻塞赋值与非阻塞赋值原理分析——小梅哥FPGA设计思想与验证方法视频教程配套文档...
  6. LeetCode - Add Binary
  7. 一流人才在军界和商界,二流人才在政界,三流人才在学术界;男孩子,可以什么都不会,但是必须会挣钱...
  8. bzoj3211,bzoj3038
  9. C#进行Visio二次开发之知识点考核试题
  10. SpringMVC启动后自动执行