13.1 Linux 兼容层架构

FreeBSD 的 Linux 兼容层常被误认为虚拟机。

该兼容层不会引入显著的性能开销,某些场景中部分软件的性能甚至超过原生 Linux 环境。该兼容层既非指令集模拟器,也非二进制转译层,而是 Linux 应用程序二进制接口(Application Binary Interface,ABI)的系统级实现。

何为 Linuxulator

FreeBSD 对 Linux 应用程序的兼容机制依赖于 Linuxulator 这一核心组件。“Linuxulator”的字面含义为“Linux Emulator”,该名称容易与传统指令集模拟器相混淆。Linuxulator 并非传统模拟器,也不是独立的 FreeBSD 用户空间程序,仅为 FreeBSD 官方文档中对某一特定内核模块的非正式称谓,该内核模块的正式标识符为 linux

Linuxulator 与 WSL2 的虚拟机方案和 Wine 的 Windows API 兼容实现层截然不同。

具体而言,其核心原理如下:FreeBSD 内核能够识别并拦截 Linux 进程的系统调用请求,将其映射到功能等价的 FreeBSD 系统调用,并以 FreeBSD 内核的实现响应这些请求。

技巧

换言之,Linuxulator 利用 FreeBSD 内核的系统调用来处理 Linux 进程的系统调用。

通过 Linuxulator 模块,FreeBSD 内核在系统调用接口上模拟 Linux 内核行为,但实际进程调度和执行仍由 FreeBSD 内核负责;处理系统调用的代码同样为 FreeBSD 原生实现。

通过 Linuxulator 运行的 Linux 进程在 FreeBSD 内核中按标准 FreeBSD 进程处理,与原生进程无异。

什么是 Linux 兼容层

FreeBSD 系统中不存在真实的 Linux 内核,FreeBSD 所声明的 Linux 内核版本号仅具有标识意义,而不具备实际的内核功能约束;该版本号甚至可设置为任意值,例如 255.255

不同的 Linux 软件对内核版本的最低要求不同,其依赖和使用的系统调用接口也存在差异。例如,声明的 Linux 内核版本过低时,Arch Linux 的 chroot 环境可能无法正常初始化,并返回 kernel too old 的错误信息。

下文根据 Terry Lambert([email protected])发往 FreeBSD 邮件列表的邮件(邮件 ID:[email protected])整理而成,介绍 Linux 二进制兼容层的工作原理。原文可能已经佚失,本节进行了适当增补更新。

用户执行命令 (shell)
         |
         v
    +------------+
    | execve(2)  |
    +------------+
         |
         v
  +------------------------+
  | 检查文件幻数 Magic Number |
  +------------------------+
         |
         v
调用对应加载器 (ELF, a.out, PE...)
         |
         v
检查 ELF Note  Brand
         |
         v
Linux Brand
         |
         v
Linux ABI Loader
         |
         v
线程 PCB / syscall context 切换到 Linux 系统调用表
 sys/amd64/linux/linux_sysent.c
         |
         v
查找依赖二进制 /compat/linux/... -> fallback /...
         |
         v
执行 Linux 二进制程序

FreeBSD 拥有抽象层“可执行类加载器(execution class loader)”,该抽象层直接嵌入在系统调用 execve(2) 的处理逻辑中。

传统上,UNIX 加载器依赖检查幻数(Magic Number)来判断文件格式。若无法匹配任何已知的二进制格式(如 ELF 或现已过时的 a.out),内核将返回 ENOEXEC 错误。此时,发起调用的 Shell 将接管该文件,并尝试将其作为该 Shell 类型的脚本进行解释执行。

思考题

结合上述信息,阅读源代码 /contrib/file/magic/Magdir/pdf,请思考 file 命令的工作原理。

sys/sys/elf_common.h 示例片段,判断是否属于 ELF 文件:

FreeBSD 并非硬编码单一加载器,而是维护了一个加载器数组(execsw)。系统会按序尝试不同的加载器,包括用于处理脚本的 #!(Shebang)加载器。换言之,在现代 FreeBSD 中,绝大多数脚本的解析路径在内核态即已完成,而非依赖 Shell 的错误回退。

为了支持 Linux ABI,FreeBSD 的 ELF 加载器在识别出标准 ELF 幻数后(通过 sys/sys/elf_common.h 中定义的幻数),会进一步校验 ELF Note 段中的专用 Brand 标记。由于 Linux 与原生 FreeBSD 程序共享相同的 ELF 基础格式,且 Linux 二进制文件的 ELF 头 e_ident[EI_OSABI] 字段通常为 ELFOSABI_NONE(与 SVR4/Solaris 使用 ELFOSABI_SOLARIS 等专用值不同),因此 ELF Note Brand 标记成为区分 Linux 与 FreeBSD ABI 目标的决定性特征。

