命令空间相关API
命名空间
命名空间是一项 Linux 内核功能,于 2002 年随 Linux 2.4.19 引入。命名空间背后的想法是将特定的全局系统资源包装在抽象层中。内核命名空间抽象,允许不同的进程组具有不同的系统视图。这使得命名空间内的进程看起来拥有自己的全局资源的隔离实例。
目前 Linux 实现了 8 个不同的命名空间:mnt、pid、net、ipc、uts、user、cgroup、time,这8个命名空间并非从一开始就实现的,而是在不断更新的 Linux 内核版本中逐渐实现的。
Linux 所有 8 种命名空间及其作用
从 Linux 内核 5.6 开始,共有 8 种命名空间,每种负责隔离一种特定的系统资源。下表详细说明:
| 命名空间类型 | 对应的 clone() 标志 | 对应的 unshare 选项 | 作用(隔离的资源) | 典型场景 |
|---|---|---|---|---|
| PID(进程 ID) | CLONE_NEWPID | -p 或 --pid | 隔离进程 ID 编号空间。每个命名空间内的进程有独立的 PID 序列(如各自的 PID=1),且看不到其他命名空间的进程。 | 容器内的进程 PID 与主机隔离(如容器内的 init 进程为 PID=1)。 |
| Mount(挂载) | CLONE_NEWNS | -m 或 --mount | 隔离文件系统挂载点。不同命名空间的进程看到的挂载点(文件系统结构)可以不同。 | 容器拥有独立的根文件系统(如 / 目录与主机不同)。 |
| UTS(主机名/域名) | CLONE_NEWUTS | -u 或 --uts | 隔离主机名和域名。每个命名空间可以设置独立的 hostname。 | 容器可以有自己的主机名(如 docker run --hostname mycontainer)。 |
| IPC(进程间通信) | CLONE_NEWIPC | -i 或 --ipc | 隔离 System V IPC 和 POSIX 消息队列。不同命名空间的进程无法通过 IPC 直接通信。 | 避免容器内进程与主机或其他容器的 IPC 冲突。 |
| Network(网络) | CLONE_NEWNET | -n 或 --net | 隔离网络资源(网卡、IP、端口、路由表、防火墙规则等)。每个命名空间有独立的网络栈。 | 容器有独立的虚拟网卡和 IP(如 Docker 的桥接网络)。 |
| User(用户/组 ID) | CLONE_NEWUSER | -U 或 --user | 隔离用户 ID(UID)和组 ID(GID)。命名空间内的 UID/GID 可以与外部不同(如容器内的 root 对应主机的普通用户)。 | 容器内的 root 用户在主机上无特权,提高安全性。 |
| Cgroup(控制组) | CLONE_NEWCGROUP | -c 或 --cgroup | 隔离 cgroup 视图。进程只能看到自己所在的 cgroup 层级,无法访问其他命名空间的 cgroup 资源。 | 限制容器对系统资源(CPU、内存)的使用范围。 |
| Time(时间) | CLONE_NEWTIME | --time | 隔离系统时间(包括时钟和计时器)。可以在命名空间内修改时间而不影响主机。 | 测试依赖时间的程序(如日志、证书过期)时,避免影响主机时间。 |
1. clone():创建新进程时“顺带”创建新命名空间
clone() 是最“主动”的一个——它的核心功能是创建一个新进程,但可以在创建时为这个新进程单独创建一个或多个命名空间,让新进程“活”在这些新命名空间里,与父进程的命名空间隔离。
关键特点:
- 必须创建新进程:
clone()本质是进程创建函数(类似fork(),但更灵活); - 新命名空间是“全新的”:新进程会进入这些新创建的命名空间,而父进程仍在原来的命名空间中;
- 通过标志指定命名空间类型:需要传递命名空间相关的标志(如
CLONE_NEWNS对应挂载命名空间,CLONE_NEWPID对应 PID 命名空间等),每个标志对应一种命名空间。
举例说明:
假设父进程在“默认命名空间”中,现在用 clone() 创建子进程,并指定 CLONE_NEWPID 标志(创建新的 PID 命名空间):
- 子进程被创建,同时内核为它新建一个 PID 命名空间;
- 子进程在这个新的 PID 命名空间中,它的 PID 会被分配为 1(因为每个 PID 命名空间的第一个进程都是 PID=1);
- 父进程仍在原来的命名空间中,看到的子进程 PID 是另一个数值(比如 1234);
- 子进程在自己的 PID 命名空间中看不到父进程的其他子进程,只能看到自己命名空间内的进程。
与 fork() 的区别:
fork() 创建的子进程会继承并共享父进程的所有命名空间(即和父进程在同一个命名空间);而 clone() 可以通过标志让子进程使用全新的命名空间,实现隔离。
2. unshare():让当前进程“脱离”共享,创建自己的命名空间
unshare() 的作用是让当前进程退出与其他进程共享的命名空间,并为自己创建一个新的命名空间(注意:不创建新进程,操作的是当前进程)。
关键特点:
- 不创建新进程:在当前进程中直接操作,修改当前进程的命名空间归属;
- “复制后独立”:解除共享时,内核会先复制一份当前命名空间的资源(比如挂载点、网络配置等),然后让当前进程使用这个复制的副本,之后当前进程对资源的修改不会影响原来的命名空间;
- 场景:适用于当前进程需要“独立”出来,单独管理资源的情况(比如容器初始化时,让进程自己隔离出一个新的挂载命名空间)。
举例说明:
假设进程 A 原本和进程 B 共享同一个挂载命名空间(即它们看到的文件系统挂载点完全一样):
- 进程 A 调用
unshare(CLONE_NEWNS)(CLONE_NEWNS对应挂载命名空间); - 内核会复制一份当前的挂载点列表,作为进程 A 的新挂载命名空间;
- 进程 A 之后执行
mount /dev/sdb1 /mnt挂载操作,只会影响自己的新命名空间,进程 B 看不到/mnt的新挂载; - 进程 B 对挂载点的修改,也不会影响进程 A。
对应命令工具:unshare
Linux 提供了 unshare 命令(对应 unshare() 系统调用),可以在命令行中让进程“脱离共享”。比如:
# 用 unshare 命令创建一个新的挂载命名空间,并在其中执行 bash
unshare --mount --fork /bin/bash
执行后,这个 bash 进程就有了自己的挂载命名空间,在里面挂载的目录不会影响外部系统。
3. setns():让当前进程“加入”已有的命名空间
setns() 是“逆向操作”——它允许当前进程离开自己的命名空间,加入到另一个已存在的命名空间中(前提是能访问目标命名空间的文件描述符)。
关键特点:
- 加入已有命名空间:不是创建新的,而是“融入”一个已存在的命名空间;
- 需要文件描述符:目标命名空间的标识通过文件描述符传递(通常来自
/proc/[进程ID]/ns/目录下的文件,比如/proc/1234/ns/pid表示进程 1234 所在的 PID 命名空间); - 场景:用于“进入”其他进程的命名空间(比如调试容器内的进程,查看容器的网络配置等)。
举例说明:
假设系统中有一个容器进程 C(PID=5678),它运行在自己的网络命名空间中(有独立的网卡 eth0):
- 我们的调试进程 D 想查看容器 C 的网络配置,需要加入 C 的网络命名空间;
- 进程 D 先打开容器 C 的网络命名空间文件:
fd = open("/proc/5678/ns/net", O_RDONLY); - 调用
setns(fd, 0),将自己加入到这个网络命名空间; - 之后进程 D 执行
ifconfig或ip addr,看到的就是容器 C 内的网络接口(比如eth0的 IP 是容器内的地址),而不是主机的网络。
对应命令工具:nsenter
nsenter 命令基于 setns() 实现,用于“进入”其他进程的命名空间执行命令。比如:
# 进入 PID=5678 进程的网络命名空间,执行 ip addr 查看网络
nsenter --net=/proc/5678/ns/net ip addr
总结:三个系统调用的核心区别
| 系统调用 | 核心操作 | 是否创建新进程 | 命名空间来源 | 典型场景 |
|---|---|---|---|---|
clone() | 创建新进程 + 新命名空间 | 是 | 全新创建 | 启动新容器(如创建隔离的子进程) |
unshare() | 当前进程创建新命名空间 | 否 | 复制原命名空间后独立 | 让当前进程“隔离”出独立资源 |
setns() | 当前进程加入已有命名空间 | 否 | 其他进程已有的命名空间 | 调试/访问其他命名空间(如容器) |
通过这三个系统调用,Linux 实现了命名空间的创建、隔离和切换,是容器技术(如 Docker、Kubernetes)实现资源隔离的核心底层支撑。理解它们的区别,就能明白容器内的进程如何被“隔离”,以及外部如何与容器内的资源交互。
1082

被折叠的 条评论
为什么被折叠?



