文档章节

操作系统级虚拟化概述

宅蓝三木
 宅蓝三木
发布于 01/05 15:22
字数 2202
阅读 1214
收藏 49
点赞 3
评论 3

操作系统级虚拟化

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
lrwxrwxrwx 1 kelvin kelvin 0 12月 26 16:28 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 kelvin kelvin 0 12月 26 16:28 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 kelvin kelvin 0 12月 26 16:28 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 kelvin kelvin 0 12月 26 16:28 net -> net:[4026531963]
lrwxrwxrwx 1 kelvin kelvin 0 12月 26 16:28 pid -> pid:[4026531836]
lrwxrwxrwx 1 kelvin kelvin 0 12月 26 16:28 user -> user:[4026531837]
lrwxrwxrwx 1 kelvin kelvin 0 12月 26 16:28 uts -> uts:[4026531838]

下面的代码演示了如何利用上述3个系统调用来操作进程的命名空间:

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define STACK_SIZE (10 * 1024 * 1024)

char child_stack[STACK_SIZE];

int child_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将会被删除, 为后续操作保留它
    return 0;
}

int main() {
    /* Clone */
    pid_t child_pid = clone(child_main, child_stack + STACK_SIZE, \
        CLONE_NEWPID | SIGCHLD, NULL);
    if(child_pid < 0) {
        perror("clone failed");
    }

    /* Unshare */
    int ret = unshare(CLONE_NEWPID); // 父进程调用unshare, 创建了一个新的命名空间,
    //但不会创建子进程. 之后再创建的子进程将会被加入到新的命名空间中
    if (ret < 0) {
        perror("unshare failed");
    }
    int fpid = fork();
    if (fpid < 0) {
        perror("fork error");
    } else if (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 */
    char path[80] = "";
    sprintf(path, "/proc/%d/ns/pid", child_pid);
    int fd = open(path, O_RDONLY);
    if (fd == -1)
        perror("open error");
    if (setns(fd, 0) == -1)
    // setns并不会改变当前进程的命名空间, 而是会设置之后创建的子进程的命名空间
        perror("setns error");
    close(fd);

    int npid = fork();
    if (npid < 0) {
        perror("fork error");
    } else if (npid == 0) {
        printf("I am child process. My pid is %d  \n", getpid());
        // 新的子进程会被加入到第一个子进程的pid命名空间中, 所以其pid应该为2
        exit(0);
    } else {
    }
    return 0;
}

运行结果:

$ sudo ./ns
I'm child process and my pid is 1 
I am child process. My pid is 1  
I am child process. My pid is 2 

 

控制组(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   3 bash 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.procs            cpuset.memory_spread_page
cgroup.sane_behavior    cpuset.memory_spread_slab
cpuset.cpu_exclusive    cpuset.mems
cpuset.cpus             cpuset.sched_load_balance
cpuset.effective_cpus   cpuset.sched_relax_domain_level
cpuset.effective_mems   docker
cpuset.mem_exclusive    tasks
cpuset.mem_hardwall     notify_on_release
cpuset.memory_migrate   release_agent
cpuset.memory_pressure

5. 创建一个新的组group0

# mkdir group0
# ls group0/
cgroup.clone_children  cpuset.mem_exclusive       cpuset.mems
cgroup.procs           cpuset.mem_hardwall        cpuset.sched_load_balance
cpuset.cpu_exclusive   cpuset.memory_migrate      cpuset.sched_relax_domain_level
cpuset.cpus            cpuset.memory_pressure     notify_on_release
cpuset.effective_cpus  cpuset.memory_spread_page  tasks
cpuset.effective_mems  cpuset.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   2 bash run.sh

上面的例子简单的展示了如何使用控制组。控制组通过文件和目录来操作,文件系统又是树形结构,因此如果不对cgroups的使用做一些限制的话,配置会变得异常复杂和混乱。因此,在新版的cgroups中做了一些限制。

小结

本文简要介绍了操作系统虚拟化的概念,以及实现操作系统虚拟化的技术——命名空间及控制组。并通过两个简单的例子演示了命名空间及控制组的使用方法。

阅读原文

© 著作权归作者所有

共有 人打赏支持
宅蓝三木
粉丝 33
博文 52
码字总数 57356
作品 0
海淀
后端工程师
加载中

评论(3)

大连馋师
大连馋师
留爪~
宅蓝三木
宅蓝三木

引用来自“Fenying”的评论

好文,收藏。
:smile:
Fenying
Fenying
好文,收藏。
X86虚拟化之三种服务器虚拟化战略架构

厚朴〖HOPE〗工作室 牵犁拈断眉毛结, 坐石蹴开足蔓缠。 戴月荷锄同一印, 耳根常寂漱鸣泉。 ©中山大学化学与化学工程学院 E-mail:cei@mail.sysu.edu.cn Tel: 020-84115662 总访问量:64418...

大侠杨六
2011/09/01
0
1
读书笔记1---为什么使用Docker

Docker在开发与运维中优势 a) 更快速的交付和部署—使用Docker,开发人员可以使用镜像来构建一套标准的开发环境,测试和运维则可以直接使用。Docker可以快速创建和删除容器,实现快速迭代,大...

makeths
2017/11/13
0
0
Linux中虚拟化方法、技术及实现(二)

QEMU 是另外一个仿真器,它与 Bochs 非常类似,不过也有一些值得一提的区别。QEMU 支持两种操作模式。第一种是 Full System Emulation(完全系统仿真)模式。这种模式与 Bochs 非常类似,它可...

范堡
2009/05/07
415
0
虚拟化概述及VMware VSphere介绍

虚拟化概述及VMware VSphere介绍: 虚拟化打破了物理硬件与操作系统及在其上运行的应用程序之间的硬性连接。操作系统和应用程序在虚拟机中实现虚拟化之后,便不再因位于单台物理计算机中而受...

甘兵
2013/08/07
0
0
虚拟化技术经验分享

云计算与融合 近几年来,“云计算”与“融合”等概念炒得沸沸扬扬,其相关技术非常值得我们去进行关注。 就我的理解而言,数据中心要实现云计算,虚拟化无疑是一个重要的基础,但虚拟化本身并...

周志超
2014/07/07
0
0
VSphere入门之ESXi的安装及基本管理

虚拟化和云计算技术正在快速的发展,新的概念、观点、产品不断涌现。服务器虚拟化技术受到了人们的高度重视,普遍相信虚拟化将成为数据中心的重要组成部分。vSphere是VMware公司推出的一套服...

杨书凡
2017/09/27
0
0
初识容器与Docker

### 这周学了虚拟化的概念,了解了虚拟化的分类:模拟,完全虚拟化,半虚拟化(准虚拟化),库级别的虚拟化,应用虚拟化,容器级虚拟化(操作系统级别虚拟化)等 #### 认真了解了Docker的诞生...

年年歳歲
2017/05/01
0
0
总结:主流x86虚拟机技术分析介绍

虚拟计算机技术是近两年来比较火爆的技术之一,已经受到了越来越多的企业和媒体的关注,时间跨进了2006年,虚拟机的热潮更是凶猛扑来。然而,从早期的概念的虚拟机出现,到现代x86虚拟机的流...

老枪
2009/04/17
694
1
基于VMware vSphere 5 企业虚拟化部署之一:企业虚拟化概述

基于VMware vSphere 5 企业虚拟化部署之一:企业虚拟化概述 作者:杨坚 一、什么是虚拟化 看该文章读者大部分是计算机玩家,但如果你问“什么是虚拟化”,我想大部分人的回答都会是“就是一个...

yaabb163
06/26
0
0
KVM 虚拟化技术之Hypervisor的实现

KVM 虚拟化技术之Hypervisor的实现 VMM(VirtualMachineMonitor)对物理资源的虚拟可以划分为三个部分: CPU虚拟化、内存虚拟化和I/O设备虚拟化,其中以CPU的虚拟化最为关键。经典的虚拟化方法:...

lcdkvm
2016/01/28
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Fetch API

Fetch和XMLHttpRequest 如果看网上的fetch教程,会首先对比XMLHttpRequest和fetch的优劣,然后引出一堆看了很快会忘记的内容(本人记性不好)。因此,我写一篇关于fetch的文章,为了自己看着方...

JamesView
5分钟前
0
0
用 Python 实现打飞机,让子弹飞吧!

所用技术和软件 python 2.7 pygame 1.9.3 pyCharm 准备工作 安装好 pygame 在第一次使用 pygame 的时候,pyCharm 会自动 install pygame。 下载好使用的素材。 技术实现 初始化 pygame 首先要...

猫咪编程
13分钟前
0
0
MySQL的行锁和表锁

简单总结一下行锁和表锁。 行锁 每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 表锁 每次操作锁住整张表。开销小,加锁快;不会出...

to_ln
15分钟前
0
0
Java IO类库之字节数组输入流ByteArrayInputStream

一、ByteArrayInputStream字节数组输入流介绍 ByteArrayInputStream是字节数组输入流,继承自InputStream。它的内部包含一个缓冲区,是一个字节数组,缓冲数组用于保存从流中读取的字节数据,...

老韭菜
17分钟前
0
0
iOS安全应该做哪些事情

1. 尽量使用HTTPS协议。 2. 密码提交的时候,密码使用SHA256加密后传输,MD5等经过哈希碰撞已经可以推算出原文。 3. 密码提交的时候,可以加盐。 4. 密码保存在本地的时候,尽量使用钥匙串保...

HOrange
23分钟前
0
0
react native 注意事项

1. 环境参考官网 android studio 必装 java jdk安装 1.8版本(环境建议自己一步一步配置,切记不要 apt ) 2.有改变编译内容发现 会白屏,然后APP消失,请卸载原来的测试 appinfo (连续两次...

304158
29分钟前
0
0
FOMO游戏代码解析

源代码在此处

怎当她临去时秋波那一转
34分钟前
1
0
EOS智能合约与DApp开发入门

EOS的是Block.One主导研发的一个区块链底层公链系统,它专门为支撑商业去中心化 应用(Decentralized Application)而设计,其代码开源。 比特币被称为区块链1.0,因为它开辟了数字加密货币的...

笔阁
47分钟前
1
0
编译cjson到dll

https://blog.csdn.net/mengzhisuoliu/article/details/52203724 编译完成后 是纯lua实现的json decode 的10倍以上...

梦想游戏人
57分钟前
0
0
JS基础- Date 对象

Date 对象 Date 对象用于处理日期和时间。 创建 Date 对象的语法: var myDate=new Date() 注释:Date 对象会自动把当前日期和时间保存为其初始值。 Date 对象属性 属性 描述 constructor 返...

ZHAO_JH
59分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部