SerenityOS 内核容器机制深度解析:VFS 根上下文、进程列表与 Hostname 隔离
2026/9/11 4:19:21 网站建设 项目流程

SerenityOS 内核容器机制深度解析:VFS 根上下文、进程列表与 Hostname 隔离

【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity

本指南以 Documentation/Kernel/Containers.md 为核心脉络,系统讲解 SerenityOS 内核如何通过 VFS 根上下文(VFS Root Context)、作用域进程列表(Scoped Process List)与主机名上下文(Hostname Context)三类资源实现进程级隔离,并完整梳理unshare_open/unshare_create/unshare_enter三条系统调用及 Jail 安全机制的内核实现与用户态调用链。读完本文,你将掌握 SerenityOS 容器从资源创建、进程进入、exec 切换直到 jail 强制的完整生命周期,并能对照runc实用工具读懂真实容器配置文件的部署流程。

什么是容器:SerenityOS 中的“概念性建构”

在 SerenityOS 生态中,容器(Container)被定义为一种概念性建构(conceptual establishment):它并不对应某个单一内核对象,而是通过一组对外暴露的、不共享的资源(unshared resources)将用户程序彼此隔离。这些资源包括但不限于:

  • PID 视图(process list)——进程只能看到与自己同属一个作用域的进程;
  • 文件系统视图(filesystem view)——进程只能看到自己根上下文下的挂载树;
  • 主机名(hostname)——一组进程共享一个独立的主机名命名空间。

这种“组合多种隔离维度、叠加为一个容器”的设计,与 Linux 中 namespace 的思想同源但实现独立,是 SerenityOS 内核在 Kernel/ 目录下自研的机制,而非对 Linux 代码的移植。

内核侧的三种隔离机制

当前内核(Kernel/)对外暴露三类可能的隔离机制,分别由独立的引用计数对象与全局链表管理:

VFS 根上下文(VFS Root Contexts)

VFS(虚拟文件系统)根上下文是每个进程持有的、用于“观察一棵文件系统树”的上下文。一个 VFS 根上下文持有两部分关键数据:

  • 挂载表(mount table):该上下文内的全部挂载关系;
  • 根目录 Custody:上下文的根目录句柄。

用户进程既可以持有所有默认程序共享的全局上下文,也可以持有特殊上下文来限制自身的文件系统视图。

从源码看,该机制实现在 Kernel/FileSystem/VFSRootContext.h:

  • 通过create_with_empty_ramfs(...)/create_with_filesystem(...)工厂方法创建,并可通过AddToGlobalContextList枚举控制是否加入全局上下文链表;
  • 每个上下文拥有独立的IndexIDAK_TYPEDEF_DISTINCT_ORDERED_ID(u64, IndexID))作为全局索引;
  • 提供root_custody()add_new_mount()unmount()等接口管理根目录与挂载点。

文档明确其生命周期规则:VFS 根上下文挂接在全局链表中,当其最后一个挂载(即根挂载)被卸载时,才会从全局链表中移除。这意味着只要根文件系统还挂着,即使没有进程引用该上下文,它依然存活在全局链表中,可供后续unshare_enter按 id 重新进入。

进程列表(Process lists)

进程列表分为两类:

  • 全局进程列表:所有进程默认挂接其上;
  • 作用域进程列表(scoped process list):由用户进程显式创建并持有引用。

当进程持有某个作用域进程列表的引用后,它只能看到位于同一列表上的其他进程,从而实现对 PID 视图的隔离。

实现见 Kernel/Tasks/ScopedProcessList.h:

  • 基于AtomicRefCounted的引用计数对象,内部以IntrusiveList维护attached_processes()集合;
  • 拥有独立IndexID与全局链表节点m_list_node
  • 通过attach(Process&)/detach(Badge<Process>)维护成员关系,m_attach_count记录附着进程数。

其生命周期规则与主机名上下文一致:作用域进程列表挂接在全局链表上,当最后一个仍附着于该列表的进程与之分离时,列表才从全局链表中移除

主机名上下文(Hostname contexts)

主机名上下文让一组用户进程共享一个自定义的主机名。任何一个持有该上下文引用的进程都可以修改主机名,且修改会立即反映给所有附着在该上下文上的其他进程,从而实现主机名维度的“命名空间”隔离。

实现见 Kernel/Tasks/HostnameContext.h:

  • 同样基于AtomicRefCounted,通过create_initial()创建内核初始上下文、create_with_name(StringView)创建命名上下文;
  • 主机名缓冲区为FixedStringBuffer<UTSNAME_ENTRY_LEN - 1>(受 POSIXstruct utsname条目长度约束),并用RecursiveSpinlockProtected保护并发访问;
  • 拥有独立IndexID、全局链表节点与m_attach_count引用计数。

生命周期规则:上下文挂接在全局链表中,当最后一个附着进程分离时才从链表移除

内核与用户空间的三条接口

