---
title: 第 11 章 Linux® 二进制兼容模式
part: 部分 II. 常见的任务
prev: books/handbook/printing
next: books/handbook/partiii
---

[[linuxemu]]
= Linux® 二进制兼容模式
:doctype: book
:toc: macro
:toclevels: 1
:icons: font
:sectnums:
:sectnumlevels: 6
:source-highlighter: rouge
:experimental:
:skip-front-matter:
:toc-title: 目录
:table-caption: 表
:figure-caption: 图
:example-caption: 例
:xrefstyle: basic
:relfileprefix: ../
:outfilesuffix:
:sectnumoffset: 11

ifeval::["{backend}" == "html5"]
:imagesdir: ../../../images/books/handbook/linuxemu/
endif::[]

ifeval::["{backend}" == "pdf"]
:imagesdir: ../../../../static/images/books/handbook/linuxemu/
endif::[]

ifeval::["{backend}" == "epub3"]
:imagesdir: ../../../../static/images/books/handbook/linuxemu/
endif::[]

include::shared/authors.adoc[]
include::shared/releases.adoc[]
include::shared/zh-cn/mailing-lists.adoc[]
include::shared/zh-cn/teams.adoc[]
include::shared/zh-cn/urls.adoc[]

toc::[]

[[linuxemu-synopsis]]
== 概述

FreeBSD 提供了与 Linux(R) 32-bit 二进制兼容， 允许用户在 FreeBSD 系统上安装和运行大多数的 32-bit Linux(R) 二进制程序而无需做任何修改。 据说在某些情况下， FreeBSD 上运行的 32-bit Linux(R) 二进制程序能有更好的表现。

然而， 仍然有一些 Linux(R) 操作系统特有的功能在 FreeBSD 上并不被支持。 例如， 要是 Linux(R) 程序过度地使用了诸如启用虚拟 8086 模式 i386(TM) 特有的调用， 则无法在 FreeBSD 上运行。 另外， 目前还不支持 64-bit 的 Linux(R) 二进制程序。

读完这章，您将了解到：

* 如何在 FreeBSD 系统中启用 Linux(R) 二进制兼容模式。
* 如何安装额外的 Linux(R) 共享库。
* 如何在 FreeBSD 上安装 Linux(R) 应用程序。
* FreeBSD 上 Linux(R) 兼容模式的实现细节。

在阅读这章之前，您应该知道：

* 知道如何安装 crossref:ports[ports, 额外的第三方软件]。

[[linuxemu-lbc-install]]
== 配置 Linux(R) 二进制兼容模式

默认情况下， Linux(R) 库并没有被安装而且 Linux(R) 二进制兼容模式也没有被启动。 Linux(R) 库可以通过手动安装或者使用 FreeBSD 的 Ports Collection。

安装 package:emulators/linux-base-f10[] 包或者 port 是最容易在 FreeBSD 系统上获得一套基本的 Linux(R) 库的方法。 使用如下方法安装 port：

[source,bash]
....
# cd /usr/ports/emulators/linux_base-f10
# make install distclean
....

安装完成以后， 加载 `linux` 模块启用 Linux(R) 二进制兼容模式：

[source,bash]
....
# kldload linux
....

查看模块是否已经被加载：

[source,bash]
....
% kldstat
Id Refs Address    Size     Name
 1    2 0xc0100000 16bdb8   kernel
 7    1 0xc24db000 d000     linux.ko
....

在 [.filename]#/etc/rc.conf# 中加入以下这行后 Linux(R) 兼容模式便会在系统启动时自动开启：

[.programlisting]
....
linux_enable="YES"
....

想要在自制内核中静态链接 Linux(R) 二进制兼容支持的用户可以在自定义的内核配置文件中加入 `options COMPAT_LINUX`。 然后按照 crossref:kernelconfig[kernelconfig,配置FreeBSD的内核] 中所描述的方法编译并安装新内核。

[[linuxemu-libs-manually]]
=== 手动安装额外的库

在配置了 Linux(R) 兼容模式之后， 如果某个 Linux(R) 应用程序依然提示找不到共享库， 需先找出此 Linux(R) 二进制程序需要的共享库再手动安装。

在 Linux(R) 系统上使用 `ldd` 找出应用程序所需的共享库文件。 比如， 在安装有 Doom 的 Linux(R) 系统上运行如下的命令列出 `linuxdoom` 所需用到的共享库文件：

