Docker 的本质是使用 LXC 实现类似虚拟机的功能,进而节省的硬件资源提供给用户更多的计算资源。本项目将 C++ 与 Linux 的 Namespace 及 Control Group 技术相结合,实现一个简易 Docker 容器。
最终效果 最后我们将为容器实现下面这些功能(欢迎点赞关注,后面内容更精彩):

1、独立的文件系统
2、网络访问的支持
3、容器资源的限制

本期目录

一、Linux Namespace技术

二、Clone系统调用

三、其他函数


一、Linux Namespace技术

  • 在 C++ 中,我们知道有 namespace 这个关键字。在 C++ 中,每一个 namespace 对不同代码的相同名称进行了隔离,只要 namespace 的名称不同,就能够让 namespace 中的代码名称相同,从而解决了代码名称冲突的问题。

  • 而 Linux Namespace 则是 Linux 内核提供的一种技术,它为应用程序提供了一种资源隔离的方案,和 C++ 中的 namespace 有异曲同工之妙。我们知道,PID、IPC、网络等系统资源本应该属于操作系统本身进行管理,但 Linux Namespace 则可以让这些资源的全局性消失,让一部分属于某个特定的 Namespace。

  • 在 Docker 技术中,我们时常听说 LXC、操作系统级虚拟化这些名词,而 LXC 就是利用了 Namespace 这种技术实现了不同容器之前的资源隔离。利用 Namespace 技术,不同的容器内进程属于不同的 Namespace,彼此不相干扰。总的来说,Namespace 技术,提供了虚拟化的一种轻量级形式,使我们可以从不同方面来运行系统全局属性。

  • 在 Linux 中,和 Namespace 相关的系统调用最重要的就是 clone()。 clone() 的作用是在创建进程时,将线程限制在某个 Namespace 中。
    返回顶部目录


二、Clone系统调用

* clone 的函数原型如下:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

fork 在创建一个进程的时候,子进程会完全复制父进程的资源。而 clone 则比 fork 更加强大,因为 clone 可以有选择性的将父进程的资源复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享(arg),具体要复制的资源,则可以通过 flags 进行指定,并返回子进程的 PID。

  • 进程四大要素:
    1、一段需要执行的程序
    2、进程自己的专用堆栈空间
    3、进程控制块(PCB)
    4、进程专有的 Namespace
    前两点要素在 clone 中对应的参数很明显,分别为 fn 和 child_stack。而对于进程控制块来说,由内核控制我们不需要关心,因此,Namespace 就落在了 flags 的身上。我们要实现一个满足我们之前所定目标的 Docker 容器,需要的主要参数如下:

    Namespace 分类系统调用参数备注
    UTSCLONE_NEWUTS供了主机名相关的设置
    MountCLONE_NEWNS提供了文件系统相关的挂载,用于复制和文件系统相关的资源
    PIDCLONE_NEWPID提供了独立的进程空间支持
    NetworkCLONE_NEWNET提供了网络相关支持

返回顶部目录


三、其他函数

  • execv()

    int execv(const char *path, char *const argv[]);
    

    execv 可以通过传入一个 path 来执行 path 中的执行文件,这个系统调用可以让我们的子进程执行 /bin/bash 从而让整个容器保持运行。

  • sethostname

    int sethostname(const char *name, size_t len);
    

    从名字不难看出,这个系统调用能够设置我们的主机名,值得一提的是,由于 C 风格的字符串使用的是指针,不指定长度是无法直接从内部得知字符串长度的,这里 len 起到了获得字符串长度的作用。

  • chdir

    int chdir(const char *path);
    

    我们知道,任何一个程序,都会在某个特定的目录下运行。当我们需要访问资源时,就可以通过相对路径而不是绝对路径来访问相关资源。而 chdir 恰好提供给了我们一个便利之处,那就是可以改变我们程序的运行目录,从而达到某些不可描述的目的。

  • chroot

    int chroot(const char *path);
    

    这个系统调用能够用于设置根目录

  • mount

    int mount(const char *source, const char *target,
                     const char *filesystemtype, unsigned long mountflags,
                     const void *data);
    

    这个系统调用用于挂载文件系统,和 mount 这个命令能够达到相同的目的。

返回顶部目录

下期预告:使用namespace进行资源隔离

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