内核通过 3 条系统调用完成资源隔离的“创建—初始化—进入”三步操作,声明位于 Kernel/API/Syscall.h(第 195~197 行)并注册为NeedsBigProcessLock::No

系统调用参数结构体职责
unshare_openSC_unshare_open_params { int type; }为一种新的非共享资源创建文件描述符,返回可操作的 fd
unshare_createSC_unshare_create_params { int unshared_resource_fd; }真正创建非共享资源,返回资源的索引号(id)
unshare_enterSC_unshare_enter_params { int type; int id; int flags; }使当前进程按资源索引进入(附着到)指定资源

资源类型与进入标志

类型与标志定义于 Kernel/API/Unshare.h:

enum class UnshareType { ScopedProcessList = 1, VFSRootContext = 2, HostnameContext = 3, }; enum class UnshareEnterFlags : u32 { None = 0, CurrentProgram = 1 << 0, // 立即生效 AfterExec = 1 << 1, // 在下次 exec 时生效 };

UnshareEnterFlags的两个标志值得重点说明:

  • CurrentProgram(立即生效):调用后当前进程立即替换/附着到新资源;
  • AfterExec(exec 后生效):资源先被记录到进程的m_exec_*槽位,待下次exec时统一替换。内核实现中,Process维护了m_exec_vfs_root_contextm_exec_hostname_contextm_exec_scoped_process_list三个AfterExecResource槽位(见 Kernel/Tasks/Process.h),并在execve系统调用路径中通过replace_resource一次性应用(见 Kernel/Syscalls/execve.cpp 第 636~638 行)。这保证容器入口程序启动瞬间“原子”切换到隔离环境中。

内核实现细节

三条系统调用的完整实现位于 Kernel/Syscalls/unshare.cpp,其公共前置条件为:

  1. 调用进程必须持有pledge("unshare")承诺(require_promise(Pledge::unshare));
  2. 调用进程必须为超级用户credentials->is_superuser()),否则返回EPERM
  3. 若进程已处于jail状态(is_jailed()),直接返回EPERM——jail 与资源切换互斥。

unshare_open:根据params.type分派到ScopedProcessList/VFSRootContext/HostnameContext三种类型,创建对应的 Kernel/FileSystem/UnsharedResourceFile.h(一个不提供读写、仅作为资源句柄的特殊File子类),随后在进程 fd 表中分配新 fd 并设置FD_CLOEXEC。非法类型返回ENOTSUP,负数类型返回EINVAL

unshare_create:通过 fd 找回对应的UnsharedResourceFile(校验is_unshared_resource_file(),否则EINVAL),然后调用其initialize_resource()。该方法(见 Kernel/FileSystem/UnsharedResourceFile.cpp)按类型完成资源实体创建并返回索引号:

  • ScopedProcessListScopedProcessList::create()
  • VFSRootContext:若此前已通过 ioctl 附加了根文件系统,则VFSRootContext::create_with_filesystem(...),否则以空的 ramfs创建create_with_empty_ramfs(...),均加入全局上下文链表;
  • HostnameContext:基于进程当前附着上下文的内容派生新上下文。

unshare_enter:要求flags != None(否则EINVAL),然后按params.type通过*_for_id(params.id)从全局链表查找资源,再根据CurrentProgram/AfterExec标志分别替换当前上下文或写入m_exec_*槽位。

用户态封装

LibCore为应用开发者提供了同名的 C++ 封装,见 Userland/Libraries/LibCore/System.cpp:

ErrorOr<unsigned> unshare_open(Kernel::UnshareType type); // 返回 fd ErrorOr<u32> unshare_create(unsigned fd); // 返回资源索引号 ErrorOr<void> unshare_enter(Kernel::UnshareType type, unsigned index, int flags);

三个封装内部均通过syscall(SC_unshare_*, &params)进入内核,并使用HANDLE_SYSCALL_RETURN_VALUE宏把负值错误码转换为Error

Jail:将容器固化为安全沙箱

当用户进程被“直到退出”式 jail(jailed until exit)后,它不能再创建或附着到其他任何资源。这使得 jail 成为构建安全(沙箱化)容器的有效机制:用户程序及其全部后代进程,将始终使用容器创建时选定的那套资源,无法中途逃逸或换用别的隔离上下文。

内核侧的强制点

Process::is_jailed()(见 Kernel/Tasks/Process.h)判断依据是jailed_until_exit.was_set() || jailed_until_exec,即支持“直到退出”与“直到 exec”两种时限。jail 的强制力体现在多处:

  • 资源切换被禁止unshare_open/unshare_create/unshare_enter在入口处即检查is_jailed()并返回EPERM(见 Kernel/Syscalls/unshare.cpp);
  • 设备访问受限Device::open检查is_jailed() && !is_openable_by_jailed_processes()时返回EPERM(见 Kernel/Devices/Device.cpp 第 115 行);
  • 内核配置只读化:SysFS 下的布尔/字符串内核配置项在写入路径上同样拒绝 jailed 进程(见 Kernel/FileSystem/SysFS/Subsystems/Kernel/Configuration/BooleanVariable.cpp 与 StringVariable.cpp),防止容器内进程篡改全局内核参数。