[source,bash]
....
% ldd linuxdoom
libXt.so.3 (DLL Jump 3.1) => /usr/X11/lib/libXt.so.3.1.0
libX11.so.3 (DLL Jump 3.1) => /usr/X11/lib/libX11.so.3.1.0
libc.so.4 (DLL Jump 4.5pl26) => /lib/libc.so.4.6.29
....

然后把上面输出中最后一列中的所有文件从 Linux(R) 系统复制到 FreeBSD 上的 [.filename]#/compat/linux#。 复制完成之后， 建立指向第一栏中文件名的符号链接。 这样在 FreeBSD 系统上将会有如下的文件：

[source,bash]
....
/compat/linux/usr/X11/lib/libXt.so.3.1.0
/compat/linux/usr/X11/lib/libXt.so.3 -> libXt.so.3.1.0
/compat/linux/usr/X11/lib/libX11.so.3.1.0
/compat/linux/usr/X11/lib/libX11.so.3 -> libX11.so.3.1.0
/compat/linux/lib/libc.so.4.6.29
/compat/linux/lib/libc.so.4 -> libc.so.4.6.29
....

如果已经有了一个与 `ldd` 输出中第一列的主修订号相同的 Linux(R) 共享库文件， 则不再需要复制最后那列文件， 现有的共享库应该可以正常使用。 如果是更新版本的共享库通常建议复制。 只要有符号链接指向新的版本， 那么就可以删除旧版的了。

比如， FreeBSD 系统中现有这些共享库文件：

[source,bash]
....
/compat/linux/lib/libc.so.4.6.27
/compat/linux/lib/libc.so.4 -> libc.so.4.6.27
....

并且 `ldd` 指出某个二进制程序需要之后版本：

[source,bash]
....
libc.so.4 (DLL Jump 4.5pl26) -> libc.so.4.6.29
....

既然现有文件最后的版本号只相差一到两个版本， 程序应该可以正常使用稍旧些的版本。 不管怎样， 使用新版本替换现有 [.filename]#libc.so# 都是安全的。

[source,bash]
....
/compat/linux/lib/libc.so.4.6.29
/compat/linux/lib/libc.so.4 -> libc.so.4.6.29
....

通常最初几次在 FreeBSD 上安装 Linux(R) 程序时需要寻找 Linux(R) 二进制程序所依赖的共享库文件。 在此之后， 系统里便会有足够多的 Linux(R) 共享库文件来运行新安装的 Linux(R) 二进制程序而无需额外操作。

=== 安装 Linux(R) ELF 二进制程序

ELF 二进制程序有时需要额外的步骤。 当未被标记的 ELF 二进制程序被执行的时候， 会生成如下的错误信息：

[source,bash]
....
% ./my-linux-elf-binary
ELF binary type not known
Abort
....

为了帮助 FreeBSD 内核分辨 FreeBSD ELF 二进制程序和 Linux(R) 二进制程序， 请使用 man:brandelf[1]：

[source,bash]
....
% brandelf -t Linux my-linux-elf-binary
....

由于现在的 GNU 工具链能自动把适当的标记信息写入 ELF 二进制程序中，这个步骤通常不是必须做的。

=== 安装基于 Linux(R) RPM 的应用程序

安装基于 Linux(R) RPM 的应用程序， 首先需要安装 package:archivers/rpm[] 包或者 port。 安装好之后 `root` 用户就能使用此命令安装 [.filename]#.rpm# 了：

[source,bash]
....
# cd /compat/linux
# rpm2cpio < /path/to/linux.archive.rpm | cpio -id
....

如有必要的话使用 `brandelf` 标记安装好的 ELF 二进制程序。 注意此项安装将无法干净卸载。

=== 配置主机名解析器

如果 DNS 不能正常工作或是出现以下的错误信息：

[source,bash]
....
resolv+: "bind" is an invalid keyword resolv+:
"hosts" is an invalid keyword
....

请参照此方法配置 [.filename]#/compat/linux/etc/host.conf#：

[.programlisting]
....
order hosts, bind
multi on
....

