FreeBSD提供了独特的功能,帮助系统管理员更好地满足用户的需求。了解系统的实际工作原理有助于您做出更好的决策。例如,虽然多处理器、多核处理器和硬件线程都可以提高系统性能,但它们并不总是像你想象的那么有帮助。了解不同类型的多处理如何影响不同类型的工作负载告诉您哪里可以提高性能,哪里不能。
为了使程序在启动时启动并在关闭时干净地停止,您必须能够创建和编辑正确的启动和关闭脚本。虽然有些程序在你杀死它们下面的操作系统时会很好地停止,但其他程序(例如数据库)要求更温和的关闭。干净地启动和停止系统服务是一个很好的开发习惯,因此我们将了解更多关于FreeBSD启动和关闭脚本的信息。
在正常情况下,你永远不需要知道FreeBSD的链接和共享库支持是如何工作的,但我们无论如何都会讨论它们。为什么?因为奇怪的是,在计算机行业中,正常情况非常罕见。
最后,FreeBSD可以运行具有Linux兼容层的Linux软件,以及为其他硬件架构编写的软件。
如果你有一个在过去10年内构建的台式机或服务器,几乎可以肯定它有多个处理器。其中一些处理器是专用的,例如视频卡中的图形处理器。现代操作系统使用对称多处理( symmetric multiprocessing ——SMP)或多个相同的通用处理器。现代硬件包括许多不同的专用处理器,如图形卡和服务器远程管理等,但提供给操作系统的硬件具有相同的处理器。
SMP系统比单处理器有很多优势,而且它不仅仅是显而易见的“更强大!”如果你从微观层面考虑,在非常小的时间范围内,CPU一次只能做一件事。计算机上的每个进程都在争夺处理器时间。如果CPU正在执行数据库查询,则它不接受以太网卡试图传递的数据包。第二个内核的每一部分都指示CPU执行上下文切换并处理另一个请求。这种情况发生得如此频繁和迅速,以至于计算机似乎同时在做很多事情——就像电视画面似乎在移动,而它实际上只是快速地一个接一个地显示单个画面。
我的桌面有cwm提供窗口管理,Firefox有80个选项卡,LibreOffice接受我的打字。SSH会话附带了一大堆终端窗口。网络中断即将到来;屏幕正在显示文本;MP3播放器正在向Stereohenge播放《带伤口的护士》。电脑的“无缝多任务处理”对我虚弱的大脑来说似乎是无缝的。实际上,计算机只是很快地从一个任务切换到另一个任务。一毫秒后,它又向我的耳机发送了一小段声音,下一毫秒,它又更新了屏幕上的文本。
有了多个处理器,你的计算机真的可以同时执行多个操作。这非常有用,但系统复杂性会飙升。
要理解SMP及其相关问题,我们必须深入内核。所有操作系统在支持SMP时都面临着同样的挑战,这里的理论适用于各种平台。接下来是一个粗略的简化。内核设计是一个棘手的主题,任何描述都几乎不可能公正地描述它。然而,这里有一个粗略的尝试。
FreeBSD将CPU利用率划分为时间片(time slices)。时间片是指一个CPU执行一个任务所花费的时间长度。一个进程可以将CPU用于全职切片,或者直到没有更多工作要做,此时下一个任务可能会运行。内核使用基于优先级的(priority-based)系统来分配时间片,并确定哪些程序可以在哪些时间片中运行。如果一个进程正在运行,但出现了一个优先级更高的进程,内核允许第一个进程被中断或抢占(preempted)。这通常被称为抢占式多任务处理(preemptive multitasking)。
虽然内核正在运行,但它不是一个进程。任何进程都有内核设置的特定数据结构,内核会根据需要操纵这些数据结构。您可以将内核视为一种特殊的进程,其行为与所有其他进程截然不同。它不能被其他程序中断——你不能键入 pkill kernel
并重新启动系统。在过去,内核可能被称为控制进程(control process)或监视器(monitor)。
内核有一些系统其他部分没有遇到的特殊问题。想象一下,你有一个程序通过网络发送数据。内核从程序中接受数据,并将其放置在一块内存中,以交给网卡。如果计算机一次只能做一件事,那么在内核返回该任务之前,该内存或网卡不会发生任何变化。但是,如果您有多个处理器,计算机可以同时执行多个任务。如果两个不同的CPU(都在处理内核任务)指示您的网卡同时执行不同的操作怎么办?当你的老板一只耳朵在尖叫,而你的配偶在另一只耳朵里尖叫时,网卡的行为和你一样;你所做的任何事情都不能满足他们中的任何一个。如果一个CPU为网络任务分配内存,而另一个CPU则为文件系统任务分配相同的内存,该怎么办?内核变得混乱,结果不会让你满意。
为单处理器设计的类Unix内核声明内核是非可感知的,不能被中断。这简化了内核管理,因为一切都变得完全确定:当内核的一部分分配内存时,它可以指望在执行下一条指令时内存保持不变。内核的任何其他部分都不会改变那块内存。当计算机一次只能做一件事时,这是一个安全的假设。然而,一次开始做很多事情,这个假设就破灭了。
FreeBSD中SMP支持的第一个实现非常简单。进程分散在CPU之间,实现了大致的平衡,并且对内核进行了锁定。在CPU尝试运行内核之前,它会检查锁是否可用。如果锁是空闲的,CPU会持有锁并运行内核。如果锁不是空闲的,CPU知道内核正在其他地方运行,并继续处理其他事情。这个锁被称为 Big Giant Lock (BGL),或者后来简称为 Giant (巨人)。在这个系统下,内核可以知道数据不会从它下面改变。本质上,Giant保证内核只会像往常一样在一个CPU上运行。
这种策略对于两个CPU来说已经足够了。你可以在双CPU机器上运行一个中级数据库和一个web服务器,并确信CPU不会成为你的瓶颈。如果一个CPU忙于处理网页,另一个CPU可以自由地回答数据库查询。但如果你有一台八CPU的机器,你就有麻烦了;系统将花费大量时间等待Giant可用!
这种简单的SMP技术既不高效也不可扩展。SMP的标准教科书很少提到这种方法,因为它太笨重了。然而,其他一些SMP处理方法更糟糕。例如,微软服务器操作系统的几个早期版本将一个处理器专用于用户界面,另一个处理器用于其他所有功能。这种技术也很少出现在教科书中,尽管它确实有助于你的鼠标看起来更灵敏。
不过,一旦你在内核上有了锁,你就可以分配这个锁。FreeBSD将Giant分割成许多较小的锁,现在内核的每个部分都使用尽可能小的锁来执行其任务。最初,锁是在核心内核基础设施上实现的,如调度器(scheduler,内核中表示哪些任务可能有哪些时间片的部分)、网络堆栈(network stack)、磁盘I/O堆栈等。这立即提高了性能,因为当一个CPU在调度任务时,另一个CPU可以处理网络流量。然后,锁被向下推入各种内核组件中。网络堆栈的每个部分都开发了自己的锁,然后是I/O子系统的每个部分,以此类推——允许内核使用多个处理器同时执行多项任务。这些独立的内核子进程称为线程(threads)。
每种类型的锁都有自己的要求。您将看到对许多不同锁的引用,如互斥锁(mutexes)、sx锁、rw锁、自旋互斥锁(spin mutexes)、信号量(semaphores)、多读(read-mostly)锁等。每种方法都有其优缺点,必须在内核中仔细应用。
细粒度(Fine-grained)锁定比听起来要困难得多。锁定太精细,内核处理锁的时间比推送数据的时间多。锁定过于粗糙,系统浪费时间等待锁可用。对于2处理器系统来说,足够的锁定会使32处理器系统停滞和阻塞,而对32核主机有效的锁定对于新的192核系统来说是完全不够的。锁的调整和调谐已经花费了数年时间,仍在进行中,并将永远持续下去。
虽然内核的每个部分都使用目前可能的最小锁,但有时这个锁就是Giant锁。拔下USB设备意味着在内核说“暂停一切!我正在重新配置硬件!”时抓住Giant不到一秒钟。一些设备驱动程序仍在使用Giant,虚拟内存堆栈的某些棘手部分、sysctl处理程序等也是如此。
所有这些内核锁都有复杂的使用规则,它们以无数种方式相互作用。这些规则可以防止意外的锁交互。假设内核线程A需要资源Y和Z,内核线程B也需要Y和Z。但B在需要Y之前需要Z。如果A锁定Y而B锁定Z,那么A最终会等待Z而B等待Y。在丢失的资源被释放之前,两个线程都无法继续。这种 deadlock (死锁,也称为致命的拥抱——deadly embrace)将破坏系统的稳定,可能会使其崩溃。正确的锁定可以避免这个问题。
您可能会看到一条控制台消息,警告锁顺序颠倒 (lock order reversal),这意味着锁的应用顺序不正确。虽然这个内核通知并不总是预示着即将到来的厄运,但重要的是要注意。
WITNESS
内核选项专门监视锁定顺序和锁定顺序违规。默认情况下,FreeBSD current上启用了此选项(见第18章),如果您报告系统出现问题,开发团队可能会要求您启用它。 WITNESS
选项使内核检查它为违反锁定顺序而采取的每一个操作,这会降低系统性能。您可以使用 debug.WITNESS.watch sysctl
启用和禁用 WITNESS
。然而,运行 WITNESS
、阅读消息并对其采取行动是帮助改进FreeBSD的好方法。
当您收到其中一条锁定顺序反转(lock order reversal——LOR)消息时,请完整复制LOR消息。除了出现在控制台上,为了您的方便,这些消息还会记录到 /var/log/messages 中。收到锁定订单消息后,在FreeBSD当前邮件列表中搜索LOR消息的前几行,查看是否有人已经提交了该消息。如果您在邮件列表中找到了LOR,请阅读该消息并采取建议的行动。除非开发人员最近特别要求通知此类LOR,否则没有必要在邮件列表上发布“我也是”的消息。
如果你有一个新的LOR,恭喜你!发现一个新的LOR并不像发现一种新的昆虫物种那样令人满意——首先,你不能命名你的LOR——但它确实有助于FreeBSD项目。将您的报告通过电子邮件发送到FreeBSD当前邮件列表。提供有关您的系统的完整详细信息,特别是LOR出现时正在进行的工作。您可能会被要求提交一份错误报告,如第24章所述。
您将看到三种不同类型的多处理器系统:多核、多包和硬件线程。您需要了解它们之间的差异,因为不同的处理器类型对系统和应用程序行为有直接影响。
处理器的基本单元是 CPU core 。每个CPU核心都由一组资源组成,如执行单元(execution units)、寄存器(registers)、缓存(cache)等。曾几何时,核心(core)与处理器(processor)是一回事。
CPU封装(package)是指插入或焊接到主板上的芯片。这就是许多人所认为的“CPU”或“处理器”。你可能会不小心踩坏这个昂贵的部件?那是一个包裹。每个包包含一个或多个内核。在SMP之前,一个包中只有一个内核。如今,大多数包至少有两个内核,而且上限还在不断增加。同一封装内的CPU内核可以相对快速地相互通信。
有些主机有多个包。多个包为您提供了多组多核(multiple groups of multiple cores),使您有机会实现更多的并行性。包之间的通信比同一包上的内核之间的通信慢。此外,每个封装通常都有自己的存储控制器。一个包中的CPU内核将需要更长的时间来检索连接到另一个包的内存中的数据。是的,这意味着16核包的性能将优于两个8核包。然而,在现实中,很少有软件具有如此多的线程,以至于它可以利用这种差异。
最后,一些CPU内核可以通过一次运行多个线程来更有效地利用其执行资源。这被称为硬件线程(hardware threading)、同步多线程(Simultaneous Multi-Threading ——SMT)或(如果你是英特尔)超线程(HyperThreading)。附加线程有时被称为虚拟处理器或虚拟核。然而,虚拟处理器并不是一个成熟的CPU;例如,只有当第一个CPU正在等待某件事时,它才可用。FreeBSD的默认调度程序 sched_ule(4)
知道哪些内核是真实的,哪些是虚拟的,并且调度工作正常。
硬件线程存在各种潜在的安全问题。运行在一个虚拟处理器上的任务可以使用各种微妙的定时攻击从运行在另一个虚拟处理机上的任务中捕获数据,如加密密钥。这不是一种对脚本小子友好的攻击,但如果你不信任你的用户,你可以通过将启动时可调的 machdep.hyperthreading_allowed
设置为0来禁用硬件线程。
请记住,多个处理器不一定会使系统更快。一个处理器每秒可以处理一定数量的操作。第二个处理器意味着计算机每秒可以处理两倍的操作,但这些操作不一定更快。
把CPU的数量想象成道路上的车道。如果你有一条车道,你可以一次移动一辆车经过任何一个地点。如果你有四条车道,你可以让四辆车驶过那个地方。虽然这条四车道的道路不允许这些汽车更快地到达目的地,但任何时候都会有更多的汽车到达。如果你认为这没有什么区别,想想如果有人用一条单行道取代你当地的高速公路会发生什么。CPU带宽很重要。
虽然一个CPU一次只能做一件事,但一个进程(process)一次只能在一个CPU上运行。许多程序不能同时在多个处理器上执行工作。线程(threaded)程序是一个例外,我们将在本章稍后看到。一些程序通过同时运行多个进程并让操作系统根据需要将它们分散在处理器之间来绕过这一限制。流行的Apache web服务器多年来一直这样做。线程程序是专门为与多个处理器一起工作而设计的,不会产生多个进程。许多线程化程序只是创建一大堆线程来处理数据,并将这些线程分散在CPU上,这是一种处理并行性的简单方法,如果不总是有效的话。其他程序根本不处理多个CPU。
如果你发现你的一个CPU 100%繁忙,而其他CPU大多空闲,那么你正在运行一个不以任何方式处理多个CPU的程序。第21章深入探讨了性能问题,但对于这样一个程序,我们无能为力。
make(1)
用于构建软件的 make(1)
程序可以启动多个进程。如果你的程序编写得很干净,你可以使用多个进程来构建它。这对小程序没有帮助,但当你构建一个大型程序,如FreeBSD本身(见第18章)或LibreOffice时,使用多个处理器确实可以加速工作。使用 make(1)
的 -j
标志告诉系统要同时启动多少进程。一个好的选择是系统中的处理器或内核数量加一。例如,在每个处理器上有两个内核的双处理器系统上(就是一共四个核),我会运行五个进程来构建一个程序。
xxxxxxxxxx
# make -j5 all install clean
一些程序员没有正确设计他们的 Makefile,所以他们的程序无法处理使用 -j
标志构建的问题。如果构建给你带来了麻烦,停止使用 -j
并重试——或者,更好的是,找出问题并向作者提交错误报告。
你会在不同的语境中听到一个词是 thread 。一些CPU支持超线程(HyperThreading)。有些进程有线程。内核的某些部分作为线程运行。我的裤子有很多线(尽管有些线比我妻子认为在公共场合穿这些裤子所需的线少)。这些线(threads)是什么,它们是什么意思?
在大多数情况下,线程是一个轻量级进程(a thread is a lightweight process)。记住,进程是系统上的一个任务,一个正在运行的程序。进程在系统中有自己的进程ID,可以由用户启动、停止和管理。线程是进程的一部分,但它们由进程管理,用户不能直接寻址。一个进程一次只能做一件事,但单个线程可以独立行动。如果你有一个多处理器系统,一个进程可以同时在多个处理器上运行线程。
任何线程化程序都需要使用 threading library (线程库),该线程库通过与内核交互来告诉应用程序如何在该操作系统上使用线程。线程库以不同的方式实现线程,因此使用特定的库会影响应用程序性能。
同样,内核线程是内核中的子进程。FreeBSD有处理I/O、网络等的内核线程。每个线程都有自己的函数、任务和锁定。内核中的线程不使用任何用户空间库。
硬件线程是虚拟CPU核,如第396页“使用多个处理器:SMP”中所述。虽然你需要了解硬件是什么以及它如何影响你的系统,但硬件线程并不是线程的一部分。
service(8)
命令是系统启动和关闭脚本的前端。这些脚本在 /etc/rc 之后被称为 rc scripts (rc脚本),/etc/rc 是管理多用户启动和关闭过程的脚本。虽然主rc脚本位于 /etc/rc.d 中,但其他位置的脚本管理附加软件。port和package安装启动脚本,但如果你安装自己的软件,你需要创建自己的rc脚本。如果你以前从未使用过shell脚本,请仔细阅读。Shell脚本编写并不难,最好的学习方法是阅读示例并对这些示例进行自己的修改。此外,更改现有包的启动或关闭过程需要了解启动脚本的功能。
在启动和关闭过程中,FreeBSD检查 /usr/local/etc/rc.d 是否有其他shell脚本要集成到启动/关闭过程中。(您可以使用 local_startup
rc.conf 变量定义其他目录,但现在我们假设您只有默认目录。)启动过程专门查找可执行的shell脚本,并假设它找到的任何脚本都是启动脚本。它使用 start
参数执行该脚本。在关机期间,FreeBSD会运行带有 stop
参数的相同命令。脚本应该读取这些参数并采取适当的行动。
几十年来,类Unix操作系统在启动脚本中编码了服务启动顺序。这真的很烦人,真的很快。许多(但不是全部)Unixes已经摆脱了这一点。同样,FreeBSD的rc脚本也按顺序排列。每个rc脚本在启动之前都会标识它需要哪些资源。rc系统使用该信息对脚本进行排序。这是由 rcorder(8)
在启动和关闭时执行的,但您可以随时手动执行,以查看其工作原理。只需将启动脚本的路径作为参数提供给 rcorder(8)
即可。
xxxxxxxxxx
# rcorder /etc/rc.d/* /usr/local/etc/rc.d/*
/etc/rc.d/growfs
/etc/rc.d/sysctl
/etc/rc.d/hostid
/etc/rc.d/zvol
/etc/rc.d/dumpon
/etc/rc.d/ddb
/etc/rc.d/geli
/etc/rc.d/gbde
--snip--
rcorder(8)
程序使用脚本本身中的标记,将 /etc/rc.d 和 /usr/local/etc/rc.d 中的所有脚本按系统启动时使用的顺序排序。如果你的rc脚本有任何排序错误,比如死锁脚本,这些错误会出现在 rcorder(8)
输出的开头。
rc脚本系统非常简单——虽然脚本可能会变得复杂,但复杂性来自脚本运行的程序,而不是rc系统。启动NFS服务器的脚本有一大堆依赖关系和要求。一个更简单的守护进程的脚本,如 timed(8)
,解释了rc系统:
xxxxxxxxxx
#!/bin/sh
➊ # PROVIDE: timed
➋ # REQUIRE: DAEMON
➌ # BEFORE: LOGIN
➍ # KEYWORD: nojail shutdown
➎ . /etc/rc.subr
➏ name="timed"
➐ desc="Time server daemon"
➑ rcvar="timed_enable"
➒ command="/usr/sbin/${name}"
➓ load_rc_config $name
run_rc_command "$1"
PROVIDE
标签➊告诉 rcorder(8)
这个脚本的正式名称。此脚本在 timed(8)
之后称为 timed。
REQUIRE
标签➋列出了运行此脚本之前必须运行的其他脚本。需要定时运行才能启动的脚本在 REQUIRE
中列出定时。此脚本可以在运行 DAEMON
脚本后的任何时间运行。
BEFORE
标签➌允许您指定在此之后应运行的脚本。此脚本应在 LOGIN
脚本之前运行。 /etc/rc.d/LOGIN 和 /etc/rc.d/timed 都指定它们必须在 DAEMON
之后运行,但 BEFORE
标签允许您设置其他顺序要求。
KEYWORD
命令➍允许启动系统仅选择某些启动脚本。timed(8)
脚本包括 nojail
和 shutdown
。即使已启用,jail也不会运行此脚本。此脚本在系统关闭时运行。
/etc/rc.subr 文件➎包含rc脚本基础结构。每个rc脚本都必须包含它。
虽然脚本有一个名称,但由脚本运行的程序可能有一个单独的名称➏。不过,最常见的是,一个正式称为 timed 的rc脚本会运行 timed 程序。
description
字段➐提供了脚本提供的服务的简要描述,正如您所期望的那样。
rcvar
语句➑列出了切换此脚本的 rc.conf 变量。
command
➒精确地标识了此脚本应运行的命令——毕竟,您的系统上可能有多个同名命令,只是在不同的目录中。
脚本执行的最后两个操作是从 /etc/rc.conf 加载➓此服务的配置,然后实际运行该命令。
虽然这看起来很吓人,但实际上并没有那么难。通过复制现有的rc脚本来启动自定义的rc脚本。将命令名称设置为命令名称,并相应地更改路径。决定脚本必须先运行什么:你需要网络运行吗?您是需要启动特定的守护进程,还是需要在某些守护进程之前运行程序?如果您真的不知道,请在最后使用 REQUIRE
语句运行您的脚本,该语句包含系统上运行的最后一个脚本的名称。通过查看其他提供类似功能的rc脚本,您将学习如何在启动脚本中执行几乎任何操作。
使用这个简单的脚本,您可以通过向 /etc/rc.conf 添加信息来启用、禁用和配置您的程序。例如,如果您的自定义守护进程名为 tracker
,启动脚本将在 /etc/rc.conf 中查找变量 tracker_enable
和 tracker_flags
,并在每次运行启动脚本时使用它们。
您可能已经注意到在我们的示例中名为 DAEMON 的服务,并认为,“这很奇怪。我不知道任何名为DAEMON的系统进程。”这是因为它不是一个进程。rc系统有一些特殊的提供者,它们定义了引导过程中的主要点。使用这些使编写rc脚本更容易。
FILESYSTEMS提供程序保证所有本地文件系统都按照 /etc/fstab 中的定义挂载。
配置完所有网络功能后,将显示NETWORKING提供程序。这包括在网络接口上设置IP地址、PF配置等。
SERVERS提供者意味着系统具有足够的基本功能来支持基本服务器,如 named(8)
和NFS支持程序。远程文件系统尚未挂载。
DAEMON提供程序确保所有本地和远程文件系统(包括NFS和CIFS)都已装载,并且更高级的网络功能(如DNS)可以运行。
在LOGIN,所有网络系统服务都在运行,FreeBSD开始启动服务,以支持通过控制台、FTP守护进程、SSH等登录。
通过在自定义rc脚本的 REQUIRE
语句中使用这些提供者之一,您可以大致指定您希望自定义程序何时运行,而无需深入细节。
也许你正在安装一个复杂的软件,而供应商不支持FreeBSD的rc系统。这不是问题。大多数供应商提供的脚本都希望得到一个参数,如 start
或 stop
。请记住,在启动时,FreeBSD会使用 start
参数运行每个rc脚本,在系统关闭时,它会使用 stop
参数运行脚本。通过将 PROVIDE
和 REQUIRE
语句作为注释添加到此供应商脚本中,并确认它接受这些参数,您可以使脚本在启动和关闭过程中的适当时间运行。
在管理脚本中使用rc系统功能不是强制性的。在引导过程的末尾,FreeBSD运行 /etc/rc.local。在那里添加您的本地命令。但是,您不能使用 service(8)
来管理 rc.local 中的任何内容。
本地脚本(如Ports Collection安装的脚本)由 /etc/rc.d/localpkg 运行。如果您的自定义脚本导致问题,您可以尝试运行带有调试的 localpkg
脚本,以查看您的脚本如何与rc系统交互。最好的方法是使用调试。
xxxxxxxxxx
# /bin/sh -x /etc/rc.d/localpkg start
这会尝试再次启动服务器上的每个本地守护进程,这在生产系统上可能是不可取的。先在测试系统上试试。此外,请记住,-x
调试标志不会传递给子脚本;您正在调试系统启动脚本 /etc/rc.d/localpkg 本身,而不是本地脚本。使用 -x
标志运行脚本进行调试。
共享库是一块编译代码,为其他编译代码提供通用函数。共享库旨在被尽可能多的不同程序重用。例如,许多程序必须对数据块生成哈希或加密校验和。如果每个程序都必须包含自己的哈希代码,程序将更难编写,更难维护。更重要的是,如果程序实现哈希的方式略有不同,程序将存在互操作性问题,程序作者需要学习很多关于哈希的知识才能使用它们。通过使用共享库(在本例中为 libcrypt
),程序可以访问哈希生成函数,而不会出现任何兼容性和维护问题。这减少了磁盘和内存中的平均程序大小,但代价是复杂性。
共享库具有人性化的名称、版本号和相关文件。人性化的名称通常(但并非总是)与关联的文件相似。例如,名为 libjail 的共享库的版本1位于 /lib/libjail.so.1 文件中。另一方面,主Kerberos库的版本11位于 /usr/lib/libkrb5.so.11 文件中。版本编号从0开始。
从历史上看,当对库的更改使其与库的早期版本不兼容时,版本号会递增。例如,libjail.so.0变成了libjail.so.1。FreeBSD团队不会对这些版本进行升级,除非在发布周期开始时(见第18章)。每个库都有一个没有版本的库名符号链接,指向库的最新版本。例如,您会发现 /usr/lib/libwres.so 实际上是一个指向 /usr/lib/libfres.s.10 的符号链接。这使得编译软件变得更加容易,因为软件只需要查找通用库文件,而不是该库的特定版本。
FreeBSD的主要库支持 symbol versioning (符号版本控制),这使得共享库支持多个编程接口。通过符号版本控制,共享库为每个程序提供程序所需的库版本。如果你有一个需要库版本2的程序,版本3也会支持这些功能。
FreeBSD支持符号版本控制并不意味着Ports Collection中的所有软件都支持它。您必须警惕库版本问题。
那么,程序如何获得它所需的共享库呢?FreeBSD使用 ldconfig(8)
和 rtld(1)
根据需要提供共享库,但也为您提供了一些人性化的工具来调整和管理共享库的处理。
rtld(1)
可能是最容易理解的程序,至少从系统管理员的角度来看是这样。每当程序启动时,rtld(8)
都会检查程序需要哪些共享库。rtld(8)
程序搜索库目录,查看这些库是否可用,然后将库与程序链接,以便一切正常。您根本无法直接使用 rtld(1)
做太多事情,但它提供了将共享库连接在一起的重要粘合剂。
ldconfig(8)
每次运行任何动态链接程序时,系统都不会在整个硬盘上搜索任何看起来像共享库的东西,而是使用 ldconfig(8)
维护共享库目录列表。(旧版本的FreeBSD在系统上构建了一个实际库的缓存,但现代版本只保留了一个目录列表来检查共享库。)如果程序找不到你知道在你的系统上的共享库,这意味着 ldconfig(8)
不知道这些共享库所在的目录。要查看 ldconfig(8)
当前找到的库,请运行 ldconfig -r
:
xxxxxxxxxx
# ldconfig -r
/var/run/ld-elf.so.hints:
search directories: /lib:/usr/lib:/usr/lib/compat:/usr/local/lib:/usr/
local/lib/perl5/5.24/mach/CORE
0:-lcxxrt.1 => /lib/libcxxrt.so.1
1:-lalias.7 => /lib/libalias.so.7
2:-lrss.1 => /lib/librss.so.1
3:-lkiconv.4 => /lib/libkiconv.so.4
4:-lpjdlog.0 => /lib/libpjdlog.so.0
--snip--
使用 -r
标志,ldconfig(8)
列出了共享库目录中的每个共享库。我们首先看到搜索到的目录列表,然后看到这些目录中的各个库。我的主邮件服务器有170个共享库;我的主网络服务器244;我的桌面,531。
如果程序在启动时因无法找到共享库而死亡,则该库将不在此列表中。然后,您的问题相当于将所需的库安装到共享库目录中,或将库目录添加到搜索的目录列表中。您可以将所需的每个共享库复制到 /usr/lib ,但这会使系统管理变得非常困难——就像文件柜一样,所有东西都放在P for paper下。从中长期来看,将目录添加到共享库列表是一个更好的主意。
如果您添加了共享库的新目录,则必须将其添加到 ldconfig(8)
搜索列表中。检查 /etc/defaults/rc.conf 中的 ldconfig(8)
条目:
xxxxxxxxxx
ldconfig_paths="/usr/lib/compat /usr/local/lib /usr/local/lib/compat/pkg"
ldconfig_local_dirs="/usr/local/libdata/ldconfig"
ldconfig_paths
变量列出了库的常见位置。虽然开箱即用的FreeBSD没有 /usr/local/lib 目录,但大多数系统在安装后不久就会有一个目录。同样,与旧版本FreeBSD兼容的库位于 /usr/lib/compat 中。存储由软件包安装的旧版本库的位置是 /usr/local/lib/compat/pkg 。默认情况下会搜索 /lib 和 /usr/lib 目录,但此变量中的路径是共享库的常见位置。
ports和packages使用 ldconfig_local_dirs
变量将其共享库放入搜索列表,而不只是将所有内容转储到 /usr/local/lib 中。软件包可以在此目录中安装文件。该文件以包命名,并包含一个包含包安装的库的目录列表。ldconfig
程序检查这些目录中的文件,读取文件中的路径,并将其视为额外的库路径。例如,Perl 5软件包在 /usr/local/lib/perl5/5.24/mach/CORE 中安装共享库。该port还安装了一个名为 /usr/local/libdata/ldconfig/perl5 的文件,其中只有一行包含此路径。ldconfig启动脚本将这些文件中的目录添加到其检查共享库的位置列表中。
/USR/LOCAL/LIB与PER-PORT库目录 /usr/local/lib 不是专门用于通过端口和软件包安装的库吗?为什么不把所有共享库都放在那个目录中呢?大多数端口都是这样做的,但有时有一个单独的目录会使维护更简单。例如,我的笔记本电脑上安装了Python 2.7,/usr/local/lib/python27 包含647个文件!将所有这些文件转储到 /usr/local/lib 会淹没我的非Python库,使我更难找到只有一两个共享库的端口安装的文件。 要将您的共享库目录添加到搜索列表中,请将其添加到 /etc/rc.conf 中的ldconfig_paths中,或在 /usr/local/libdata/ldconfig 中创建一个列出您的目录的文件。要么奏效。添加目录后,该目录中的库立即可用。
ldconfig(8)
和奇怪的库共享库有一些你应该理解的边缘情况,还有更多你真的不必担心的情况。这些包括用于不同二进制类型的库和用于其他架构的库。
FreeBSD支持两种不同格式的二进制文件,a.out和ELF。系统管理员不需要知道这些二进制类型的详细信息,但你应该知道ELF二进制文件是现代标准,早在1998年就成为FreeBSD 3.0版本的标准。FreeBSD的旧版本使用a.out。作为一种类型编译的程序不能使用另一种类型的共享库。虽然a.out二进制文件在很大程度上已经消失,但支持它们的成本非常低,以至于这种支持从未被删除。ldconfig(8)
为a.out和ELF二进制文件维护单独的目录列表,您可以从 /etc/rc.d/ldconfig 的输出中看到。您可以在 rc.conf 中找到 ldconfig(8)
和a.out库的单独配置选项。您几乎不可能需要a.out程序。
另一个奇怪的情况是,当你在64位FreeBSD安装上运行32位二进制文件时。当您运行amd64安装并希望使用旧版本FreeBSD的程序时,这是最常见的。64位二进制文件不能使用32位库,因此 ldconfig(8)
为它们保留了一个单独的目录列表。您也可以在 rc.conf 中找到配置这些目录的选项。不要混合使用32位和64位库!
一些硬件平台,如ARM,具有用于软浮点运算的特殊版本的库。您还可以找到这些目录的 rc.conf 选项,指向第三组目录。
简而言之,不要将不寻常的库和标准库混合使用。结果会让FreeBSD感到困惑,这反过来又会让你心烦意乱。
如果你是系统管理员,FreeBSD内置的共享库配置系统运行良好,但如果你只是一个没有root访问权限的低级用户,它就无法运行。此外,如果你有自己的个人共享库,你可能不希望它们在全球范围内可用。系统管理员当然不想冒生产程序链接到随机用户拥有的库的风险!这就是 LD_LIBRARY_PATH
发挥作用的地方。
每次运行 rtld(1)
时,它都会检查环境变量 LD_LIBRARY_PATH
。如果此变量中有目录,它会检查这些目录中是否有共享库。这些目录中的任何库都作为程序的选项包含在内。您可以在 LD_LIBRARY_PATH
中指定任意数量的目录。例如,如果我想做一些测试,并在下次运行程序时使用 /home/mwlucas/lib 和 /tmp/testlibs 中的库,我只需按如下方式设置变量:
xxxxxxxxxx
# setenv LD_LIBRARY_PATH /home/mwlucas/lib:/tmp/testlibs
您可以在登录时通过在 .cshrc 或 .login 中输入正确的命令来自动设置此设置。
同样,LD_PRELOAD
环境变量允许您首先加载特定的库。您已经通过在 LD_PRELOAD
中提供自定义libc的完整路径来测试它。当 rtld(1)
运行时,它从 LD_PRELOAD
获取库,并忽略提供相同符号的后续库。
LD环境变量与安全
使用 LD_LIBRARY_PATH
或 LD_PRELOAD
是不安全的。如果你将这个变量指向一个过于容易访问的目录,你的程序可能会链接到任何人放在那里的任何东西。LD_LIBRARY_PATH
变量会覆盖共享库目录列表,因此,如果有人可以将任意文件放入您的库目录中,他们就可以接管您的程序。因此,setuid和setgid程序会忽略这些变量。
最后,还有一个问题是程序需要什么库才能正确运行。使用 ldd(1)
获取此信息。例如,要发现Emacs需要什么库,请输入以下命令:
xxxxxxxxxx
# ldd /usr/local/bin/emacs
/usr/local/bin/emacs:
libtiff.so.5 => /usr/local/lib/libtiff.so.5 (0x800a78000)
libjpeg.so.8 => /usr/local/lib/libjpeg.so.8 (0x800cf1000)
libpng16.so.16 => /usr/local/lib/libpng16.so.16 (0x800f63000)
libgif.so.7 => /usr/local/lib/libgif.so.7 (0x80119d000)
libXpm.so.4 => /usr/local/lib/libXpm.so.4 (0x8013a6000)
libgtk-3.so.0 => /usr/local/lib/libgtk-3.so.0 (0x801600000)
--snip--
这个输出告诉我们Emacs需要的共享库的名称以及包含这些库的文件的位置。如果你的程序找不到必要的库,ldd(1)
告诉你。当你试图运行它时,程序本身会公布第一个丢失的共享库的名称,但 ldd(1)
会给你完整的列表,这样你就可以使用搜索引擎来查找所有丢失的库。
在 ldconfig(8)
和 ldd(1)
之间,您应该为管理FreeBSD系统上的共享库做好充分准备。
偶尔,你会发现一个软件,你想用系统其他部分不使用的特定共享库运行。例如,FreeBSD的标准C库是libc。你可以有一个libc的第二个副本,其中包含专门为特定程序提供的特殊函数,你可以让只有该程序使用特殊的libc,而其他所有程序都使用标准的libc。FreeBSD允许您更改任何应用程序获得的任何共享库。这听起来很奇怪,但它在各种边缘情况下都非常有用。开发人员使用此功能在将代码推送到整个系统之前对其进行小规模测试。使用 /etc/libmap.conf 和 /usr/local/etc/libmap.d/ 中的文件告诉 rtld(1)
对客户端程序撒谎。
虽然 libmap.conf 条目对于开发软件很有用,但您也可以使用它们全局替换库。通过软件包安装的某些视频卡驱动程序要求您使用它们的驱动程序,而不是某些系统库。一些Nvidia驱动程序希望提供libGL图形功能。不要覆盖一切所依赖的libGL包:相反,请重新映射该库。您可以为整个系统、单个程序名称或特定完整路径上的程序配置库替换。
libmap文件(libmap.conf 或 /usr/local/etc/libmap.d/ 中的文件)有两列。第一列是程序请求的共享库的名称;第二列是提供共享库。所有更改都会在下次执行程序时发生;不需要重新启动或守护进程重新启动。例如,这里我们告诉系统,每当任何程序请求libGL时,都要为其提供英伟达版本的库。这些全局覆盖必须首先出现在 libmap.conf 中:
xxxxxxxxxx
libGL.so libGL-NVIDIA.so
libGL.so.1 libGL-NVIDIA.so.1
“我可以要libGL.so.1吗?” “当然,这是libGL-NVIDIA.so.1。”
全局重新映射库是一个相当大胆的步骤,可能会让其他系统管理员谈论你,但逐个程序重新映射库的野心要小得多,而且更有可能解决比它产生的问题更多的问题。只需在重新映射语句之前在方括号中指定所需的程序。如果按完整路径指定程序,则只有按完整路径调用程序时,重新映射才有效。如果只给出名称,则只要运行该名称的任何程序,重新映射就会工作。例如,在这里我们重新映射 emacs(1)
,以便在通过其完整路径调用时使用Nvidia的库而不是系统库:
xxxxxxxxxx
[/usr/local/bin/emacs]
libGL.so libGL-NVIDIA.so
libGL.so.1 libGL-NVIDIA.so.1
你如何证明这是有效的?那么,检查 ldd(1)
:
xxxxxxxxxx
# ldd /usr/local/bin/emacs | grep libGL
libGL.so.1 => /usr/local/lib/libGL-NVIDIA.so.1 (0x80ad60000)
您可以看到,当 /usr/local/bin/emacs 请求libGL.so.1时,rtld(1)
将其附加到libGL-NVIDA.so.1。但是,我们指定了Emacs二进制文件的完整路径,因此我们需要按其完整路径调用程序。尝试在Emacs上使用 ldd(1)
,而不按其完整路径调用它:
xxxxxxxxxx
# cd /usr/local/bin
# ldd emacs | grep libGL
libGL.so.1 => /usr/local/lib/libGL.so.1 (0x0x8056fa000)
通过转到 /usr/local/bin 并直接在Emacs上运行 ldd(1)
而不必指定完整路径,rtld看不到 emacs(1)
二进制文件的完整路径 /etc/libmap.conf 规定仅对 /usr/local/bin/emacs 的完整路径使用Nvidia的库。当纯裸 emacs
请求libGL.so.1时,它会得到它所请求的。
如果你想让一个程序使用备用库,无论它是通过完整路径还是基名称调用,只需在方括号中给出程序名称,而不是全名:
xxxxxxxxxx
[emacs]
libGL.so libGL-NVIDIA.so
libGL.so.1 libGL-NVIDIA.so.1
同样,您可以通过列出目录名后跟斜线来为目录中的所有程序选择一个备用库。在这个 /usr/local/etc/libmap.d/oracle 文件中,我们强制目录中的所有程序使用备用库:
xxxxxxxxxx
[/opt/oracle/bin/]
libc.so.7 libc-special.so.2
使用 libmap.conf 可以任意重新映射共享库。开发人员使用此功能来测试代码。ports使用此功能覆盖某些程序的库。你也会发现它的用途。
传统软件是为特定操作系统编写的,只在该操作系统上运行。许多人建立了健康的商业改变了软件,使其可以在另一个系统上运行,这一过程称为移植(porting)。作为一名管理员,您有几种不同的方法来使用为FreeBSD以外的平台编写的软件。最有效的方法是重新编译源代码以在FreeBSD上本机运行。如果这不可能,您可以在模拟器(如Wine)下运行非本机软件,或者通过重新实现软件本机平台的应用程序二进制接口(application binary interface —— ABI)来运行。
许多FreeBSD软件包实际上是最初为其他平台设计的软件的移植。(这就是为什么它被称为Ports Collection。)为Linux、Solaris或其他类Unix操作系统编写的软件通常可以从源代码重新编译,只需很少或根本不需要修改,就可以在FreeBSD上完美运行。只需获取源代码并在FreeBSD机器上构建,您就可以在BSD上本机运行外部软件。
当平台相似时,重新编译效果最佳。类Unix平台应该相当相似,不是吗?例如,FreeBSD和Linux提供了许多类似的系统功能;两者都是基于标准的C函数构建的,都使用类似的工具,都使用GCC编译器,等等。然而,多年来,各种类Unix操作系统已经分道扬镳。每个版本的Unix都实现了新功能、新库和新函数,如果一个软件需要这些功能,它就不会在其他平台上构建。POSIX标准的引入部分是为了缓解这个问题。POSIX定义了可接受的最小Unix和类Unix操作系统。仅使用符合POSIX标准的系统调用和库编写的软件应该可以立即移植到任何其他符合POSIX的操作系统,大多数Unix供应商都符合POSIX。问题是确保开发人员遵守POSIX。许多开源开发人员只关心让他们的软件在他们喜欢的平台上运行。许多特定于Linux的软件不仅不符合POSIX标准,还包含一系列通常称为 Linuxism 的独特功能。而且POSIX专用代码没有利用操作系统提供的任何特殊功能。
平心而论,FreeBSD也有FreeBSDisms,比如高效的数据读取系统调用 kqueue(2)
。其他类Unix操作系统使用 select(2)
和 poll(2)
,或者实现自己的系统调用。应用程序开发人员问自己,他们是否应该使用 kqueue(2)
,这会使他们的软件在FreeBSD上速度极快,但在其他任何地方都毫无用处,或者他们应该使用 select(2)
和 poll(2)
让他们的软件可以在任何地方工作,尽管速度较慢。开发人员可以投入更多时间,平等地支持 kqueue(2)
、select(2)
和 poll(2)
以及任何其他特定于操作系统的变体,但虽然这让用户满意,但从开发人员的角度来看,这相当糟糕。
FreeBSD走了一条中间道路。如果一个软件可以重新编译以在FreeBSD上正常运行,那么ports团队通常会做到这一点。如果软件需要小补丁,ports团队会将补丁包含在ports中,并将其发送给软件开发人员。大多数软件开发人员很乐意接受允许他们支持另一个操作系统的补丁。即使他们可能没有可供测试的操作系统,或者他们可能不熟悉该操作系统,通常也会接受来自信誉良好的来源的外观不错的补丁。
如果软件需要大量的重新设计才能在FreeBSD上运行,或者源代码根本不可用,我们可以尝试模拟。仿真器将一个操作系统的系统和库调用转换为本地操作系统提供的等效调用,因此在仿真器下运行的程序认为它们是在本地系统上运行的。然而,翻译所有这些调用会产生额外的系统开销,从而影响程序的速度和性能。
FreeBSD支持各种各样的模拟器,其中大多数都在 /usr/Ports/emulators 下的Ports集合中。在大多数情况下,模拟器对教育或娱乐很有用。如果你有一个旧的Commodore 64游戏,你很想再玩一次,请安装 /usr/ports/emulators/frodo 。(请注意:在现代FreeBSD系统上安装C64软盘将教会你更多关于磁盘的知识,而不是人类所知道的。) /usr/ports/emulators/dolphin-emu 中有一个任天堂GameCube模拟器, /usr/ports/emulators/simh 中有一台PDP-11模拟器,以此类推。
仿真器虽然很酷,但对服务器来说并不是很有用,所以我们不会深入介绍它们。
除了重新编译和模拟之外,运行外部程序的最后一种选择是FreeBSD最著名的:应用程序二进制接口(application binary interface —— ABI)重新实现(reimplementation)。ABI是内核的一部分,为程序提供服务,包括从管理声卡到读取文件、在屏幕上打印到启动其他程序的所有内容。就程序而言,ABI是操作系统。通过在本机操作系统上完全实现不同操作系统的ABI,并提供该操作系统使用的用户空间库,您可以像在本机平台上一样运行非本机程序。
虽然ABI的重新实现经常被称为仿真,但事实并非如此。在实现ABI时,FreeBSD并没有模拟系统调用,而是为应用程序提供本机实现。没有程序运行将系统调用转换为FreeBSD等效程序,也没有努力将用户空间库转换为FreeBSD库。出于同样的原因,说“FreeBSD实现了Linux”是不正确的。当这种技术被创建时,没有一个词可以描述它,即使在今天,也没有一个很好的描述。你可以说FreeBSD实现了Linux系统调用接口,并支持将二进制文件定向到适当的系统调用接口。你最常听到它被称为一种 mode,比如“Linux mode”。
ABI重新实现的问题是重叠。许多操作系统都包含具有通用名称的系统调用,如read、write等。FreeBSD的 read(2)
系统调用的行为与微软的read()系统调用非常不同。当程序使用read()调用时,FreeBSD如何知道它想要哪个版本?你可以给你的系统调用不同的名称,但这样你就违反了POSIX并混淆了程序。FreeBSD通过提供多个ABI并通过branding使用哪个ABI来解决这个问题。
操作系统通常具有执行程序的系统功能。当内核向该执行引擎发送程序时,它会运行该程序。
几十年前,BSD(当时的Unix)程序执行系统调用被更改为对以 #!/bin/sh
开头的程序进行特殊检查,并使用系统shell而不是执行引擎运行它们。BSD将这一想法推向了逻辑的极端:它的执行引擎包括一系列不同的二进制类型。每个程序的二进制类型都将其指向正确的ABI。因此,FreeBSD系统可以实现多个ABI,将它们分开,并支持来自各种不同操作系统的程序。
这个系统的优点是开销很小。既然FreeBSD无论如何都必须决定如何运行程序,为什么不让它决定使用什么ABI呢?毕竟,不同操作系统的二进制文件都有略微不同的特征,FreeBSD可以用这些特征来识别它们。FreeBSD只是让这个过程对最终用户透明。二进制文件的标识称为其 branding 。FreeBSD二进制文件被标记为FreeBSD,而其他操作系统的二进制文件则被适当地标记为FreeBSD。
由于这种ABI重定向,FreeBSD可以运行Linux二进制文件,就像它们是本机编译的一样。旧版本的FreeBSD也可以运行OSF/1、SCO和SVR4二进制文件,但对这些平台的需求急剧下降。如果你需要其中之一,你可以尝试在虚拟机上运行旧版本的BSD。
Linux模式,也称为 Linuxulator,非常全面,因为Linux的源代码是可用的,其ABI也有很好的记录。事实上,Linux模式运行良好,Ports Collection中的许多程序都依赖于它。
虽然ABI的重新实现解决了一个主要问题,但程序需要的不仅仅是ABI。如果没有共享库、支持程序和其他用户空间,大多数程序将无法正常运行。无论您使用哪种ABI,您都必须能够访问该平台的用户区。
如果你想使用Ports Collection中提供的Linux软件,请安装端口。这会自动安装任何用户空间依赖项。
如果你想运行一个任意的Linux软件,你必须先安装一个Linux用户区。FreeBSD通常有几个不同的Linux用户区作为软件包提供。要查看可用内容,请在包数据库中搜索linux_base。
xxxxxxxxxx
# pkg search linux_base
linux_base-c6-6.9_2 Base set of packages needed in Linux mode (Linux CentOS 6.9)
linux_base-c7-7.3.1611_6 Base set of packages needed in Linux mode (Linux CentOS 7.3.1611)
这个版本的FreeBSD有两个Linux用户区:一个基于CentOS 6.9,一个基于CentOS 7.3。Linux发行版在未来可能会发生变化,具体取决于Linux的发展方向。
检查你的软件运行在什么版本的Linux上。为你的应用程序安装最合适的用户区。FreeBSD在 /usr/compat/linux 下安装Linux用户区(userlands)。
该port还加载Linux模式内核模块。要在启动时自动加载该模块,请使用以下 rc.conf 条目:
xxxxxxxxxx
linux_enable="YES"
就是这样!Linux模式不是一个合适的服务,因为你无法重新启动它或获取状态,所以你无法用 service(8)
配置它。运行 /etc/rc.d/abi-start
以激活Linux模式,而无需重新启动。
在我们开始运行Linux程序之前,让我们先来探索一下用户空间。
正如 linux.ko
内核模块提供Linux ABI一样,Linuxulator需要非常少的Linux用户空间。看看 /usr/compat/linux 下,你会看到类似以下的内容:
xxxxxxxxxx
# ls
bin etc lib64 proc selinux sys var
dev lib opt sbin srv usr
看起来很像FreeBSD的/目录的内容,不是吗?如果你稍微浏览一下,你会发现,一般来说,/usr/compat/linux 的内容与你的核心FreeBSD安装相当。你会在两者中发现许多相同的程序。
Linux爱好者会立即注意到,与典型的Linux安装相比,任何Linux_base port的内容都是最少的。这是因为每个基于Linux的软件包只安装运行所需的东西。FreeBSD的Linux软件包将极简主义的BSD哲学强加于Linux软件。
只要有可能,Linux模式下的程序就会试图保持在 /usr/compat/linux 下,这有点像一个弱jail(见第22章)。当您执行调用其他程序的Linux二进制文件时,Linux ABI首先在 /usr/compat/linux 下检查该程序。如果程序不存在,Linux模式将在主系统中显示。例如,假设您有一个调用 ping(8)
的Linux二进制文件。ABI首先在 /usr/compat/linux/ 下搜索ping程序;在撰写本文时,它将一无所获。ABI然后检查主FreeBSD系统,找到 /sbin/ping 并使用它。Linuxulator大量使用这种回退行为来减少Linux模式用户区的大小。
或者,假设一个Linux二进制文件想要调用 sh(1)
。Linux ABI在 /usr/compat/linux 下检查,找到 /usr/compat/linux/bin/sh ,并执行该程序,而不是FreeBSD原生 /bin/sh 。
Linux使用进程文件系统或 procfs。FreeBSD几十年前就消除了procfs作为默认安全风险,但一些Linux程序会需要它。使用需要procfs的Linux软件意味着接受固有的风险。FreeBSD提供了一个Linux procfs,名为 linprocfs(5)
。
要启用 linprocfs(5)
,请在安装Linuxulator后将以下内容添加到 /etc/fstab
中:
xxxxxxxxxx
linproc /compat/linux/proc linprocfs rw 0 0
FreeBSD按需加载文件系统内核模块,因此输入 mount /compat/linux/proc
以激活 linprocfs(5)
,而无需重新启动。
许多Linux程序也期望共享内存为 /dev/shm 。FreeBSD可以用 tmpfs(5)
来模拟这一点。
xxxxxxxxxx
tmpfs /compat/linux/dev/shm tmpfs rw,mode=1777 0 0
输入 mount /compat/linux/dev/shm
,共享内存设备就绪。
现在您已经知道在Linux模式下安装了什么,测试Linux功能很容易。运行Linux shell并询问它在哪个操作系统上运行:
xxxxxxxxxx
# /usr/compat/linux/bin/sh
sh-4.1# uname -a
Linux storm 2.6.32 FreeBSD 12.0-CURRENT #0 r322672: Fri Aug 17 16:31:34 EDT
2018 x86_64 x86_64 x86_64 GNU/Linux
sh-4.1#
当我们问这个命令提示符运行在什么类型的系统上时,这个shell会回答说,这是一个运行在Linux 2.6.32内核FreeBSD之上的Linux系统。很酷,是吧?
然而,请记住,Linux模式并不是一个完整的Linux用户区。您不能在默认的Linuxulator安装中交叉编译软件。你只能执行非常基本的任务。
给软件二进制文件打branding 比给牛打branding 更容易,但远不如冒险。大多数现代类Unix二进制文件都是ELF格式,其中包括注释空间。这就是品牌的所在。FreeBSD根据二进制文件上的品牌为每个程序分配一个ABI。如果一个二进制文件没有品牌,则假定它是FreeBSD二进制文件。
使用brandelf查看和更改brandelf(1):
xxxxxxxxxx
# brandelf /bin/sh
File '/bin/sh' is of brand 'FreeBSD' (9).
这并不奇怪。这是一个FreeBSD二进制文件,因此它将在FreeBSD ABI下执行。让我们尝试一个Linux二进制文件:
xxxxxxxxxx
# brandelf /usr/compat/linux/bin/sh
File '/usr/compat/linux/bin/sh' is of brand 'Linux' (3).
使用 -l
标志查看FreeBSD支持的brands:
xxxxxxxxxx
# brandelf -l
known ELF types are: FreeBSD(9) Linux(3) Solaris(6) SVR4(0)
如果你有一个无法运行的外国程序,请检查它的品牌。如果它没有品牌或品牌不正确,你可能已经发现了你的问题:FreeBSD正试图在原生FreeBSD ABI下运行该程序。通过使用 brandelf -t
手动设置品牌来更改这一点。例如,要将程序命名为Linux,请执行以下操作:
xxxxxxxxxx
# brandelf -t Linux /usr/local/bin/program
下次您尝试运行该程序时,FreeBSD将在Linux ABI和Linux用户区下运行它,程序应按预期工作。
您还可以使用sysctls设置 fallback brand 。所有FreeBSD二进制文件都会正确标记,但您复制到主机的随机程序可能不会。未标记的二进制文件将使用所选的回退品牌进行处理。sysctl kern.elf32.fallback_brand
为32位主机提供回退品牌,而 kern.elf64.fallback_brand
为64位主机设置fallback brand。这个sysctl接受brand的数字标识符,对于Linux来说是3。
xxxxxxxxxx
# sysctl kern.elf64.fallback_brand=3
现在,您应该能够运行Linux程序,而无需任何进一步的配置。剩下的就是Linux模式的小烦恼和小过失。可悲的是,正如我们接下来将说明的那样,其中有一些。
许多Linux程序只能作为ports使用。Ports Collection足够智能,可以意识到一个软件需要Linux模式,并选择合适的Linux进行安装。一个常见的选择是Skype。安装此port会触发安装正确的Linux用户区。
拥有最小的Linux用户空间的缺点是,任何port都会有一大堆依赖关系。其中一些将是FreeBSD二进制文件,另一些是Linux。我建议使用port的 make missing
命令来显示缺失的依赖关系,甚至可以像第16章中讨论的那样从包中自动安装依赖关系。一旦安装了所有必需的软件包,安装了linprocfs和Linux共享内存设备,并且加载了所有内核模块,安装Skype就像 make install clean
一样简单。
Linux模式不是Linux,没有什么比程序崩溃更清楚的了。许多程序都有神秘的错误消息,Linux模式可能会进一步掩盖它们。您需要能够挖掘错误消息并查看真正错误的工具。
truss(1)
我找到的调试Linux模式的最好工具是 truss(1)
,它是FreeBSD系统调用跟踪器。有人告诉我,用truss(1)
做这件事就像把麦克卡车上的12缸发动机装进大众甲壳虫,但经过深思熟虑,我决定不在乎。它奏效了。一旦你了解了truss(1)
,你会想知道没有它你是如何生活的。
truss(1)
程序准确地识别程序进行的系统调用以及每次调用的结果。记住,系统调用是程序到内核的接口。当程序试图与网络通信、打开文件甚至分配内存时,它会进行系统调用。这使得truss(1)
成为了解程序失败原因的一个很好的方法。程序进行大量的系统调用,这意味着truss(1)
生成大量的数据,使得使用truss(1)
进行调试成为 script(1)
的一个很好的候选。
让我们运行Skype。
xxxxxxxxxx
$ skype
Segmentation fault (core dumped)
好消息是:该程序正在运行!坏消息是,它卡住了什么。我发现最常见的错误是缺少库、文件和目录,但具体是什么?truss(1)
的输出可以告诉我。启动 script(1)
会话,在truss(1)
下运行程序,然后结束脚本。
您的脚本文件将长达数百或数千行;你怎么可能找到问题?搜索错误消息的相关部分或字符串ERR。在这种情况下,我搜索了字符串目录,并在输出末尾附近找到了以下内容:
xxxxxxxxxx
$ truss skype
--snip--
linux_open("/usr/local/Trolltech/Qt-4.4.3-static/lib/tls/i686/sse2/libasound.so.2",0x0,00)
ERR#-2 'No such file or directory'
linux_stat64("/usr/local/Trolltech/Qt-4.4.3-static/lib/tls/i686/sse2",0xffffb248) ERR#-2 'No
such file or directory'
linux_open("/usr/local/Trolltech/Qt-4.4.3-static/lib/tls/i686/libasound.so.2",0x0,00) ERR#-2 'No such file or directory'
--snip--
啊哈!Skype找不到所需的库。包维护者可能错过了这些,或者我可能搞砸了。检查主机上是否存在库。如果没有,您需要安装它们。或许存在一个port。或者我可能需要安装Linux软件包。
如果你需要的Linux库或软件没有port,你有几个选择。一个是为该软件创建一个port。Ports是参与FreeBSD社区的好方法。然而,如果你的目标是让软件启动并运行,这样你就可以继续你的一天,你需要从源RPM安装合适的Linux软件。不过要注意:一旦你安装了Ports Collection之外的东西,你就需要手动维护它。
找到所需软件的RPM。确保软件包版本与linux_base中安装的版本匹配。如果你的FreeBSD主机使用CentOS 7.3.1611,那么为你丢失的库找到CentOS 8包是没有用的。下载RPM。
商用LINUX软件与LINUX模式 请记住,商业软件供应商不支持FreeBSD的Linux模式下的Linux软件。如果你在一个有服务级别协议的工业环境中,并且有支付罚款的风险,在使用Linux模式之前要非常仔细地考虑。商业软件的主要好处是当它崩溃时有人可以责怪,但FreeBSD的Linux模式消除了这一好处。
假设我的生活发生了可怕的转折,我需要在Linux模式下运行Supermin。我找到并下载了包文件,然后用 tar(1)
安装它。FreeBSD的基于libarchive的tar可以破解打开的RPM文件,就像它做其他事情一样。
xxxxxxxxxx
# cd /compat/linux
# tar -xf /home/mwl/supermin-5.1.16-4.el7.x86_64.rpm
现在我可以找到下一个缺失的依赖项了。一旦我有了整个依赖关系列表,我将编写一个port来为其他人节省这种乏味。
当你运行FreeBSD的amd64平台时,你最终会发现一些只适用于i386平台的软件。如果你的内核有 COMPAT_FREEBSD32
选项(已经在GENERIC中),FreeBSD/amd64可以运行所有FreeBSD/i386软件。你不能为FreeBSD/i386软件使用FreeBSD/amd64共享库。如果要在64位计算机上运行复杂的32位程序,则必须提供所需库的32位版本。这得到了很好的支持;如果你检查 rc.conf ,你会发现 ldconfig(8)
的选项 ldconfig32_paths
和 ldconfiglocal32_drs
。这些选项专门用于告诉amd64系统在哪里可以找到32位库。FreeBSD在安装介质上包含32位库。
此外,FreeBSD可以运行旧版本FreeBSD的软件。GENERIC内核包括所有系统调用,但您仍然需要基本系统库。这些库以包的形式提供,每个主要的FreeBSD版本都有一个。每个包都命名为 compat,后面跟着一个版本号,以 x 结尾。如果您必须运行FreeBSD 8二进制文件,请安装compat8x包。compat包包括64位和32位库。
如果你需要运行非i386或amd64的二进制文件,你甚至可以使用 binmisctl(8)
在运行非x86二进制文件时自动启动适当的模拟器。
虽然关于软件管理总是有更多的知识需要学习,但你现在已经掌握了足够的知识。让我们继续学习升级FreeBSD。