用户态入口

用户态通过prctl进入 jail 模式,LibCore封装见 Userland/Libraries/LibCore/System.cpp:

  • enter_jail_mode_until_exit():调用prctl(PR_SET_JAILED_UNTIL_EXIT, ...)
  • 另有enter_jail_mode_until_exec()PR_SET_JAILED_UNTIL_EXEC)对应“直到下次 exec 解除”的弱化模式。

实战:runc 工具如何组装一个容器

Userland/Utilities/runc/是 SerenityOS 上以用户态方式组装容器的参考实现,它把本文所述的三类资源与 jail 机制串成完整流程。

命令行用法

Userland/Utilities/runc/main.cpp 中serenity_main解析以下参数:

runc [-p|--pid-isolation] [-j|--enforce-jail] [-E|--preserve-env] \ [-f|--configuration <json-config>] [command...]
  • -p/--pid-isolation:创建并进入新的作用域进程列表;
  • -j/--enforce-jail:对容器强制 jail 限制;
  • -E/--preserve-env:保留用户环境变量执行命令;
  • -f/--configuration:使用 JSON 配置文件部署容器(与位置参数命令二选一);
  • 位置参数command:要在容器内以提升权限运行的命令。

不使用配置文件时,要求至少指定--pid-isolation--enforce-jail之一,否则报错Can't create a container with no attributes (jail/pid-isolation).

JSON 配置文件格式

使用-f时,配置文件是 JSON 对象,必需/可选字段如下(解析逻辑见extract_values_from_file):

字段类型说明
jailbool必需,是否启用 jail 强制
pid-isolationbool必需,是否启用 PID 隔离(作用域进程列表)
commandstring必需,容器入口命令
layoutarray必需,VFS 根上下文的挂载布局创建序列
hostnamestring 或 null可选,但必须显式指定其一;为字符串时创建并进入新的主机名上下文

解析器对非法组合(如hostname同时为 null 与字符串)会拒绝加载。挂载布局由 Userland/Utilities/runc/LayoutParsing.cpp 与 Userland/Utilities/runc/VFSRootContextLayout.cpp 处理,支持“在临时目录中构造根挂载点 → 按序列创建后续挂载 → 复制挂载骨架到新的 VFS 根上下文”的流程。

部署序列:六步组装一个容器

deploy_container_based_on_config_file严格按以下顺序部署(源码注释亦明确此顺序不可颠倒):

  1. 创建 PID 隔离unshare_open(ScopedProcessList)unshare_create(fd)unshare_enter(ScopedProcessList, index, AfterExec)
  2. 创建 VFS 根上下文:在临时目录构造布局并应用到新 VFS 根上下文;
  3. 附着 VFS 根上下文unshare_enter(VFSRootContext, id, AfterExec),随后卸载并清理临时目录;
  4. 创建并附着主机名上下文unshare_open(HostnameContext)unshare_create(fd)unshare_enter(HostnameContext, id, CurrentProgram)sethostname(hostname)
  5. 强制 jailenter_jail_mode_until_exit()
  6. exec 入口命令exec_command(command, false),此时第 1、3 步中标记为AfterExec的资源随 exec 一并生效。

注意第 4 步使用CurrentProgram(立即生效)而非AfterExec,因为要在 exec 前先以新主机名调用sethostname,从而把容器主机名“烙进”新上下文。

最小权限原则:pledge 的逐步收紧

部署过程中pledge承诺被逐级收窄,体现最小权限原则:

  1. 读配置前:pledge("stdio rpath wpath cpath proc mount unshare exec fattr chown")
  2. 构造完 VFS 布局后移除fattr/chown... mount unshare exec
  3. 附着完主机名上下文后移除unshare... mount exec
  4. jail 后移除proc... exec,最终以最少的权限执行容器命令。

关键源码索引

  • 官方文档:Documentation/Kernel/Containers.md
  • 系统调用实现:Kernel/Syscalls/unshare.cpp
  • 资源类型与标志定义:Kernel/API/Unshare.h
  • 系统调用号与参数结构:Kernel/API/Syscall.h
  • 资源句柄文件:Kernel/FileSystem/UnsharedResourceFile.h、Kernel/FileSystem/UnsharedResourceFile.cpp
  • 三类隔离对象:Kernel/FileSystem/VFSRootContext.h、Kernel/Tasks/ScopedProcessList.h、Kernel/Tasks/HostnameContext.h
  • 进程侧资源槽位与 jail 状态:Kernel/Tasks/Process.h、Kernel/Syscalls/execve.cpp
  • 用户态封装:Userland/Libraries/LibCore/System.cpp
  • 容器部署工具:Userland/Utilities/runc/main.cpp

【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询