For the complete documentation index, see llms.txt. This page is also available as Markdown.

18.1 Linux Compatibility Layer Architecture

FreeBSD's Linux compatibility layer is often mistaken for a virtual machine.

This compatibility layer does not introduce significant performance overhead; in some scenarios, certain software even outperforms native Linux environments. The compatibility layer is neither an instruction set emulator nor a binary translation layer, but rather a system-level implementation of the Linux Application Binary Interface (ABI).

What is the Linuxulator

FreeBSD's compatibility mechanism for Linux applications relies on a core component called Linuxulator. The literal meaning of "Linuxulator" is "Linux Emulator," a name that is easily confused with traditional instruction set emulators. Linuxulator is not a traditional emulator, nor is it an independent FreeBSD user-space program; it is merely an informal designation in the official FreeBSD documentation for a specific kernel module, whose official identifier is linux.

Linuxulator is fundamentally different from WSL2's virtual machine approach and Wine's Windows API compatibility implementation layer.

Specifically, its core principle is as follows: the FreeBSD kernel can identify and intercept Linux process system call requests, map them to functionally equivalent FreeBSD system calls, and respond to these requests using the FreeBSD kernel's implementation.

Tip

In other words, Linuxulator uses the FreeBSD kernel's system calls to handle Linux process system calls.

Through the Linuxulator module, the FreeBSD kernel simulates Linux kernel behavior at the system call interface, but actual process scheduling and execution are still handled by the FreeBSD kernel; the code that processes system calls is also a native FreeBSD implementation.

Linux processes running through Linuxulator are treated as standard FreeBSD processes within the FreeBSD kernel, indistinguishable from native processes.

What is the Linux Compatibility Layer

There is no real Linux kernel in the FreeBSD system; the Linux kernel version number declared by FreeBSD serves only as an identifier and does not impose actual kernel functionality constraints; this version number can even be set to an arbitrary value, such as 255.255.

Different Linux software has different minimum kernel version requirements, and the system call interfaces they depend on and use also differ. For example, if the declared Linux kernel version is too low, the Arch Linux chroot environment may fail to initialize properly and return a kernel too old error message.

The following is compiled from an email sent by Terry Lambert (tlambert@primenet.com) to the FreeBSD mailing list (message ID: 199906020108.SAA07001@usr09.primenet.com), introducing how the Linux binary compatibility layer works. The original text may have been lost; this section has been appropriately supplemented and updated.

User executes command (shell)
         |
         v
    +------------+
    | execve(2)  |
    +------------+
         |
         v
  +------------------------+
  | Check file Magic Number |
  +------------------------+
         |
         v
Invoke corresponding loader (ELF, a.out, PE...)
         |
         v
Check ELF Note segment Brand
         |
         v
Linux Brand
         |
         v
Linux ABI Loader
         |
         v
Thread PCB / syscall context switches to Linux system call table
e.g. sys/amd64/linux/linux_sysent.c
         |
         v
Lookup dependency binaries /compat/linux/... -> fallback /...
         |
         v
Execute Linux binary program

FreeBSD has an abstraction layer called the "execution class loader," which is embedded directly in the processing logic of the execve(2) system call.

Traditionally, UNIX loaders rely on checking magic numbers to determine file format. If no known binary format (such as ELF or the now-obsolete a.out) can be matched, the kernel returns an ENOEXEC error. At this point, the calling Shell takes over the file and attempts to interpret and execute it as a script of that Shell type.

Exercise

Based on the above information, read the source code /contrib/file/magic/Magdir/pdf, and consider how the file command works.

sys/sys/elf_common.h example snippet, determining whether a file is an ELF file:

FreeBSD does not hardcode a single loader; instead, it maintains an array of loaders (execsw). The system tries different loaders in sequence, including the #! (Shebang) loader for processing scripts. In other words, in modern FreeBSD, the parsing path for most scripts is completed in kernel mode, rather than relying on Shell error fallback.

