22.8. 高级主题

如果你对Linux兼容模式是如何工作的感到好奇,这节正是你所需要的。 下面的绝大部分内容是由 Terry Lambert (Message ID: <199906020108.SAA07001@usr09.primenet.com>)发表在邮件列表FreeBSD chat 邮件列表上的内容组成的。

22.8.1. 它是如何工作的?

FreeBSD有一个“可执行类加载器”。它主要是嵌入了execve(2)系统调用。

碰巧的是FreeBSD有一个引导器(loader)的列表,而不是一个简单的返回一个 符号 #!的引导器!

从历史上来讲,只有UNIX®平台的引导器会检查魔术(magic)数 (通常是文件的前4个或8个字节)是否是二进制的, 如果是,就调用二进制引导程序。

如果它不是二进制类型的execve(2)调用就会返回一个错误,shell就试图用shell命令执行它。

缺省是使用“当前设定的shell”。

随后,进行了一些hack, sh(1)开始检查前两个字符,如果它们是:\n, 那它就调用csh(1)(我们相信是SCO最先做这个hack的)。

FreeBSD现在所做的是用一个普通的#!引导器仔细检查引导器的列表, 然后由解释程序一个接一个地解释,返回给/bin/sh

为了支持Linux ABI,FreeBSD就把魔术数看作为一个二进制ELF程序。( 这样一来,它就使得在FreeBSD, Solaris™,Linux和其他任何操作系统之间只要使用ELF格式就都可以顺利运行)。

ELF引导器会寻找一个专门的标记, 它是在ELF映像中的一个注释部分,但在SVR4/Solaris的ELF中没有。

为了执行Linux程序,它们必须被打上Linux类型的标记; 使用brandelf(1)

# brandelf -t Linux file

做完之后,ELF引导器就会看到文件上的Linux的标记。

当ELF引导器看到Linux的标记, 引导器就会在proc结构中替换一个指示器。 所有的系统调用就会通过这个指示器来索引(在一个传统的 UNIX系统中, 这就是sysent[]结构队列,包含系统调用)。 此外,为了解决由于信号杂乱所造成的陷阱向量的问题,会造成线程的剧增, 需要切断其他(或较小的)由Linux内核模块产生的修正。

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

当一个系统调用被Linux程序调用时,有缺陷的代码会把系统调用功能的指示器从proc结构中解除, 然后获得Linux,而不是FreeBSD,系统调用入口点。

另外,Linux模式动态地reroots查找;这和启动文件系统的union 选项是等效的(即时不是unionfs文件系统)。 首先会试图在/compat/linux/original-path 目录查找文件,如果失败了,就会在/original-path 目录下查找。这使得需要其它程序的程序可以运行(例如,Linux工具链都可以在Linux ABI的支持下工作)。 也就是说Linux程序可以加载和执行FreeBSD程序,如果当前没有相应的Linux程序, 那你可以在/compat/linux目录树中放置一个uname(1),来确保Linux程序不提示它们不能运行在Linux上。

在FreeBSD内核中有一个Linux内核;由内核提供的能够提供所有服务的各种潜在功能 在FreeBSD系统调用表记录和Linux系统调用表记录之间是一样的: 文件系统操作,虚拟内存操作,信号发送,System V IPC,...等等。 唯一的不同是FreeBSD会得到FreeBSD的胶合功能, 而Linux程序会得到Linux的胶合功能 (大部分老的操作系统只有它们自己的胶合函数, 函数地址在静态全局变量sysent[]结构数据里面, 而不是动态的初始化到进程的proc结构)。

哪一个是FreeBSD自己的ABI呢?这无关紧要。基本上, 唯一的不同是FreeBSD的胶合功能是被静态连接到内核, 而Linux的胶合功能可能是被静态连接到内核, 也可能它们通过一个内核模块来访问。

有一个真正的模拟器吗?没有,它只不过是一个ABI执行机制,不是一个模拟器。

为什么有时它被叫做“Linux模拟器”? 只是为了更容易地卖出FreeBSD罢了! 实际上,历史上从来没有描述这样一种执行机制的名字,FreeBSD并不是真正地运行Linux程序,如果你不编译进代码, 或加载一个模块。 就需要有一个名字来描述这样一种加载功能--因此就想出了“Linux模拟器”这样一个名字。