21.1 Linux 兼容层架构

一个常见的技术认知误区是将 FreeBSD 的 Linux 兼容层等同于虚拟机,进而认为该机制会不可避免地降低软件运行效率。然而,实证观察表明,该兼容层不仅不会引入显著的性能开销,在某些特定场景下,部分软件的运行性能甚至优于其在原生 Linux 环境中的表现。这一特性的根本原因在于:该兼容层既非指令集模拟器,亦非二进制转译层,而是 Linux 应用程序二进制接口(Application Binary Interface, ABI)的一种系统级实现。

https://cgit.freebsd.org/src/tree/sys/compat/linux/linux_file.carrow-up-right

从该内核源代码文件中可以清晰地观察到,该模块的核心功能具有单一性:其主要职责是识别 Linux 系统调用,随后定位并映射到与之对应的 FreeBSD 系统调用实现,并将原始的 Linux 系统调用请求转发至 FreeBSD 系统调用入口点进行处理。

何为 Linuxulator

若要深入理解 FreeBSD 对 Linux 应用程序的兼容机制,首先需要考察 Linuxulator 这一核心组件。Linuxulator 这一术语的字面含义为 "Linux Emulator",该名称极易引起误解,使人将其与传统意义上的指令集模拟器相混淆。然而,Linuxulator 并非传统模拟器范畴,它甚至不是一个具有独立可执行文件形态的 FreeBSD 用户空间应用程序。确切地说,Linuxulator 只是 FreeBSD 官方文档中对某一特定内核模块的非正式称谓,该内核模块的正式标识符为 linux

下面先查看一下 linux 内核模块对应的 man 页面。

man 页面

查看 FreeBSD 中 Linux 兼容层相关的内核接口说明:

$ man 4 linux

man 4 linux 的输出内容翻译如下:


LINUX(4) FreeBSD 内核接口手册 LINUX(4)

名称 linux – Linux ABI 支持

简介 要在启动时启用 Linux ABI,请在 rc.conf(5) 中添加以下行:

linux_enable="YES"

描述 Linux 内核模块提供了有限的 Linux ABI(Application Binary Interface,ABI)兼容性,使得无需虚拟化和仿真即可运行许多未经修改的 Linux 应用程序。提供的一些功能包括:

  • Linux 到原生系统调用的转换

  • Linux 特有的系统调用

  • Linux 进程的特殊信号处理

  • 路径转换机制

  • Linux 特有的虚拟文件系统

    路径转换机制使得 Linux 进程在查找文件路径时,会首先在 emul_path(默认为 /compat/linux)下查找,然后才是 /。例如,当一个 Linux 进程尝试打开 /etc/passwd 时,它会首先访问 /compat/linux/etc/passwd,如果兼容路径不存在,则回退到 /etc/passwd。此机制确保 Linux 进程加载的是 Linux 共享库,而非它们对应的 FreeBSD 版本,并且为某些其他文件和虚拟文件系统提供替代版本。

    要将 Linux 共享库和系统文件安装到 /compat/linux,可以使用 Port emulators/linux_base-c7 或对应的包,或者通过 sysutils/debootstrap 安装 debootstrap(8)

    为避免在启动时挂载 Linux 特有的文件系统,可以在 rc.conf(5) 文件中添加以下行:

SYSCTL 变量 以下变量既可作为 sysctl(8) 变量,亦可作为 loader(8) 可调参数使用:

  • compat.linux.debug 启用调试消息。设置为 0 可静音。默认为 3。设置为 1 时打印调试消息,报告未实现的功能(仅一次)。设置为 2 时类似于 1,但也会打印已实现但未测试的功能的消息(仅一次)。设置为 3 及更高时,类似于 2,但没有消息频率限制。

  • compat.linux.default_openfiles Linux 应用程序的默认软打开文件资源限制。设置为 -1 即禁用该限制。默认为 1024

  • compat.linux.emul_path Linux 运行时环境的路径。默认为 /compat/linux

  • compat.linux.osname Linux 内核操作系统名称。默认为 "Linux"。

  • compat.linux.osrelease Linux 内核操作系统发布版本。不建议在非开发系统上修改此值,因为这可能会影响 Linux 程序的工作方式。已知某些版本的 GNU libc 会根据该值的不同使用不同的系统调用。

  • compat.linux.oss_version Linux 开放音频系统版本。默认为 198144

  • compat.linux.preserve_vstatus 设置为 1 时,防止 Linux 应用程序重置 termios(4) 的 VSTATUS 设置。从用户角度来看,这使得 SIGINFO 对 Linux 可执行文件有效。默认为 1

  • compat.linux.setid_allowed 启用处理新进程镜像文件的用户 ID 和组 ID 设置模式位(set-user-ID 和 set-group-ID)。设置为 0 时,新的 Linux 镜像始终使用发出 execve(2) 调用的程序的凭证,而忽略镜像文件的模式。因为 FreeBSD 没有完全仿真 Linux 环境,缺失的功能可能会导致安全漏洞。默认为 1

    compat.linux32.emulate_i386 在 x86_64(amd64)环境中启用真实的 i386 Linuxulator 行为。例如,当设置为 0 时,即使 uname 是 i386 Linux 可执行文件,uname -m 也会返回 "x86_64"。当设置为 1 时,Linux i386 uname -m 会返回 "i686"。默认为 0

