26.6.从源代码更新 FreeBSD ========================= 与二进制更新相比,通过从源代码编译来更新 FreeBSD 具有一些优势。可以用不同的参数来编译代码,以利用特定的硬件。基本系统的某些部分可以用非默认的配置来编译,或者完全丢弃不需要或不想要的地方。与安装二进制更新相比,编译过程需要更长的时间来更新系统,但可完全定制,以产生自定义的 FreeBSD 版本。 26.6.1.快速开始 --------------- 这是对通过从源代码编译来更新 FreeBSD 的典型步骤的快速参考。后面的章节会更详细地介绍这个过程。 **警告** 当从 `mergemaster(8) `__ 切换到 `etcupdate(8) `__ 时,第一次运行可能会错误地合并变化,产生虚假的冲突。为了防止这种情况,在更新源代码和编译新的世界之 **前**\ ,请执行以下步骤: .. code:: shell-session # etcupdate extract ① # etcupdate diff ② ① 自举旧的 **/etc** 文件的数据库;更多信息见 `etcupdate(8) `__\ 。 ② 在自举后检查差异。修剪任何不再需要的本地修改,以减少在未来更新中发生冲突的机会。 .. - 更新和编译 .. code:: shell-session # git pull /usr/src ① check /usr/src/UPDATING ② # cd /usr/src ③ # make -j4 buildworld ④ # make -j4 kernel ⑤ # shutdown -r now ⑥ # etcupdate -p ⑦ # cd /usr/src ⑧ # make installworld ⑨ # etcupdate -B ⑩ # shutdown -r now ⑪ ① 获取最新版本的源代码。关于获取和更新源代码的更多信息,请参见\ `更新源代码 `__\ 。 ② 检查 **/usr/src/UPDATING**\ ,看看在从源代码编译之前或之后是否需要任何手动步骤。 ③ 切换到源代码目录。 ④ 编译世界(除了内核以外的一切)。 ⑤ 编译和安装内核。这等同于 ``make buildkernel installkernel``\ 。 ⑥ 重新启动系统到新的内核。 ⑦ 在安装世界之前,更新和合并 **/etc/** 中所需要的配置文件。 ⑧ 切换到源代码目录。 ⑨ 安装世界。 ⑩ 更新和合并 **/etc/** 中的配置文件。 ⑪ 重新启动系统以使用新编译的世界和内核。 26.6.2. 为从源代码更新做准备 ---------------------------- 阅读 **/usr/src/UPDATING**\ 。在更新之前或之后必须执行的任何手动步骤在这个文件中都有说明。 26.6.3. 更新源代码 ------------------ FreeBSD 的源代码位于 **/usr/src/**\ 。更新此源代码的首选方法是通过 Git 版本控制系统。验证源代码是否在版本控制之下: .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # cd /usr/src # git remote --v origin https://git.freebsd.org/src.git (fetch) origin https://git.freebsd.org/src.git (push) 这表明 **/usr/src/** 处于版本控制之下,可以用 `git(1) `__ 更新: .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # git pull /usr/src 如果目录最近没有被更新,更新过程可能需要一些时间。在它完成后,源代码是最新的,可以开始下一节所述的编译过程。 **注意** **获得源代码:** 如果输出显示 ``fatal: not a git repository``\ ,说明那里的文件缺失或是用不同的方法安装的。需要重新检出该源代码。 **表 19. FreeBSD 版本和仓库的分支** ================= ================== ========================================================================================================================================================================================================================================================================================== **uname -r 输出** **仓库路径** **说明** ================= ================== ========================================================================================================================================================================================================================================================================================== *X.Y*-RELEASE ``releng/``\ *X.Y* RELEASE 版只附带有关键的安全和错误修复补丁。建议大多数用户使用该分支。 *X.Y*-STABLE ``stable/``\ *X* RELEASE 版加上该分支上的所有额外开发。\ *STABLE* 指的是应用程序二进制接口(ABI)没有改变,因此为早期版本编译的软件仍然可以运行。例如,在 FreeBSD 10.1 上编译的软件仍然可以在后来编译的 FreeBSD 10-STABLE 上运行。STABLE 分支偶尔会有可能影响用户的错误或不兼容,尽管这些通常会很快被修复。 *X*-CURRENT ``main`` 最新的未发布的 FreeBSD 开发版本。CURRENT 分支可能会有重大的错误或不兼容,因此只推荐给高级用户。 ================= ================== ========================================================================================================================================================================================================================================================================================== .. 用 `uname(1) `__ 确定正在使用哪个版本的 FreeBSD: .. code:: shell-session # uname -r 13.2-RELEASE 根据 `FreeBSD 版本和仓库分支 `__\ ,用于更新 ``13.2-RELEASE`` 的源代码的仓库路径是 ``releng/13.2``\ 。这个路径在检出源代码时被使用: .. code:: shell-session # mv /usr/src /usr/src.bak ① # git clone --branch releng/13.2 https://git.FreeBSD.org/src.git /usr/src ② ① 把旧的目录移开。如果这个目录中没有本地的修改,就可以删除它。 ② `FreeBSD 版本和仓库分支 `__\ 的路径会被添加到仓库的链接中。第三个参数是本地系统上源代码的目标目录。 26.6.4.从源代码开始编译 ----------------------- *世界*\ ,即除了内核以外的所有操作系统,都被编译了。这样做首先是为了提供最新的工具来编译内核。然后是内核本身的编译。 .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # cd /usr/src # make buildworld # make buildkernel 编译后的代码被写到 **/usr/obj**\ 。 这些是基本步骤。下面将介绍控制编译的其他选项。 26.6.4.1.执行清洁编译 ~~~~~~~~~~~~~~~~~~~~~ 某些版本的 FreeBSD 联编系统会将先前编译的代码留在临时对象目录 **/usr/obj** 中。这可以通过避免重新编译没有变化的代码来加快以后的联编速度。要强制编译所有内容,可以在开始联编前使用 ``cleanworld``\ : .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # make cleanworld 26.6.4.2.设置作业的数量 ~~~~~~~~~~~~~~~~~~~~~~~ 在多核处理器上增加编译作业的数量可以提高编译速度。用 ``sysctl hw.ncpu`` 来确定核心数。处理器各不相同,不同版本的 FreeBSD 所使用的联编系统也各不相同,因此测试是唯一可以确定不同数量的作业对联编速度有何影响的方法。作为一个开始,可以考虑核心数的一半到两倍之间的数值。作业数量是用 ``-j`` 指定的。 **例 42. 增加编译作业的数量** 用四项作业来编译世界和内核: .. code:: shell-session # make -j4 buildworld buildkernel 26.6.4.3.只编译内核 ~~~~~~~~~~~~~~~~~~~ 如果源代码有变化,必须完成 ``buildworld``\ 。除此之外,可以在任何时候运行 ``buildkernel`` 来编译内核。要想只编译内核: .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # cd /usr/src # make buildkernel 26.6.4.4.编译定制内核 ~~~~~~~~~~~~~~~~~~~~~ 标准的 FreeBSD 内核基于一个叫做 **GENERIC** 的 *内核配置文件*\ 。\ **GENERIC** 内核包括最常用的设备驱动和选项。有时,建立一个定制内核是有用的或必要的,可以添加或删除设备驱动程序或选项以适应特定的需要。 例如,有人在开发一台内存严重受限的小型嵌入式计算机时,可以删除不需要的设备驱动程序或选项,使内核略微缩小。 内核配置文件位于 **/usr/src/sys/arch/conf/**\ ,其中 *arch* 是 ``uname -m`` 的输出。在大多数计算机上,它是 ``amd64``\ ,给出的配置文件目录是 **/usr/src/sys/amd64/conf/**\ 。 **技巧** **/usr/src** 可以被删除或重新创建,所以最好把定制的内核配置文件放在一个单独的目录下,比如 **/root**\ 。将内核配置文件链接到 conf 目录中。如果该目录被删除或覆盖,内核配置文件可以被重新链接到新目录中。 通过复制配置文件 **GENERIC** 可以创建一个自定义的配置文件。在这个例子中,新的定制内核是用于存储服务器的,所以被命名为 **STORAGESERVER**\ : .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # cp /usr/src/sys/amd64/conf/GENERIC /root/STORAGESERVER # cd /usr/src/sys/amd64/conf # ln -s /root/STORAGESERVER . 然后编辑 **/root/STORAGESERVER**\ ,添加或删除设备或选项,如 `config(5) `__ 所示。 定制的内核是通过在命令行上设置 ``KERNCONF`` 到内核配置文件来编译的: .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # make buildkernel KERNCONF=STORAGESERVER 26.6.5.安装已编译的代码 ----------------------- 在 ``buildworld`` 和 ``buildkernel`` 的步骤完成后,安装新的内核和世界: .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # cd /usr/src # make installkernel # shutdown -r now # cd /usr/src # make installworld # shutdown -r now 如果编译了定制内核,\ ``KERNCONF`` 也必须被设置为使用新的定制内核: .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # cd /usr/src # make installkernel KERNCONF=STORAGESERVER # shutdown -r now # cd /usr/src # make installworld # shutdown -r now 26.6.6.完成更新 --------------- 最后几项任务完成了更新。任何修改过的配置文件都将与新版本合并,过时的库被定位并删除,然后系统被重新启动。 26.6.6.1.用 `etcupdate(8) `__ 合并配置文件 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ `etcupdate(8) `__ 是一个管理更新文件的工具,这些文件没有作为安装世界的一部分被更新,比如位于 **/etc/** 的文件。它通过对这些文件的修改与本地版本进行三方合并来管理更新。与 `mergemaster(8) `__ 的交互式提示相比,它还被设计为尽量减少用户的干预。 **注意** 一般来说,\ `etcupdate(8) `__ 在执行中不需要任何特殊的参数。然而,有一个很方便的中间命令,用于检查第一次使用 `etcupdate(8) `__ 时将会做什么: .. code:: shell-session # etcupdate diff 该命令允许用户审计配置的变化。 如果 `etetcupdate(8) `__ 不能自动合并文件,可以通过执行手动交互来解决合并冲突: .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # etcupdate resolve .. **警告** 当从 `mergemaster(8) `__ 切换到 `etcupdate(8) `__ 时,第一次运行可能会不正确地合并变化,产生虚假的冲突。为了防止这种情况,在更新源代码和编译新的世界 **之前**\ ,请执行以下步骤: .. code:: shell-session # etcupdate extract ① # etcupdate diff ② ① 启动库存 **/etc** 文件的数据库,更多信息见 `etcupdate(8) `__\ 。 ② 在启动后检查差异。修剪任何不再需要的本地修改,以减少在未来更新中发生冲突的机会。 26.6.6.2.用 `mergemaster(8) `__ 合并配置文件 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ `mergemaster(8) `__ 提供了一种将对系统配置文件的修改与这些文件的新版本进行合并的方法。\ `mergemaster(8) `__ 是替代的 `etcupdate(8) `__ 的首选方法。使用 ``-Ui``\ ,\ `mergemaster(8) `__ 可自动更新未被用户修改的文件,并安装尚未存在的新文件: .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # mergemaster -Ui 如果一个文件必须被手动合并,一个交互式的显示允许用户选择文件的哪些部分被保留。参见 `mergemaster(8) `__ 以了解更多信息。 26.6.6.3.检查是否有过期的文件和库 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 一些过时的文件或目录在更新后可能仍然存在。可以找到这些文件: .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # make check-old 并删除: .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # make delete-old 一些过时的库也可能保留下来。这些可以用以下方法检测: .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # make check-old-libs 并删除: .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # make delete-old-libs 当库被删除后,仍然使用这些旧库的程序将停止工作。这些程序必须在删除旧库后重新编译或替换。 **技巧** 当知道所有的旧文件或目录都可以安全删除时,可以通过在命令中设置 ``BATCH_DELETE_OLD_FILES``\ 来避免按 ``y`` 和\ ``回车键``\ 来删除每个文件。比如说: .. code:: shell-session # make BATCH_DELETE_OLD_FILES=yes delete-old-libs 26.6.6.4.更新后重新启动 ~~~~~~~~~~~~~~~~~~~~~~~ 更新后的最后一步是重新启动计算机,以便所有的变化都生效。 .. raw:: latex \diilbookstyleinputcell .. code:: shell-session # shutdown -r now