操作系统级虚拟化
KVM和XEN等虚拟化技术允许每个虚拟机拥有自己独立的操作系统。与KVM、XEN等虚拟化技术不同,所谓OS级虚拟化,也叫容器化,是操作系统本身的一个特性,它允许存在多个隔离的用户空间实例。这些用户空间实例也称为容器。普通进程可以看到计算机的所有资源,而容器中的进程只能看到分配给容器的资源。一般来说,操作系统级虚拟化将操作系统管理的计算机资源分组,包括进程、文件、设备、网络等。然后交给不同的容器使用。在容器中运行的进程只能看到分配给容器的资源。从而达到隔离和虚拟化的目的。
操作系统虚拟化的实现需要名称空间和cgroups技术。
名称空间(名称空间)
在编程语言中,命名空间的概念被引入来重用变量名或服务例程名。在不同的名称空间中使用相同的变量名,不会产生冲突。在Linux系统中引入名称空间也有类似的效果。例如,在没有操作系统级虚拟化的Linux系统中,用户模式进程从1开始编号(PID)。引入操作系统虚拟化后,不同的容器有不同的PID命名空间,每个容器中的进程可以从1开始编号,不会冲突。
目前Linux中有六种类型的命名空间,分别对应操作系统管理的六种资源:
挂载点)CLONE_NEWNS
进程(pid)克隆_新PID
网络克隆
进程间通信(ipc) CLONE_NEWIPC
主机名(UTS)克隆_新uts
用户(uid)克隆_新用户
未来会引入时间、装备等相应的命名空间。
Linux版本2.4.19引入了第一个名称空间——挂载点。因为当时没有其他类型的名称空间,所以clone系统调用中引入的标志称为CLONE_NEWNS。
与名称空间相关的三个系统调用
以下三个系统调用用于操纵名称空间:
Clone() ——用于创建一个新的进程和一个新的命名空间,新的进程将被放置在新的命名空间中。
Unshare() ——创建新的命名空间,但不创建新的子进程。稍后创建的子进程将被放置在新创建的名称空间中。
Setns() ——将进程添加到现有的命名空间中。
注意:这三个系统调用不会改变调用进程的pid名称空间,但是会影响其子进程的pid名称空间。
名称空间本身不命名(尴尬),不同的名称空间用不同的inode号标识,也符合Linux用文件称霸世界的惯例。可以在proc文件系统中检查进程的命名空间,比如用PID 4123检查进程的命名空间:
开尔文@桌面:~$ls -l /proc/4123/ns/
总剂量0
lrwxrwxrwx 1 kelvin kelvin 012 December 2616:28 c group-c group:[4026531835]
lrwxrwxrwx 1 kelvin kelvin 012 December 2616:28 IPC-IPC:[4026531839]
lrwxrwxrwx 1 kelvin kelvin 012 December 2616:28 mnt-mnt:[4026531840]
lrwxrwxrwx 1 kelvin kelvin 012 December 2616:28 net-net:[4026531963]
lrwxrwxrwx 1 kelvin kelvin 012 December 2616:28 PID-PID:[4026531836]
lrwxrwxrwx 1 kelvin kelvin 012 December 2616:28用户-用户:[4026531837]
lrwxrwxrwx 1 kelvin kelvin 012 December 2616:28 uts-uts:[4026531838]
下面的代码演示了如何使用上述三个系统调用来操作进程的命名空间:
#定义_ GNU _源
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#定义堆栈大小(10 * 1024 * 1024)
char child _ STACK[STACK _ SIZE];
intchild_main(void* args){
PID _ t child _ PID=getpid();
printf(我我的pid是% d \ n ,child _ PID);
//子进程会放在克隆系统调用新创建的pid命名空间里,所以它的pid应该是1。
睡眠(300);
//命名空间中的所有进程退出后,命名空间的inode将被删除,它将被保留用于后续操作。
return0
}
intmain(){
PID _ t child _ PID=clone(child _ main,child_stack STACK_SIZE,\
CLONE_NEWPID | SIGCHLD,NULL);
if(child_pid 0){
perror(克隆失败);
}
intret=unshare(CLONE _ NEWPID);//父进程调用unshare并创建新的命名空间,
//但是不会创建子进程。稍后创建的子进程将被添加到新的名称空间中
if(ret 0){
perror(取消共享失败);
}
int fpid=fork();
if(fpid 0){
perror(分叉错误);
}elseif(fpid==0){
printf(我是子进程。我的pid是% d \ n ,getpid());
fork之后的子进程会添加到unshare创建的命名空间中,所以pid应该是1。
退出(0);
}否则{
}
waitpid(fpid,NULL,0);
char path[80]='
sprintf(path,/proc/% d/ns/PID child _ PID);
intfd=open(path,O _ rd only);
如果(fd==-1)
perror(打开错误);
if(setns(fd,0)==-1)
//setns不改变当前进程的命名空间,而是设置后面创建的子进程的命名空间。
perror(etns错误);
关闭(FD);
int npid=fork();
if(npid 0){
perror(分叉错误);
}elseif(npid==0){
printf(我是子进程。我的pid是% d \ n ,getpid());
//新的子进程将被添加到第一个子进程的pid命名空间中,所以它的pid应该是2。
退出(0);
}否则{
}
return0
}
运行结果:
$须藤。/ns
我mchildprocess和我的pid是1
我是childprocess。我的pid是1
我是childprocess。我的pid是2
控制组(群组)
如果从命名和编号的角度来看,命名空间是孤立的,那么控制组将进程分组,并真正限制和隔离每组进程的计算资源。控制组是一种内核机制,它可以对进程进行分组,并跟踪限制其使用的计算资源。对于每一种计算资源,控制组通过所谓的子系统进行控制。目前,现有的子系统包括:
Cpusets:用于将一组CPU分配给指定的cgroup,cgroup中的进程只被调度给该组CPU执行。
BlkIO:阻止限制cgroup的IO
Cpuacct:用于统计cgroup中CPU的使用情况。
设备:使用黑白列表来控制cgroup可以创建和使用的设备节点。
冻结:用于暂停指定的群组,或唤醒暂停的群组。
Hugetlb:用于限制在cgroup中使用hugetlb。
内存:用于跟踪受限内存和交换分区的使用情况。
Net_cls:用于根据发送方的cgroup来标记数据包,流量控制器会根据这些标记来分配优先级。
Net_prio:用于设置cgroup的网络通信优先级。
Cpu:用于设置cgroup中CPU的调度参数
Perf_event:用于监控cgroup的CPU性能
与命名空间不同的是,控件组不添加系统调用,而是实现一个文件系统,通过文件和目录操作来管理控件组。让让我们看看cgroup如何使用cpuset子系统将进程绑定到指定的CPU上执行。
1.创建一个始终执行的shell脚本。
#!/bin/bash
x=0
while[True];做
搞定;
2.在后台执行这个脚本。
# bash run.sh
[1]20553
3.检查脚本在哪个CPU上运行。
# ps -eLo ruser,lwp,psr,args | grep 20553 | grep -v grep
root 20553 3bash run.sh
可以看到PID为20553的进程运行在编号为3的CPU上,然后被cgroups绑定到编号为2的CPU上执行。
4.将cgroups类型的文件系统挂载到新创建的目录cgroups中。
# mkdir组
# mount -t cgroup -o cpuset cgroups。/cgroups/
# ls cgroups/
c group . clone _ children cpuset . memory _ pressure _ enabled
cgroup . procscpuset . memory _ spread _ page
cgroup . sane _ behavior puset . memory _ spread _ slab
cpu_exclusivecpuset.mems
CPU set . CPU CPU set . sched _ load _ balance
CPU set . effective _ CPU CPU set . sched _ relax _ domain _ level
cpuset.effective_mems docker
cpuset.mem_exclusivetasks
CPU set . mem _ hardwall notify _ on _ release
CPU set . memory _ migrate release _ agent
cpuset.memory_pressure
5.创建新的组group0
#市场目录组0
# ls组0/
cgroup . clone _ children cpuset . mem _ exclusive cpuset . MEMS
cgroup . procs cpuset . mem _ hardwall cpuset . sched _ load _ balance
CPU set . CPU _ exclusive CPU set . memory _ migratecpuset . sched _ relax _ domain _ level
CPU set . cpuscpuset . memory _ pressure notify _ on _ release
cpuset . effective _ cpuscpuset . memory _ spread _ page tasks
CPU set . effective _ memscpuset . memory _ spread _ slab
6.将上述过程20553添加到新创建的控制组中:
# echo 20553组0/任务
# cat组0/任务
20553
7.限制该组的进程只在编号为2的CPU上运行
# echo 2 group 0/CPU set . CPU
# cat group 0/CPU set . CPU
2
8.检查PID为20553的进程的CPU号
# ps -eLo ruser,lwp,psr,args | grep 20553 | grep -v grep
root 20553 2bash run.sh
上面的例子简单地展示了如何使用控制组。控制组是通过文件和目录来操作的,文件系统是树形结构,所以如果不对cgroups的使用做一些限制,配置会变得极其复杂和混乱。因此,在新版本的cgroups中做了一些限制。
总结
简要介绍了操作系统虚拟化的概念,以及实现操作系统虚拟化——命名空间和控件组的技术。给出了两个简单的例子来演示命名空间和控件组的使用。
标签:进程空间命名