To support the Linux ABI, after identifying the standard ELF magic number (through the magic numbers defined in sys/sys/elf_common.h), FreeBSD's ELF loader further validates the dedicated Brand tag in the ELF Note segment. Since Linux and native FreeBSD programs share the same underlying ELF format, and the ELF header e_ident[EI_OSABI] field of Linux binaries is typically ELFOSABI_NONE (unlike SVR4/Solaris which uses dedicated values like ELFOSABI_SOLARIS), the ELF Note Brand tag becomes the decisive characteristic for distinguishing Linux from FreeBSD ABI targets.

sys/compat/linux/linux_elf.c example snippet, used to further determine whether the ELF is a Linux program.

sys/compat/linux/linux_mib.h example:

After confirming the Linux Brand, the loader modifies the sysentvec pointer in the process execution context, switching the default system call table to the Linux ABI system call table. Thereafter, all system calls initiated by this process are indexed through this table. The associated signal trampoline and trap vectors are also switched accordingly. This system call table is provided by the kernel module; for amd64, its entries are generated by sys/amd64/linux/linux_sysent.c, which maps Linux system call numbers to corresponding kernel wrapper functions.

sys/compat/linux/linux_util.c code defining the default root path:

The above code is essentially the default definition for sysctl compat.linux.emul_path.

At the file system level, the Linux compatibility layer implements a fallback root path redirection mechanism. When a Linux process requests a path lookup, the system first attempts to locate the file under the /compat/linux/ path; if not found, it falls back to the host system's native path. Linux programs can thus seamlessly load their dedicated shared libraries while still accessing FreeBSD system resources when necessary. Combined with sysctl compat.linux.osname (default value "Linux") and compat.linux.osrelease, and by providing customized tools such as uname(1) in /compat/linux, the environment disguise is fully realized.

sys/compat/linux/linux_util.c code for setting the fallback root directory:

sys/sys/namei.h partial definition of the fallback mechanism:

The FreeBSD kernel provides native-level support for the Linux ABI. The vast majority of system calls (such as VFS, VM, and IPC operations) directly share the FreeBSD kernel's implementation at the kernel level. The difference between the two lies only in argument marshaling at the system call boundary: FreeBSD programs use native glue functions, while Linux programs connect through compatibility layer wrappers.

Strictly speaking, this is a system-call-level ABI translation implementation, not an instruction-set-level "emulation." The CPU directly executes the native machine code of Linux binaries, with no intermediate instruction translation overhead. Early literature used the term "emulation" due to the limitations of the technical terminology at the time, which is not an accurate description of its technical essence.

Mounting the Linux Compatibility Layer File Systems

Based on the linux_start() snippet defined in the source code file libexec/rc/rc.d/linux:

sysctl -n compat.linux.emul_path will output the current path of the Linux compatibility layer; the default value was mentioned above as "/compat/linux/". When building the compatibility layer under the /compat/linux/ path, the relevant file system paths can be automatically mounted and ldd dependencies refreshed. When building manually, the sysctl variable compat.linux.emul_path must be pointed to the custom path to avoid excessive manual configuration.

Warning

compat.linux.emul_path is a global sysctl variable that can only point to one path at a time. If multiple compatibility layers are installed, the later-installed compatibility layer will overwrite the emul_path setting of the earlier-installed one, causing the earlier-installed compatibility layer to malfunction. When multiple compatibility layers coexist, you must manually switch emul_path or use different startup configurations.

The linux_mounts_enable="YES" variable is read by the libexec/rc/rc.d/linux service script and then executes the actual file system mount operations. It is enabled by default, defined in the default rc file libexec/rc/rc.conf, and can be overridden in /etc/rc.conf.

Why Using the Linux Compatibility Layer is Not a Philosophy of Suffering

The practice of loading modules via kldload linux should not be questioned. As Xunzi said: "The gentleman is not different by nature; he is good at making use of things." The same applies to using the Linux compatibility layer. Similar technologies include using Wine or CrossOver on Linux, and even ReactOS; as well as Linux compatibility layers and Android compatibility layers on the Windows platform, all of which have been widely adopted.

References

Exercises

  1. Read the linux_file.c file in the sys/compat/linux/ directory of the FreeBSD source code, analyze the mapping mechanism from Linux system calls to FreeBSD system calls, and select 3 key system calls to trace their processing flow.

  2. Modify compat.linux.osrelease to 2 different Linux kernel version numbers, and test the compatibility behavior of Linux software under different version numbers.

Last updated