sys/compat/linux/linux_elf.c 示例片段,用于进一步判断该 ELF 是否是 Linux 程序。

sys/compat/linux/linux_mib.h 示例:

在确认 Linux Brand 后,加载器会通过修改进程执行上下文中的 sysentvec 指针,将默认的系统调用表切换为 Linux ABI 系统调用表。此后,该进程发起的所有系统调用均通过此表索引。相关的信号跳板(Signal Trampoline)和陷阱向量也随之切换。该系统调用表由内核模块提供,对于 amd64,其条目由 sys/amd64/linux/linux_sysent.c 生成,负责将 Linux 系统调用号映射至相应的内核包装函数(Wrappers)。

sys/compat/linux/linux_util.c 对默认根路径的定义代码:

上述代码实际上是对 sysctl compat.linux.emul_path 的默认定义。

在文件系统层面,Linux 兼容层实现了一套备用根路径重定向机制。Linux 进程请求路径查找时,系统会优先尝试在 /compat/linux/ 路径下定位文件;若未命中,则回退至宿主系统的原生路径。Linux 程序由此可无缝加载其专有的共享库,必要时仍可访问 FreeBSD 的系统资源。结合 sysctl compat.linux.osname(默认值“Linux”)与 compat.linux.osrelease,再通过在 /compat/linux 中提供定制的 uname(1) 等工具,环境伪装得以完整实现。

sys/compat/linux/linux_util.c 对备用根目录的设置代码:

sys/sys/namei.h 定义的部分回退机制:

FreeBSD 内核为 Linux ABI 提供了原生级的支持。绝大多数系统调用(如 VFS、VM、IPC 操作)在底层直接共享 FreeBSD 的内核实现。两者的差异仅在于系统调用边界的参数重组(Argument Marshaling):FreeBSD 程序使用原生胶水函数,而 Linux 程序通过兼容层包装器接入。

严格来说,这是一种系统调用级的 ABI 翻译实现,而非指令集层面的“仿真”。CPU 直接执行 Linux 二进制文件的原生机器码,不存在中间指令翻译损耗。早期文献受限于当时的技术术语体系才使用“仿真”一词,这并非对其技术本质的准确描述。

Linux 兼容层文件系统的挂载

根据源代码文件 libexec/rc/rc.d/linuxlinux_start() 片段定义:

sysctl -n compat.linux.emul_path 将输出当前 Linux 兼容层的路径,默认值上文已提及“/compat/linux/”。在 /compat/linux/ 路径下构建兼容层时,可实现相关文件系统路径的自动挂载,并刷新 ldd 依赖。自行构建时,须将 sysctl 项 compat.linux.emul_path 指向自定义路径,从而避免过多的手动配置。

警告

compat.linux.emul_path 是全局 sysctl 变量,同一时刻只能指向一个路径。如果安装了多个兼容层,后安装的兼容层会覆盖先安装的 emul_path 设置,导致先安装的兼容层无法正常工作。多兼容层共存时需手动切换 emul_path 或使用不同的启动配置。

linux_mounts_enable="YES" 变量由 libexec/rc/rc.d/linux 服务脚本读取然后执行实际的文件系统挂载操作。默认启用,定义在默认 rc 文件 libexec/rc/rc.conf 中,可在 /etc/rc.conf 覆盖默认设置。

为什么使用 Linux 兼容层并非苦难哲学

通过 kldload linux 加载模块的做法,不应受到质疑,正如荀子所言:“君子生非异也,善假于物也。”使用 Linux 兼容层也是如此。类似的技术包括 Linux 上使用 Wine 或 CrossOver,乃至 ReactOS;以及 Windows 平台上的 Linux 兼容层和 Android 兼容层,这些方案都已得到广泛应用。

参考文献

课后习题

  1. 阅读 FreeBSD 源代码中 sys/compat/linux/ 目录下的 linux_file.c 文件,分析 Linux 系统调用到 FreeBSD 系统调用的映射机制,选取 3 个关键系统调用追踪其处理流程。

  2. 修改 compat.linux.osrelease 为 2 个不同的 Linux 内核版本号,测试不同版本号下 Linux 软件的兼容性表现。

最后更新于