文件 /compat/linux Linux 运行时环境 /compat/linux/dev 设备文件系统,参见 devfs(5) /compat/linux/dev/fd 文件描述符文件系统,挂载了 linrdlnk 选项,参见 fdescfs(5) /compat/linux/dev/shm 内存文件系统,参见 tmpfs(5) /compat/linux/proc Linux 进程文件系统,参见 linprocfs(5) /compat/linux/sys Linux 内核对象文件系统,参见 linsysfs(5)

参见 brandelf(1), pty(4), elf(5), fdescfs(5), linprocfs(5), linsysfs(5), tmpfs(5)

历史 Linux ABI 支持首次出现在 FreeBSD 2.1 中。对 amd64 二进制文件的支持首次出现在 FreeBSD 10.3 中。对 arm64 二进制文件的支持首次出现在 FreeBSD 12.0 中。

BUG 某些 Linux 特有的系统调用和系统调用参数的支持仍然缺失。

FreeBSD 14.2-RELEASE 2022 年 1 月 9 日 FreeBSD 14.2-RELEASE


通过上述分析可以得出结论:Linuxulator 既不同于 WSL2 所采用的虚拟机封装方案,也有别于 Wine 所采用的二进制兼容层解释执行机制。

其工作原理可概括为:由 FreeBSD 内核识别并截获 Linux 进程发起的系统调用请求,定位到 FreeBSD 内核中与之功能等价的系统调用实现,随后利用 FreeBSD 内核的系统调用实现来响应 Linux 进程的系统调用请求。

注意

Linuxulator 的核心运作机制是利用 FreeBSD 内核的系统调用实现来响应 Linux 进程的系统调用请求

换言之,借助 Linuxulator 这一内核模块,FreeBSD 内核能够在系统调用接口层面模拟 Linux 内核的行为;然而,实际调度和执行 Linux 进程的仍然是 FreeBSD 内核本身,负责处理 Linux 系统调用的代码也依然是 FreeBSD 内核中的原生实现。

从 FreeBSD 内核的进程管理视角来看,通过 Linuxulator 运行的 Linux 进程与 FreeBSD 原生进程在本质上并无差异——Linux 进程在 FreeBSD 内核中依然被视为标准的 FreeBSD 进程。

什么是 Linux 兼容层

本节将对 Linux 兼容层的核心机制进行详细阐述。其中,路径转换机制是该兼容层的关键组件之一:该机制使得 Linux 进程在访问文件系统路径时,会优先在 emul_path(默认配置为 /compat/linux)目录下进行查找,仅当该路径下不存在目标文件时,才会回退到根文件系统 / 进行查找。例如,当 Linux 进程尝试打开 /etc/passwd 时,它实际会首先尝试访问 /compat/linux/etc/passwd,仅当该文件不存在时,才会访问 /etc/passwd。这一机制的设计目的在于确保 Linux 进程能够加载 Linux 原生共享库,而非名称类似的 FreeBSD 对应库,同时也为某些特定文件和虚拟文件系统提供了替代版本。

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

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

附录:为什么使用 Linux 兼容层并非“苦难哲学”

在系统启动后加载 kldload linux64kldload linux 并不应受到责难或讽刺,正如荀子所言:“君子生非异也,善假于物也”(荀子[M].北京:中华书局,1954. 系统阐述“善假于物”的方法论,为技术兼容实践提供哲学依据。)。

使用 Linux 兼容层亦是如此,这并不存在荒唐或可笑之处。类似的技术包括 Linux 上使用 Wine 或 CrossOver,乃至 ReactOSarrow-up-right 备份arrow-up-right;以及 Windows 平台上的 Linux 兼容层和 Android 兼容层,这些方案都得到了广泛应用。

最后更新于