如何看出网站用dede做的,wordpress兼容html,外贸尾单t恤,怎么自己做网站吗namespace 资源隔离#xff08;一#xff09;#xff1a;进行 namespace API 操作的 4 种方式 1.通过 clone() 在创建新进程的同时创建 namespace2.查看 /proc/[pid]/ns 文件3.通过 setns() 加入一个已经存在的 namespace4.通过 unshare() 在原先进程上进行 namespace 隔离5… namespace 资源隔离一进行 namespace API 操作的 4 种方式 1.通过 clone() 在创建新进程的同时创建 namespace2.查看 /proc/[pid]/ns 文件3.通过 setns() 加入一个已经存在的 namespace4.通过 unshare() 在原先进程上进行 namespace 隔离5.fork() 系统调用拓展 当谈论 Docker 时常常会聊到 Docker 的实现方式。很多开发者都知道Docker 容器本质上是宿主机上的进程容器所在的运行环境统一称为宿主机。Docker 通过
namespace 实现了
资源隔离通过
cgroups 实现了
资源限制通过写时复制机制
copy-on-write实现了
高效的文件操作。但当更进一步深入
namespace 和
cgroups 等技术细节时大部分开发者都会感到茫然无措。所以在这里希望先带领大家走进 Linux 内核了解
namespace 和
cgroups 的技术细节。 Docker 大热之后热衷技术的开发者就会思考想要实现一个资源隔离的容器应该从哪些方面下手也许第一反应就是 chroot 命令这条命令给用户最直观的感受就是在使用后根目录 / 的挂载点切换了即 文件系统 被隔离了。接着为了在分布式的环境下进行通信和定位容器必然要有独立的 IP、端口、路由等自然就联想到了 网络 的隔离。同时容器还需要一个独立的 主机名 以便在网络中标识自己。有了网络自然离不开通信也就想到了 进程间通信 需要隔离。开发者可能也已经想到了权限的问题对用户和用户组的隔离就实现了 用户权限 的隔离。最后运行在容器中的应用需要有进程号PID自然也需要与宿主机中的 PID 进行隔离。
由此基本上完成了一个容器所需要做的 6 项隔离Linux 内核中提供了这 6 种 namespace 隔离的系统调用如下表所示。当然真正的容器还需要处理许多其他工作。
namespace系统调用参数隔离内容UTSCLONE_ NEWUTS主机名与域名IPCCLONE_NEWIPC信号量、消息队列和共享内存PIDCLONE_NEWPID进程编号NetworkCLONE_NEWNET网络设备、网络栈、端口等MountCLONE_NEWNS挂载点文件系统UserCLONE_NEWUSER用户和用户组
实际上Linux 内核实现 namespace 的一个主要目的就是实现轻量级虚拟化容器服务。在同一个 namespace 下的进程可以感知彼此的变化而对外界的进程一无所知。这样就可以让容器中的进程产生错觉仿佛自己置身于一个独立的系统环境中以达到独立和隔离的目的。
需要说明的是本文所讨论的 namespace 实现针对的均是 Linux 内核 3.8 3.8 3.8 及以后的版本user namespace 在内核 3.8 3.8 3.8 版本以后才支持。接下来将首先介绍使用 namespace 的 API然后对这 6 种 namespace 进行逐一讲解并通过程序让读者切身感受隔离效果。
namespace 的 API 包括 clone()、setns() 以及unshare()还有 /proc 下的部分文件。为了确定隔离的到底是哪 6 项 namespace在使用这些 API 时通常需要指定以下 6 个参数中的一个或多个通过 |位或操作来实现。从上表可知这 6 个参数分别是 CLONE_NEWIPC、CLONE_NEWNS、CLONE_NEWNET、CLONE_NEWPID、CLONE_NEWUSER 和 CLONE_NEWUTS。
1.通过 clone() 在创建新进程的同时创建 namespace
使用 clone() 来创建一个独立 namespace 的进程是最常见的做法也是 Docker 使用 namespace 最基本的方法它的调用方式如下。
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);clone() 实际上是 Linux 系统调用 fork() 的一种更通用的实现方式它可以通过 flags 来控制使用多少功能。一共有 20 多种 CLONE_* 的 flag标志位参数用来控制 clone 进程的方方面面如是否与父进程共享虚拟内存等下面挑选与 namespace 相关的 4 个参数进行说明。
child func 传入子进程运行的程序主函数。child stack 传入子进程使用的栈空间。flags 表示使用哪些 CLONE_* 标志位与 namespace 相关的主要包括 CLONE_NEWIPC、CLONE_NEWNS、CLONE_NEWNET、CLONE_NEWPID、CLONE_NEWUSER 和 CLONE_NEWUTS。args 则可用于传入用户参数。
2.查看 /proc/[pid]/ns 文件
从 3.8 3.8 3.8 版本的内核开始用户就可以在 /proc/[pid]/ns 文件下看到指向不同 namespace 号的文件效果如下所示形如 [4026531839] 者即为 namespace 号。
$ ls -l /proc/$$/ns --$$是shell中表示当前运行的进程ID号
total 0
lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 ipc - ipc:[4026531839]
lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 mnt - mnt:[4026531840]
lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 net - net:[4026531956]
lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 pid - pid:[4026531836]
lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 user-user:[4026531837]
lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 uts - uts:[4026531838]如果两个进程指向的 namespace 编号相同就说明它们在同一个 namespace 下否则便在不同 namespace 里面。/proc/[pid]/ns 里设置这些 link 的另外一个作用是一旦上述 link 文件被打开只要打开的 文件描述符fd存在那么就算该 namespace 下的所有进程都已经结束这个 namespace 也会一直存在后续进程也可以再加入进来。在 Docker 中通过文件描述符定位和加入一个存在的 namespace 是最基本的方式。
另外把 /proc/[pid]/ns 目录文件使用 --bind 方式挂载起来可以起到同样的作用命令如下
# touch ~/uts
# mount --bind /proc/27514/ns/uts ~/uts为了方便起见后面的讲解中会使用这个 ~/uts 文件来代替 /proc/27514/ns/uts。
注意如果大家看到 ns 下的内容与本节所述不符那可能是因为使用了 3.8 3.8 3.8 以前版本的内核。如在内核版本 2.6 2.6 2.6 中该目录下存在的只有 ipc、net 和 uts并且以硬链接方式存在。
3.通过 setns() 加入一个已经存在的 namespace
上文提到在进程都结束的情况下也可以通过挂载的形式把 namespace 保留下来保留 namespace 的目的是为以后有进程加入做准备。在 Docker 中使用 docker exec 命令在已经运行着的容器中执行一个新的命令就需要用到该方法。通过 setns() 系统调用进程从原先的 namespace 加入某个已经存在的 namespace使用方法如下。通常为了不影响进程的调用者也为了使新加入的 pid namespace 生效会在 setns() 函数执行后使用 clone() 创建子进程继续执行命令让原先的进程结束运行。
int setns(int fd, int nstype);参数 fd 表示要加入 namespace 的文件描述符。上文提到它是一个指向 /proc/[pid]/ns 目录的文件描述符可以通过直接打开该目录下的链接或者打开一个挂载了该目录下链接的文件得到。参数 nstype 让调用者可以检查 fd 指向的 namespace 类型是否符合实际要求。该参数为 0 表示不检查。
为了把新加入的 namespace 利用起来需要引入 execve() 系列函数该函数可以执行用户命令最常用的就是调用 /bin/bash 并接受参数运行起一个 shell用法如下。
fd open(argv[1], O_RDONLY); /* 获取 namespace 文件描述符 */
setns(fd, 0); /* 加入新的 namespace */
execvp(argv[2], argv[2]); /* 执行程序 */假设编译后的程序名称为 setns-test。
# ./setns-test ~/uts /bin/bash # ~/uts 是绑定的 /proc/27514/ns/uts至此就可以在新加入的 namespace 中执行 shell 命令了下文会多次使用这种方式来演示隔离的效果。
4.通过 unshare() 在原先进程上进行 namespace 隔离
最后要说明的系统调用是 unshare()它与 clone() 很像不同的是unshare() 运行在原先的进程上不需要启动一个新进程。
int unshare(int flags);调用 unshare() 的主要作用就是不启动新进程就可以起到隔离的效果相当于跳出原先的 namespace 进行操作。这样就可以在原进程进行一些需要隔离的操作。Linux 中自带的 unshare 命令就是通过 unshare() 系统调用实现的。Docker 目前并没有使用这个系统调用这里不做展开读者可以自行查阅资料学习该命令的知识。
5.fork() 系统调用拓展
系统调用函数 fork() 并不属于 namespace 的 API这部分内容属于延伸阅读如果读者已经对 fork() 有足够多的了解可以忽略该部分。
当程序调用 fork() 函数时系统会创建新的进程为其分配资源例如存储数据和代码的空间然后把原来进程的所有值都复制到新进程中只有少量数值与原来的进程值不同相当于复制了本身。那么程序的后续代码逻辑要如何区分自己是新进程还是父进程呢?
fork() 的神奇之处在于它仅仅被调用一次却能够返回两次父进程与子进程各返回一次通过返回值的不同就可以区分父进程与子进程。它可能有以下 3 种不同的返回值
在父进程中fork() 返回新创建子进程的进程 ID在子进程中fork() 返回 0如果出现错误fork() 返回一个负值。
下面给出一段实例代码命名为 fork_example.c。
#include unistd.h
#include stdio.h
int main (){pid_t fpid; // fpid 表示 fork 函数返回的值int count0;fpidfork();if (fpid 0) printf(error in fork!);else if (fpid 0) {printf(I am child. Process id is %d\n,getpid());}else {printf(i am parent. Process id is %d\n,getpid());} return 0;
}编译并执行结果如下。
[rootlocal:~#] gcc -Wall fork_example.c ./a.out
I am parent. Process id is 28365
I am child. Process id is 28366代码执行过程中在语句 fpidfork() 之前只有一个进程在执行这段代码在这条语句之后就变成父进程和子进程同时执行了。这两个进程几乎完全相同将要执行的下一条语句都是 if (fpid 0)同时 fpidfork() 的返回值会依据所属进程返回不同的值。
使用 fork() 后父进程有义务监控子进程的运行状态并在子进程退出后自己才能正常退出否则子进程就会成为 “孤儿” 进程。 后续将根据 Docker 内部对 namespace 资源隔离使用的方式分别对 6 种 namespace 进行详细的解析。