这里指定了先查询 [.filename]#/etc/hosts# 再查询 DNS。 如果 [.filename]#/compat/linux/etc/host.conf# 不存在的话， Linux(R) 程序便会读取 [.filename]#/etc/host.conf# 并提示与 FreeBSD 的语法不兼容。 如果没有在 [.filename]#/etc/resolv.conf# 文件中配置域名服务器， 可以删除 `bind`。

[[linuxemu-advanced]]
== 高级主题

此章节将讲述是 Linux(R) 二进制兼容如何工作的， 内容基于 Terry Lambert mailto:tlambert@primenet.com[tlambert@primenet.com] (Message ID: `<199906020108.SAA07001@usr09.primenet.com>`) 发表在 {freebsd-chat} 的邮件。

FreeBSD 有一个叫 "execution class loader" 的抽象层。 它被嵌入进了 man:execve[2] 系统调用。

历史上 UNIX(R) 加载器会依靠查看魔数 （通常是文件的开头 4 至 8 个字节）来确认是否是系统已知的的二进制程序， 如果是的话， 就会调用二进制程序加载器。

如果它不是二进制类型的程序， man:execve[2] 调用会返回一个错误， shell 则会把它当作 shell 命令执行。 "不论当前是哪一种 shell" 都会默认做出此种假设。

随后， man:sh[1] 会检查开头的两个字符， 如果它们是 `:\n`， 那么就调用 man:csh[1]。

FreeBSD 有一份加载器列表而不是一个单一的加载器， 并能回退到 `#!` 加载器来运行 shell 解释器或者 shell 脚本。

为了支持 Linux(R) ABI， FreeBSD 看到了二进制 ELF 程序的魔数。 ELF 加载器会查找一个专用的 _标记_， 那是在 ELF 镜像中的一个注释部分， 此区域在 SVR4/Solaris(TM) ELF 二进制中并不存在。

要运行 Linux(R) 二进制程序， 必须先使用 man:brandelf[1] 命令 _标记_ 为 `Linux` 类型：

[source,bash]
....
# brandelf -t Linux file
....

当 ELF 加载器看到了 `Linux` 标记，便会替换 `proc` 结构中的一个指针。 所有的系统调用都通过此指针来索引。 除此以外， 进程被标记以便对 signal trampoline 代码的陷阱向量做特殊处理， 还有一些其他由 Linux(R) 内核模块来处理的（细微）修补。

Linux(R) 系统调用向量包含一个 `sysent[]` 记录的列表， 它的地址位于内核模块之中。

当一个系统调用被 Linux(R) 二进制程序调用时， 陷阱代码会把系统调用函数指针从 `proc` 解引用至 Linux(R) 而不是 FreeBSD 的系统调用入口。

Linux(R) 模式会动态地 _reroots_ 查找。 这与 `union` 文件系统选项是等效的。 首先会试图在 [.filename]#/compat/linux/original-path# 目录查找文件。 如果失败了， 就会在 [.filename]#/original-path# 目录下查找。 这使得需要其它程序的程序得以运行。 例如，Linux(R) 工具链都可以在 Linux(R) ABI 的支持下运行。 也就是说 Linux(R) 二进制程序可以加载并执行 FreeBSD 二进制程序， 如果当前没有相应的 Linux(R) 二进制程序， 可以在 [.filename]#/compat/linux# 目录树中放置一个 man:uname[1] 命令， 使 Linux(R) 程序不易察觉它们并没有运行在 Linux(R) 系统上。

事实上， 在 FreeBSD 内核中有一个 Linux(R) 内核。 所有由内核提供的服务的各种底层功能在 FreeBSD 系统调用表的记录和 Linux(R) 系统调用表的记录是一样的： 文件系统操作， 虚拟内存操作， 信号发送， 和 System V IPC。 唯一的不同是 FreeBSD 会得到 FreeBSD 的 _glue_ 功能， 而 Linux(R) 程序会得到 Linux(R) 的 _glue_ 功能。 FreeBSD 的 _glue_ 功能是静态链接入内核的， 而 Linux(R) 的 _glue_ 功能可以静态链接， 或者通过内核模块访问。

严格说来其实并没有真正的模拟， 这是一种 ABI 的实现。 有时这被称为 "Linux(R) 模拟" 是因为在实现的时候还没有其他适合的词用来描述。 要说 FreeBSD 运行 Linux(R) 二进制程序并不确切， 因为当时代码并还没有被编译进去。
