深入剖析 WSL2 启动流程:从 wsl.exe 到 bash 的完整链路
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
WSL2 的启动是一个横跨 Windows 与 Linux 两个世界、涉及 COM 调用、Hyper-V 虚拟机(HCS)、hvsocket 通信与多级 Linux init 进程的复杂过程。本文以仓库文档 boot-process.md 为骨架,结合 WSL 开源仓库的真实源码,逐层拆解从用户在终端敲下wsl.exe到 bash 就绪的每一步,涵盖虚拟机配置、mini_init / gns / init / session leader / relay 各进程的职责与消息协议,帮助你建立对 WSL2 启动链路的完整认知,并能够循着源码路径继续深入阅读。
启动流程全景:一张序列图看懂整体架构
WSL2 的启动并非"一条命令启动一个进程"那么简单,它本质上包含两条独立但又相互依赖的链路:
- Windows 侧:
wsl.exe(命令行入口)通过 COM 与运行在会话 0 中的wslservice.exe服务通信,由后者负责创建发行版实例、启动 WSL2 虚拟机、维护与 Linux 侧各进程的 hvsocket 通道; - Linux 侧:虚拟机内的
mini_init→gns→init→ session leader → relay → 用户进程(bash)逐级 fork/exec,形成一条完整的进程创建链。
官方文档用下面的 Mermaid 序列图精确描绘了从wsl.exe调用到 bash 启动的完整事件顺序(详见 boot-process.md):
这条链路中各进程的定位与相互连接,还可以参考 index.md 中的组件架构图:wslservice.exe通过 hvsocket 同时连接mini_init与gns,mini_init通过 exec 派生出gns、init与localhost,而init又会继续 exec 出plan9与 session leader,最终由 session leader 派生 relay 去 exec 用户命令。
CreateInstance():发行版实例的创建
当wslservice.exe通过 COM 接收到CreateInstance()调用时,它需要完成三件事(详见 wslservice.exe.md 与 boot-process.md):
1)识别目标发行版
服务会在 Windows 注册表中查询DistributionRegistration(实现见 DistributionRegistration.cpp),匹配方式有两种:
- 按调用方传入的发行版 ID 精确匹配;
- 若未提供 ID,则回退使用系统默认发行版。
2)按发行版类型分派
根据发行版是 WSL1 还是 WSL2 走不同路径:
- WSL1:无需虚拟机,直接在 Windows 侧创建一个 WSL1 实例;
- WSL2:需要启动(或复用)一个 WSL2 虚拟机,下文单独展开。
3)关联实例与调用进程
将新创建的发行版实例与发起调用的 Windows 进程进行关联(实现见 Lifetime.cpp),以便管理该实例的生命周期。
值得补充的是,wslservice.exe是运行在会话 0、以 SYSTEM 身份运行的 Windows 服务,负责管理 WSL 会话、与 WSL2 虚拟机通信以及配置发行版。客户端通过其 COM 接口ILxssUserSession(定义见 wslservice.idl)与之交互;CoCreateInstance()由LxssUserSessionFactory(见 LxssUserSessionFactory.cpp)处理,同一 Windows 用户多次调用会返回同一个LxssUserSession实例(见 LxssUserSession.cpp)。除CreateInstance()外,该接口还提供CreateLxProcess()(在发行版内启动进程)、RegisterDistribution()(注册新发行版)、Shutdown()(终止全部发行版)等关键方法。
启动 WSL2 虚拟机:HCS 与虚拟机配置 JSON
启动 WSL2 发行版的前提是存在一个 WSL2 虚拟机。如果虚拟机尚未运行,它会作为CreateInstance()调用的一部分被创建出来。
通过 HCS 服务创建虚拟机
虚拟机的创建依托 Windows 的 Host Compute System(HCS)服务,核心逻辑位于 WslCoreVm.cpp。流程如下:
wslservice.exe生成一段描述虚拟机配置的JSON 字符串;- 将该 JSON 传给
HcsCreateComputeSystem()创建新虚拟机; - HCS 的 JSON schema 细节可参阅 hcs_schema.h。
JSON 配置中的三个关键部分
虚拟机配置 JSON 至少包含以下内容(见 boot-process.md):
| 配置项 | 说明 |
|---|---|
| 内核(kernel) | WSL 默认使用内置内核,通常安装于C:\Program Files\WSL\tools\kernel;若用户在.wslconfig中覆盖了kernel=配置项,则使用自定义内核 |
| initramfs | WSL 使用自带的 initramfs(通常位于C:\Program Files\WSL\tools\initrd.img),其中只包含 mini_init 这一个二进制 |
| 虚拟化资源 | 虚拟机可访问的 CPU、内存(RAM)、GPU 等资源配额 |
虚拟机启动后,会先引导进入上述内核,随后执行 initramfs 中的 mini_init——这是 WSL2 虚拟机内的第一个用户态进程,也是整个 Linux 侧启动链的起点。
Linux 启动过程:mini_init 与两级配置消息
mini_init:虚拟机内的用户态初始化
mini_init是 WSL2 虚拟机启动后执行的第一个可执行文件。与标准 Linux init 类似,它启动时首先挂载/proc、/sys、/dev等标准挂载点,随后执行一系列配置,包括:启用崩溃转储收集、通过/dev/console配置日志、进行 tty 配置等(详见 mini_init.md)。
一切就绪后,mini_init会向wslservice.exe建立两条 hvsocket 通道:
- mini_init 通道:用于接收
wslservice.exe发来的消息。常见消息包括LxMiniInitMessageLaunchInit(挂载虚拟磁盘并启动新发行版)、LxMiniInitMessageMount(在/mnt/wsl下挂载磁盘,用于wsl --mount)、EJECT_VHD_MESSAGE(弹出磁盘)、LxMiniInitMessageImport/LxMiniInitMessageExport(导入 / 导出发行版)等; - 通知通道:用于向
wslservice.exe发送通知,主要用来上报 Linux 进程的退出事件,wslservice据此判断发行版是否已终止。
所有消息与响应的完整枚举定义在 lxinitshared.h,例如:
LxMiniInitMessageLaunchInit, // 挂载发行版 VHD 并启动 init LxMiniInitMessageImport, LxMiniInitMessageImportInplace, LxMiniInitMessageExport, LxMiniInitMessageCreateInstanceResult, LxMiniInitMessageEjectVhd, LxMiniInitMessageEarlyConfig, // 早期配置 LxMiniInitMessageInitialConfig, // 初始配置 LxMiniInitMessageMount, LxMiniInitMessageUnmount, ...LxMiniInitMessageEarlyConfig:早期配置消息
在执行完自身初始化后,mini_init会收到wslservice.exe发来的LxMiniInitMessageEarlyConfig消息(见 boot-process.md),其中携带:
- 系统 VHD、交换 VHD 以及(如有)内核模块 VHD 的标识符;
- 虚拟机主机名(hostname);
- 配置的内存回收模式(memory reclaim mode)与页上报顺序(page reporting order)。
在源码 main.cpp 的LxMiniInitMessageEarlyConfig处理分支中可以看到该消息的更多实际用途:EnableSafeMode会开启安全模式并禁用大量特性;IsolateDistroCgroup且宿主支持 cgroup v2 时调用SetupWslUserCgroup()设置用户 cgroup;随后建立供 guest 网络服务使用的 hvsocket 连接;若启用了 DNS 隧道(EnableDnsTunneling),还会单独再开一条 hvsocket 连接用于 DNS 隧道。
gns:网络配置进程
收到 EarlyConfig 后,mini_init会fork()并exec("/gns")派生 gns 进程,负责虚拟机内的网络配置。wslservice.exe通过LxGnsMessageInterfaceConfiguration向gns下发配置,gns以LxGnsMessageResult回执结果。
WSL2 的网络设置对所有发行版共享。只要 WSL2 在运行,gns就与wslservice.exe保持一条 hvsocket 通道,用于下发并应用:接口 IP 配置、路由表项、DNS 配置、MTU 大小等。当启用 DNS 隧道时,gns还负责直接应答 DNS 请求。相关实现可参见 GnsEngine.cpp(Linux 侧)与 GnsChannel.cpp(Windows 侧)。
LxMiniInitMessageInitialConfig:初始配置消息
随后mini_init会收到LxMiniInitMessageInitialConfig消息,其中包含(见 boot-process.md):
- 熵缓冲区(entropy buffer):用于为虚拟机熵池播种;
- GPU 驱动共享(GPU driver shares)信息:如有,则指定需要挂载的 GPU 驱动共享;
- 是否启用 wslg(WSL 图形界面支持)。
其他维护任务
此外,mini_init还承担多项维护任务(见 mini_init.md):
- 回收未使用的内存(对应 EarlyConfig 中的内存回收配置);
- 启动调试 shell tty;
- 在虚拟机终止时同步 IO;
- 调整文件系统大小(供
wsl --manage <distro> --resize使用); - 格式化磁盘(安装新发行版时使用)。
在应用完wslservice.exe请求的全部配置之后,虚拟机即具备启动 Linux 发行版的条件。
启动 Linux 发行版:从 LaunchInit 到 session leader
LxMiniInitMessageLaunchInit:挂载 VHD 并派生 init
要启动一个新的发行版,wslservice.exe会向mini_init发送LxMiniInitMessageLaunchInit消息。mini_init随即挂载发行版 VHD,并在子命名空间中派生并启动 init(详见 init.md)。
从源码 main.cpp 可以看到这条消息的实际处理逻辑:mini_init通过UtilConnectVsock建立到LX_INIT_UTILITY_VM_INIT_PORT的 hvsocket 通道(若启用 GUI 应用还会额外连接一条),随后调用UtilCreateChildProcess创建名为"LaunchDistro"的子进程,子进程在ProcessLaunchInitMessage中真正完成启动,并且创建时使用了如下命名空间克隆标志:
CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | SIGCHLD这正是文档所强调的隔离机制:每个 WSL2 发行版都运行在独立的 mount、pid 与 UTS 命名空间中,互不可见、可并行运行。具体启动顺序是:挂载发行版 VHD → 克隆进子命名空间 → chroot 到 VHD 挂载点 → 执行 init。同时,所有发行版共享同一个/mnt/wsl挂载点。
init:发行版内的顶级进程
init是发行版的顶级进程:WSL1 下由wslservice直接启动,WSL2 下由mini_init启动。启动后init会执行一系列初始化任务(见 init.md):
- 挂载
/proc、/sys、/dev; - 配置 cgroups;
- 注册 binfmt 解释器(与 interop 相关);
- 解析
/etc/wsl.conf; - 启动 systemd(见 systemd.md);
- 挂载 drvfs 驱动器(见 drvfs.md);
- 配置 wslg。
就绪后,init建立与wslservice的连接(WSL1 走lxbus,WSL2 走hvsocket),并通过该通道接收命令。从 lxinitshared.h 的枚举可以看到完整命令集,例如:
LxInitMessageCreateProcess, // 创建用户进程(WSL1 路径) LxInitMessageCreateSession, // 创建新的 session leader LxInitMessageCreateSessionResponse, LxInitMessageNetworkInformation, LxInitMessageInitialize, // 配置发行版 LxInitMessageCreateProcessUtilityVm, // 创建用户进程(WSL2 路径) LxInitMessageExitStatus, // 进程退出状态上报 LxInitMessageWindowSizeChanged, // 终端窗口尺寸变化 LxInitMessageTerminateInstance, // 终止发行版 ...LxInitMessageCreateSession在 init.cpp 中处理:initfork()出一个 session leader,并向wslservice.exe回送LxInitMessageCreateSessionResponse。
session leader:面向用户进程的创建者
session leader 是收到LxInitMessageCreateSession消息后从initfork 出来的 Linux 进程(见 session-leader.md 与 init.cpp),每个 session leader 与一个 Windows 控制台(console)关联,代表用户创建 Linux 进程。
创建用户进程时,wslservice.exe发送LxInitMessageCreateProcess(WSL1)或LxInitMessageCreateProcessUtilityVm(WSL2)消息,消息中携带:命令行、当前目录、环境变量、用户名等。两条路径的实现略有差异:
- WSL1 路径:session leader 直接
fork(),子进程在exec()进入用户 Linux 进程前,先配置好 uid/gid、当前目录以及 stdin/stdout/stderr 标准文件描述符; - WSL2 路径:session leader
fork()出 relay 进程,由 relay 负责创建用户进程并把输出中继回wsl.exe。
中继 Linux 进程的输入输出到 Windows
CreateLxProcess 返回的五个句柄
用户进程创建成功后,wslservice.exe从CreateLxProcess()返回给wsl.exe。在 WSL2 场景下,wsl.exe会收到以下 HANDLES(见 boot-process.md):
| 句柄 | 用途 |
|---|---|
| STDIN | 向 Linux 进程写入标准输入 |
| STDOUT | 读取 Linux 进程的标准输出 |
| STDERR | 读取 Linux 进程的标准错误 |
| Control channel | 通知 Linux 进程终端变化(如wsl.exe终端窗口被调整大小时),并将变化应用到 Linux 进程 |
| Interop channel | 双向用途,见下 |
relay:WSL2 的中继进程
WSL2 下真正承担中继工作的是 relay 进程。它由 session leader 在收到LxInitMessageCreateProcessUtilityVm消息后创建,随后与wslservice.exe建立多条 hvsocket 通道,分别用于:
- 中继标准文件描述符(stdin / stdout / stderr);
- 中继终端信息(如 Windows 侧窗口尺寸变化);
- 向 Windows 通知 Linux 进程的退出。
通道就绪后,relayfork()成两个进程:父进程负责读写子进程的标准文件描述符并中继给 Windows;子进程调用exec()启动用户进程(本例中为/bin/bash)。随后wsl.exe与 relay 之间全双工中继 STDIN / STDOUT / STDERR——这正是你在终端里与 bash 交互的物理通道。
STDIN / STDOUT / STDERR 的中继逻辑
STDIN、STDOUT、STDERR三个句柄用于把 Linux 进程的输入输出中继到 Windows 终端。关键点在于:句柄类型不同(终端、管道、文件等),wsl.exe会套用不同的中继逻辑,以达成 Windows 与 Linux 之间最好的兼容性。这部分逻辑集中在 relay.cpp(Windows 侧中继实现;注意与 Linux 侧的 relay 进程区分)。
Control channel 与 Interop channel
Control channel用于把终端变化通知给 Linux 进程——典型场景是wsl.exe终端窗口被 resize 时,变化会经由该通道同步应用到 Linux 进程(对应 lxinitshared.h 中的LxInitMessageWindowSizeChanged消息)。
Interop channel有两种用途:
- 从 Linux 创建 Windows 进程——即 WSL 的双向互操作能力,详见 interop.md;
- 通知
wsl.exeLinux 进程已退出——通过LxInitMessageExitStatus消息传递退出码。
进程退出与 wslhost.exe 接管
当 Linux 进程退出后,relay 通过waitpid()回收子进程,并向wsl.exe发送LxInitMessageExitStatus(进程退出码);wsl.exe收到后刷新剩余 IO,并以与 Linux 进程相同的退出码退出自身——这正是 WSL 命令的退出码能够无缝传递给 Windows 脚本的原因。
还有一个值得注意的边界场景:如果wsl.exe在 Linux 进程退出之前被终止(例如用户直接关闭了终端窗口),wslhost.exe 会接管 Interop channel,继续处理执行 Windows 进程的请求,避免正在运行的 Linux 进程因宿主进程消失而失去互操作能力。
附:关键源码路径速查
| 关注点 | 仓库路径 |
|---|---|
| 消息与响应枚举(mini_init / init / gns 全部消息) | src/shared/inc/lxinitshared.h |
mini_init消息处理(LaunchInit / EarlyConfig / InitialConfig 等) | src/linux/init/main.cpp |
init的 session 与进程创建 | src/linux/init/init.cpp |
| 网络配置(Linux 侧) | src/linux/init/GnsEngine.cpp |
| 发行版注册表查询 | src/windows/service/exe/DistributionRegistration.cpp |
| 实例生命周期关联 | src/windows/service/exe/Lifetime.cpp |
| WSL2 虚拟机管理 | src/windows/service/exe/WslCoreVm.cpp |
| HCS JSON schema | src/windows/common/hcs_schema.h |
| Windows 侧 IO / 终端中继逻辑 | src/windows/common/relay.cpp |
| COM 服务与会话 | src/windows/service/exe/LxssUserSession.cpp |
| WSL 架构总览 | doc/docs/technical-documentation/index.md |
至此,从用户敲下wsl.exe到 bash 提示符出现的完整链路已经清晰:wsl.exe通过 COM 驱动wslservice.exe,后者借 HCS 拉起承载 mini_init 的虚拟机,mini_init 通过两级配置消息完成环境准备并派生 gns 与 init,init 创建 session leader,session leader 再经由 relay 把用户进程接入 Windows 的终端 IO。理解这条链路,是进一步研究 WSL 网络、互操作、systemd 集成与调试疑难问题的坚实基础。
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考