操作系統級虛擬化
KVM、XEN等虛擬化技術允許各個虛擬機擁有自己獨立的操作系統。與KVM、XEN等虛擬化技術不同,所謂操作系統級虛擬化,也被稱作容器化,是操作系統自身的一個特性,它允許多個相互隔離的用戶空間實例的存在。這些用戶空間實例也被稱作為容器。普通的進程可以看到計算機的所有資源而容器中的進程只能看到分配給該容器的資源。通俗來講,操作系統級虛擬化將操作系統所管理的計算機資源,包括進程、文件、設備、網絡等分組,然后交給不同的容器使用。容器中運行的進程只能看到分配給該容器的資源。從而達到隔離與虛擬化的目的。
實現操作系統虛擬化需要用到Namespace及cgroups技術。
命名空間(Namespace)
在編程語言中,引入命名空間的概念是為了重用變量名或者服務例程名。在不同的命名空間中使用同一個變量名而不會產生沖突。Linux系統引入命名空間也有類似的作用。例如,在沒有操作系統級虛擬化的Linux系統中,用戶態進程從1開始編號(PID)。引入操作系統虛擬化之后,不同容器有著不同的PID命名空間,每個容器中的進程都可以從1開始編號而不產生沖突。
目前,Linux中的命名空間有6種類型,分別對應操作系統管理的6種資源:
掛載點(mount point) CLONE_NEWNS
進程(pid) CLONE_NEWPID
網絡(net) CLONE_NEWNET
進程間通信(ipc) CLONE_NEWIPC
主機名(uts) CLONE_NEWUTS
用戶(uid) CLONW_NEWUSER
將來還會引入時間、設備等對應的namespace.
Linux 2.4.19版本引入了第一個命名空間——掛載點,因為那時還沒有其他類型的命名空間,所以clone系統調用中引入的flag就叫做CLONE_NEWNS
與命名空間相關的三個系統調用(system calls)
下面3個系統調用用來操作命名空間:
clone() —— 用來創建新的進程及新的命名空間,新的進程會被放到新的命名空間中
unshare() —— 創建新的命名空間但并不創建新的子進程,之后創建的子進程會被放到新創建的命名空間中去
setns() —— 將進程加入到已經存在的命名空間中
注意:這3個系統調用都不會改變調用進程(calling process)的pid命名空間,而是會影響其子進程的pid命名空間
命名空間本身并沒用名字(囧),不同的命名空間用不同的inode號來標識,這也符合Linux用文件一統天下的慣例。可以在proc文件系統中查看一個進程所屬的命名空間,例如,查看PID為4123的進程所屬的命名空間:
kelvin@desktop:~$ls -l /proc/4123/ns/
總用量0
lrwxrwxrwx1kelvin kelvin012月2616:28cgroup -> cgroup:[4026531835]
lrwxrwxrwx1kelvin kelvin012月2616:28ipc -> ipc:[4026531839]
lrwxrwxrwx1kelvin kelvin012月2616:28mnt -> mnt:[4026531840]
lrwxrwxrwx1kelvin kelvin012月2616:28net -> net:[4026531963]
lrwxrwxrwx1kelvin kelvin012月2616:28pid -> pid:[4026531836]
lrwxrwxrwx1kelvin kelvin012月2616:28user -> user:[4026531837]
lrwxrwxrwx1kelvin kelvin012月2616:28uts -> uts:[4026531838]
下面的代碼演示了如何利用上述3個系統調用來操作進程的命名空間:
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define STACK_SIZE (10 * 1024 * 1024)
charchild_stack[STACK_SIZE];
intchild_main(void* args){
pid_t child_pid = getpid();
printf("I'm child process and my pid is %d \n",child_pid);
// 子進程會被放到clone系統調用新創建的pid命名空間中, 所以其pid應該為1
sleep(300);
// 命名空間中的所有進程退出后該命名空間的inode將會被刪除, 為后續操作保留它
return0;
}
intmain(){
/* Clone */
pid_t child_pid = clone(child_main,child_stack + STACK_SIZE,\
CLONE_NEWPID | SIGCHLD,NULL);
if(child_pid < 0){
perror("clone failed");
}
/* Unshare */
intret = unshare(CLONE_NEWPID);// 父進程調用unshare, 創建了一個新的命名空間,
//但不會創建子進程. 之后再創建的子進程將會被加入到新的命名空間中
if(ret < 0){
perror("unshare failed");
}
intfpid = fork();
if(fpid < 0){
perror("fork error");
}elseif(fpid == 0){
printf("I am child process. My pid is %d\n",getpid());
// Fork后的子進程會被加入到unshare創建的命名空間中, 所以pid應該為1
exit(0);
}else{
}
waitpid(fpid,NULL,0);
/* Setns */
charpath[80] = "";
sprintf(path,"/proc/%d/ns/pid",child_pid);
intfd = open(path,O_RDONLY);
if(fd == -1)
perror("open error");
if(setns(fd,0) == -1)
// setns并不會改變當前進程的命名空間, 而是會設置之后創建的子進程的命名空間
perror("setns error");
close(fd);
intnpid = fork();
if(npid < 0){
perror("fork error");
}elseif(npid == 0){
printf("I am child process. My pid is %d\n",getpid());
// 新的子進程會被加入到第一個子進程的pid命名空間中, 所以其pid應該為2
exit(0);
}else{
}
return0;
}
運行結果:
$sudo./ns
I'mchildprocess andmy pid is1
Iam childprocess.My pid is1
Iam childprocess.My pid is2
控制組(Cgroups)
如果說命名空間是從命名和編號的角度進行隔離,而控制組則是將進程進行分組,并真正的將各組進程的計算資源進行限制、隔離。控制組是一種內核機制,它可以對進程進行分組、跟蹤限制其使用的計算資源。對于每一類計算資源,控制組通過所謂的子系統(subsystem)來進行控制,現階段已有的子系統包括:
cpusets: 用來分配一組CPU給指定的cgroup,該cgroup中的進程只等被調度到該組CPU上去執行
blkio : 限制cgroup的塊IO
cpuacct : 用來統計cgroup中的CPU使用
devices : 用來黑白名單的方式控制cgroup可以創建和使用的設備節點
freezer : 用來掛起指定的cgroup,或者喚醒掛起的cgroup
hugetlb : 用來限制cgroup中hugetlb的使用
memory : 用來跟蹤限制內存及交換分區的使用
net_cls : 用來根據發送端的cgroup來標記數據包,流量控制器(traffic controller)會根據這些標記來分配優先級
net_prio : 用來設置cgroup的網絡通信優先級
cpu :用來設置cgroup中CPU的調度參數
perf_event : 用來監控cgroup的CPU性能
與命名空間不同,控制組并沒有增加系統調用,而是實現了一個文件系統,通過文件及目錄操作來管理控制組。下面通過一個例子來看一看cgroup是如何利用cpuset子系統來把進程綁定到指定的CPU上去執行的。
1. 創建一個一直執行的shell腳本
#!/bin/bash
x=0
while[True];do
done;
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 cgroups
# mount -t cgroup -o cpuset cgroups ./cgroups/
# ls cgroups/
cgroup.clone_children cpuset.memory_pressure_enabled
cgroup.procscpuset.memory_spread_page
cgroup.sane_behaviorcpuset.memory_spread_slab
cpuset.cpu_exclusivecpuset.mems
cpuset.cpus cpuset.sched_load_balance
cpuset.effective_cpus cpuset.sched_relax_domain_level
cpuset.effective_mems docker
cpuset.mem_exclusivetasks
cpuset.mem_hardwall notify_on_release
cpuset.memory_migrate release_agent
cpuset.memory_pressure
5. 創建一個新的組group0
# mkdir group0
# ls group0/
cgroup.clone_childrencpuset.mem_exclusive cpuset.mems
cgroup.procs cpuset.mem_hardwallcpuset.sched_load_balance
cpuset.cpu_exclusive cpuset.memory_migratecpuset.sched_relax_domain_level
cpuset.cpuscpuset.memory_pressure notify_on_release
cpuset.effective_cpuscpuset.memory_spread_pagetasks
cpuset.effective_memscpuset.memory_spread_slab
6. 將上面的進程20553加入到新建的控制組中:
# echo 20553 >> group0/tasks
# cat group0/tasks
20553
7. 限制該組的進程只能運行在編號為2的CPU上
# echo 2 > group0/cpuset.cpus
# cat group0/cpuset.cpus
2
8. 查看PID為20553的進程所運行的CPU編號
# ps -eLo ruser,lwp,psr,args | grep 20553 | grep -v grep
root 20553 2bash run.sh
上面的例子簡單的展示了如何使用控制組。控制組通過文件和目錄來操作,文件系統又是樹形結構,因此如果不對cgroups的使用做一些限制的話,配置會變得異常復雜和混亂。因此,在新版的cgroups中做了一些限制。
小結
本文簡要介紹了操作系統虛擬化的概念,以及實現操作系統虛擬化的技術——命名空間及控制組。并通過兩個簡單的例子演示了命名空間及控制組的使用方法。
-
虛擬化
+關注
關注
1文章
368瀏覽量
29775 -
命名空間
+關注
關注
0文章
3瀏覽量
1842 -
控制組
+關注
關注
0文章
1瀏覽量
1250
原文標題:操作系統級虛擬化概述
文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論