23.7. 重新编译 world

一旦 FreeBSD 特定版本(如: FreeBSD-STABLE 或 FreeBSD-CURRENT)的本地源代码树完成同步, 就可以使用这些源代码树重新编译系统。

做好备份:

无需强调操作 之前 应进行备份的重要性, 尽管重新编译系统是一件简单的事情, 但难免会有一些出现在源代码里的问题而导致系统无法正常运行的情况。

创建并校验备份, 并制作一个可引导的安装介质。 您可能永远不会用到它, 但安全第一嘛!

订阅相关邮件列表:

FreeBSD-STABLE 和 FreeBSD-CURRENT 分支, 从实质上都属于 开发阶段 。 为 FreeBSD 贡献者也都是人, 难免会犯错。

这些错误可能没什么危害, 只会让系统生成新的错误警告。 也可能是灾难性的, 可能导致系统无法启动或破坏文件系统。

当问题发生时, 发送一封 注意 (heads up) 开头的邮件到相关邮件列表, 并描述问题, 并写上受影响的系统。 当问题解决以后再发送一封 已解决 (all clear) 开头的声明邮件。

如果使用 FreeBSD-STABLE 或 FreeBSD-CURRENT 而又不订阅 FreeBSD-STABLE 邮件列表FreeBSD-CURRENT 邮件列表 的用户, 那么完全是自找麻烦。

不要使用 make world 命令:

一些比较早期的文档推荐使用 make world 命令完成这项工作。 然而, 这个命令会跳过很多重要的步骤, 建议只有在您知道自己在做什么的时候才这样做。 绝大部分情况下, 请不要胡乱使用 make world 命令, 您应该使用以下介绍的方式。

23.7.1. 更新系统的标准方法

更新系统之前, 请先阅读 /usr/src/UPDATING 文件, 以便了解重新编译 world 之前需要了解哪些事情或注意事项, 然后再执行以下步骤。

这里假定从一个包含旧编译器、 旧内核、旧 world 和旧配置文件的较早 FreeBSD 版本上的升级步骤。 World 包含了核心系统二进制文件, 库和程序文件。 编译器是 world 的一部分, 但也有一些需要特别注意的问题。

此外, 我们还假定您已获得最新版本的源代码。 如果你还没来得及更新源代码, 请参照 Section 23.6, “同步源代码” 获取同步到最新版本的详细帮助。

从源代码更新系统的步骤有时会比初看上去更麻烦一些, 另一方面, FreeBSD 开发者有时又不得不修改推荐更新步骤, 特别是当出现无法避免的依赖关系的时候。 本节余下部分将介绍目前推荐的更新步骤及其原因。

所有成功的更新操作都必须解决以下这些问题:

  • 旧编译器可能有 bug, 无法编译新内核。 因此, 新内核应该用新编译器构建, 这意味着新内核在构建之前, 新编译器必须先行构建, 但并不意味着在构建新内核之前, 新编译器必须先 安装

  • 新 world 可能依赖新内核的某些功能。 因此, 新 world 安装之前, 新内核必须先安装。

这两个问题就是为什么需要按照 buildworldbuildkernelinstallkernelinstallworld 的顺序来更新的原因, 除此之外还有:

  • 新内核可能无法在旧 world 中正常运行, 因此新 world 必须在新内核安装完成后尽快更新。

  • 一些配置文件需要在安装新 world 之前完成更新, 一些配置文件的更新则会导致旧 world 不能正常工作。 因此, 通常需要两次不同的配置文件更新步骤。

  • 多数情况下, 更新步骤只会替换或增加文件而不会删除现有的旧文件。 某些时候这可能会导致其他问题。 因此, 有时安装操作会指明必须在操作某些步骤前删除这些文件。 未来, 这可能会实现自动化, 也可能无法实现。

由于这些问题的存在, 一般会建议以下更新步骤。 请注意, 对于特定的更新步骤可能需要一些额外的步骤, 但这些核心步骤应该会在很长一段时间保持不变的:

  1. make buildworld

    这个步骤首先会编译新编译器及相关必需工具, 然后使用新编译器来编译新 world 。 编译成果会存放在 /usr/obj 目录。

  2. make buildkernel

    这个步骤将会使用 /usr/obj 中的 编译器。 以防止编译器与新内核不匹配的问题。

  3. make installkernel

    将新内核和内核模块安装到磁盘, 以便系统能从新内核启动。

  4. 重启进入单用户模式。

    单用户模式能尽量减少更新时正在运行的软件导致的问题。 此外, 它也能使新内核配合旧 world 时尽量少出问题。

  5. mergemaster -p

    这个步骤会进行安装新 world 前所需的配置文件更新工作。 例如, 它可能会添加新用户组或用户名的密码数据库到系统中。 这通常是自上次更新后增加了新用户组或特殊系统用户之后才需要做的, 因为 installworld 会需要这些用户或用户组才能完成工作。

  6. make installworld

    /usr/obj 中拷贝 world 副本。 这个步骤之后, 您的磁盘上的系统, 包括内核和 world 都将是新的了。

  7. mergemaster

    再次更新剩下的配置文件, 现在您磁盘的新 world 更新完成。

  8. make delete-old

    这个步骤目标将是删除旧的(过时的)文件。 这点很重要, 因为有时它们会导致一些问题, 例如, 旧 utmp.h 文件如果存在, 在安装某些需要新的 utmpx.h 文件的 ports 就会出现问题。

  9. 重启。

    重启计算机, 现在将加载新内核, 新 world 和新配置文件。

  10. make delete-old-libs

    删除所有过时的库, 以避免与新库的发生冲突。 确保所有的 ports 重建之前, 已删除旧库文件。

注意, 如果您正从一个主要 FreeBSD 发行版的分支升级, 例如, 从 9.0 升级到 9.1 , 则上述步骤可能就没那么必要了, 因为它不太可能遇到严重的编译器、 内核、 用户空间程序和配置文件不匹配的情况不匹配的情况。 旧的 make world 命令可能能完成编译, 并按照新内核, 并有可能正常完成这种次要版本的升级工作。

但是, 在大主版本升级时, 不按前面所介绍的步骤进行升级则很有可能需要一些问题。

值得一提的是, 很多升级过程可能需要额外的附加步骤, 例如, 重命名或删除 installworld 之前的一些特定文件。 仔细阅读 /usr/src/UPDATING 文件, 特别是它结尾部分推荐的升级步骤。

由于这个过程是不断演化的过程中, 开发者可能会发现某些不能完全避免的不匹配方面的问题。 不过幸运的是, 目前这个推荐升级步骤应该会在很长的时间内保持不变。

总之, 目前推荐的从源代码升级 FreeBSD 的步骤是:

# cd /usr/src
# make buildworld
# make buildkernel
# make installkernel
# shutdown -r now

Note:

有时, 能需要额外执行一次 mergemaster -p 命令, 才能完成 buildworld 步骤。 这些特殊情况一般都会在 UPDATING 中做说明。 一般情况下, 可以简单的跳过这一步, 只要不是一个大跨度的 FreeBSD 版本升级。

installkernel 完成后, 需要重启进入单用户模式(loader 加载器提示符后输入 boot -s)。

如果使用 UFS 文件系统, 运行:

# mount -u /
# mount -a -t ufs

如果使用的是 ZFS 文件系统 (假设 zpool 存储池名为 zroot), 则运行:

# zfs set readonly=off zroot
# zfs mount -a

然后运行:

# adjkerntz -i
# mergemaster -p
# cd /usr/src
# make installworld
# mergemaster
# make delete-old
# reboot
# make delete-old-libs

进一步说明::

以上步骤只是升级步骤的简要说明, 所要清楚了解每一步, 尤其是希望自定义内核配置, 就更应该阅读以下内容。

23.7.2. 阅读 /usr/src/UPDATING

在执行任何更新操作之前, 请务必阅读 /usr/src/UPDATING 文件。 这个文件包含了更新可能存在的潜在问题的重要信息, 或可能指定了某些命令的执行顺序。 如果 UPDATING 中出现的内容与本文相冲突, 请按 UPDATING 中的内容执行。

Important:

阅读 UPDATING 并不能替代的订阅适当的邮件列表。 它们并非相互排斥的, 而是相互相成的。

23.7.3. 检查 /etc/make.conf

make(1) 可用的参数可以在 make.conf(5)/usr/share/examples/etc/make.conf 中找到。 这些参数可以添加到 /etc/make.conf 文件中, 以控制 make(1) 的运行及其编译程序的方式。 请注意, 默认参数照顾到了性能与安全性, 变更默认参数值可能会产生严重后果, 但也可能会达到意想不到的效果。

/etc/make.conf 中的参数会影响到 make(1) 的每次执行, 包括 Ports Collection , 用户自己写的 C 语言程序以及重新编译 FreeBSD 操作系统。

23.7.4. 检查 /etc/src.conf

/etc/src.conf 文件控制从源代码构建操作系统的相关参数。 与 /etc/make.conf 不同, /etc/src.conf 仅控制构建 FreeBSD 操作系统本身。 有关这个文件的更多信息, 请参考 src.conf(5) 。 有时, 禁用那些看起来很不是必要的内核模块或编译选项也能有意想不到的效果。

23.7.5. 更新 /etc 里的文件

/etc 目录中包含了大部分系统配置文件及系统启动脚本, 这些文件可能会因为 FreeBSD 版本的不同而有所差别。

一些配置文件日常运行时都要经常用到, 比如, /etc/group 文件。

有时作为安装过程的一部分, make installworld 会要求事先建立某些特定的用户或用户组, 在升级前它们可能并不存在, 因此 make buildworld 会先检查它们是否已存在。

解决的办法是在构建前 (pre-buildworld) 使用个带 -p 参数运行 mergemaster(8) 命令。 它只会更新在 buildworldinstallworld 过程起关键性作用的文件。

Tip:

您可以这样检测哪些文件是被重命名或删除了的用户组所拥有的:

# find / -group GID -print

该命令将会显示所有 GID 用户组所拥有的所有文件, 这里的用户组名称可以是字母组名或数字 ID 。

23.7.6. 切换到单用户模式

您可能会考虑在单用户模式下编译系统。 重新安装涉及很多重要的系统文件, 包括所有基本系统的二进制文件、 库文件, 头文件等等。 在正在运行中的系统(尤其是大量用户在线时)更新将是自寻烦恼的行为。

另一种方法是在多用户模式编译系统, 然后切换到单用户模式进行安装。 如果使用这种方法, 可以在编译完成后, 以下命令, 这将会直接切换为单用户模式, 然后执行 installkernelinstallworld 命令即可:

从正在运行的系统进入单用户模式使用:

# shutdown now

也可以重启系统, 然后在 boot 提示符下, 选择 single user 选项。 单用户模式启动完毕后会得到一个 shell 提示符, 执行:

# fsck -p
# mount -u /
# mount -a -t ufs
# swapon -a

这将会检查文件系统, 重新以读/写方式挂载 / , 根据 /etc/fstab 里的记录挂载所有 UFS 文件系统, 最后启用交换分区。

Note:

如果您的 CMOS 时钟设置的是本地时间而不是 GMT (就是 date(1) 命令不能报告正确的时间和地区), 您可能还需要执行以下命令:

# adjkerntz -i

这样就可以确保了本地时区设置正确了。

23.7.7. 移除 /usr/obj

随着系统重新构建的进行, 编译结果默认会放到 /usr/obj 中, 目录里的结构会对应 /usr/src 里的目录结构。

为了加快 make buildworld 过程, 并避免可能出现的依赖性问题, 如果这个目录已存在, 请删除它。

/usr/obj 中的某些文件可能设置了不可更改标志, 删除前必须先使用 chflags(1) 取消这些标志。

# cd /usr/obj
# chflags -R noschg *
# rm -rf *

23.7.8. 重新编译基本系统

23.7.8.1. 保存输出日志

make(1) 输出日志保存这将是一个好主意。 如果出现错误, 可以将该错误消息的副本可以发送到 FreeBSD 邮件列表。

这样做最简单的方法是使用 script(1) 命令, 带上一个保存所有输出的文件名的参数即可。 这一方法应该在重新编译系统之前执行, 编译完成后输入 exit 退出。

# script /var/tmp/mw.out
Script started, output file is /var/tmp/mw.out
# make TARGET
… compile, compile, compile …
# exit
Script done, …

不要 将输出日志保存到 /tmp 目录, 这个目录可能会在下次启动的时候被清空, 比较稳妥的是保存到 /var/tmp 目录或 root 用户主目录中。

23.7.8.2. 编译基本系统

进入 /usr/src 目录:

# cd /usr/src

使用 make(1) 重新编译 world , 会从 Makefile (描述如何构建 FreeBSD ,及其构建顺序的文件)中读取构建指令。

命令一般如下格式:

# make -x -DVARIABLE target

示例中, -x 参数会传递给 make(1) 。 更多可用参数, 请参考 make(1) 联机手册。

-D变量值 会传递一个变量给 Makefile 。 这些变量会控制 Makefile 的行为。 这与设置 /etc/make.conf 变量一样, 只是提供了另一种设置方法。

# make -DNO_PROFILE target

如上指定不编译先定库 (profiled libraries) 则等同于在

NO_PROFILE=    true     #    Avoid compiling profiled libraries

/etc/make.conf 里的设置。

target 告诉 make(1) 该执行什么。 每个 Makefile 都定义了许多不同的 targets , 然后由您决定使用哪个 target , 并产生对应结果。

Makefile 中列出的 targets ,并不是都需要使用, targets 的目的是将重新编译系统的过程分成几个子步骤。

大多数情况下都没必要向 make(1) 传递参数, 因此命令看起来像是这样:

# make target

这里的 target 是众多编译选项中的一个。 一般情况下, 第一个 target 都应该是 buildworld

正如名字所暗示的, buildworld 就是在 /usr/obj 下构建一个全新的系统, 然后使用 installworld 将新系统安装到计算机上。

使用独立的选项有两个优点。 首先, 它允许您 独立 的构建系统, 而不会影响系统的任何一个部件, 正因如此, buildworld 可以大胆的在多用户模式下的计算机上执行, 而不用担心用不良影响, 不过依旧推荐您在单用户模式执行 installworld 命令。

其次, 它允许您使用 NFS 升级网络上的多台计算机。 如果您有 ABC 三台计算机需要升级, 那么您可以在 A 计算机执行 make buildworldmake installworld 命令。 BC 计算机通过 NFS 方式 挂载 A 计算机上的 /usr/src/usr/obj 目录。 运行 make installworld 命令, 将编译成果安装到 BC 计算机上。

尽管 world target 依旧存在, 强烈建议您不要再使用它。

相反的, 推荐使用:

# make buildworld

我们可以指定 -j 参数, 强制 make 启动多个并发进程。 对于有多个 CPU 的计算机而言, 这样有助于发挥计算机性能。 不过多数情况下, 编译过程的主要瓶颈在 I/O 上,而不是不是 CPU , 但它依旧对单个 CPU 的计算机有好处。

在常见的单 CPU 的计算机上, 使用:

# make -j4 buildworld

这样 make(1) 就能同时启动运行 4 个进程。 根据邮件列表中的统计测试报告, 这样做通常能提供最佳的性能。

如果您的计算机配置了多颗 CPU , 并使用 SMP 内核, 也可以试试 6 到10 的数值, 看看它们是如何高速完成编译的。

23.7.8.3. 耗时

有许多因素会影响的编译时间, 不过, 较新的计算机一般都能在一两个小时内完成从源代码构建 FreeBSD-STABLE , 而不需要使用任何技巧或走捷径。 对于 FreeBSD-CURRENT , 则通常需要更长一点的时间。

23.7.9. 编译和安装新内核

要充分利用新系统, 您应该重新编译内核。 这是很有必然的, 因为特定的内存结构已经发生了改变, 像 ps(1)top(1) 这样的程序可能无法正常工作, 除非内核与源代码树的版本是一样的。

要做到这一点最简单, 最安全的方式是构建和安装一个基于 GENERIC 配置文件的内核。 虽然 GENERIC 可能没有包含所有系统所需的设备驱动, 但它包含启动到系统启动到单用户模式所需的所有内容。 这是一个很好的测试新系统是否正常工作的方法。 从 GENERIC 启动, 校验完系统后, 就可以建立您自己的自定义内核了。

在 FreeBSD 上, 在构建新内核前完成 build world 是非常重要的。

Note:

如果您想构建一个自定义内核, 并且这个配置文件已经创建完成, 只需要指定内核名称 KERNCONF=MYKERNEL

# cd /usr/src
# make buildkernel KERNCONF=MYKERNEL
# make installkernel KERNCONF=MYKERNEL

如果您已把 kern.securelevel 调高到 1 或 以上 , 而且还设置了 noschg 或相似标志到您的二进制内核上, 你可能会发现需要切换到单用户模式执行 installkernel 。 否则, 这两个命令在多用户模式下执行应该也不会有问题。 请参阅 init(8) 以了解更多关于 kern.securelevel 的信息; 参阅 chflags(1) 了解更多不同文件标志的信息。

23.7.10. 重启到单用户模式

您应该使用单用户模式测试新内核, 根据 Section 23.7.6, “切换到单用户模式” 中的说明去操作。

23.7.11. 安装编译好的新系统

接下来, 我们使用 installworld 来安装新系统二进制文件:

# cd /usr/src
# make installworld

Note:

make buildworld 时跟随的编译参数, 在 make installworld 时也必须跟随, 当然除 -j 参数外, 它不能用在 installworld 上。

就像这样时:

# make -DNO_PROFILE buildworld

安装时您也必须这样:

# make -DNO_PROFILE installworld

否则,该命令将试图安装在 make buildworld 阶段没有建立的先定库。

23.7.12. 更新不由 make installworld 负责更新的文件

重构的 world 将不会更新下某些目录, 如 /etc/var/usr 目录下的新的或有变更的文件。

更新这些目录中的文件的最简单的方法是使用在 mergemaster(8) 。 为了避免出错, 请务必先备份 /etc 目录。

23.7.12.1. mergemaster

贡献者: Tom Rhodes.

mergemaster(8) 工具是一个 Bourne 脚本, 用于检测 /etc 和源代码中的 /usr/src/etc 中的配置文件的差异。 这是保持系统中配置文件与源代码保持同步的一种首选方式。

首先, mergemaster 会建立一个临时的根环境, 在 / 下, 放置各种系统配置文件。 然后, 这些文件会和系统中已按照的文件做对比, 并使用 diff(1) 格式显示出来, 使用 + 表示添加或修改的行, 使用 - 表示完全删除或被替换成了新行。 查阅 diff(1) 联机手册以了解更多有关 diff(1) 的语法和文件不同点是怎么显示的。

mergemaster(8) 会给您显示每个文件的不同处, 这样您就可以选择是删除新文件 (相对临时文件), 是以未改状态安装临时文件, 是以当前安装的文件合并临时文件, 还是再看一次 diff(1) 结果。

选择删除临时文件将让 mergemaster(8) 保留当前文件, 并删除新版本文件。 不建议使用这个选项, 除非您有不变更当前文件的理由。 任何时候, 当需要帮助时, 可以在 mergemaster(8) 提示符里输入 ? , 您将得到帮助。 如果选择跳过这个文件, 将会在其他文件处理完后进行。

选择使用未修改临时文件将会使用新文件替换当前文件。 对于大多数未修改的文件, 这是最好的选择。

选择合并文件将会打开一个文本编辑器, 里面是两个文件的内容。 文件将并排在出现在屏幕上, 从它们中间选择需要部分合并为最终文件。 当两个文件比较时, 使用 l 键选择左边内容, 使用 r 键选择右边内容, 最终文件就是这两部分文件组成的, 然后就可以用它安装。 这个选项通常用于用户修改过的配置文件。

选择再次查看 diff(1) 结果将再次显示文件差异, 就像 mergemaster(8) 之前提示的一样。

最后, 在 mergemaster(8) 完成系统文件处理后, 它还会提示其他问题。 重建密码文件, 并在最后提示您是否删除余下的临时文件。

23.7.12.2. 手动更新

如果希望手动执行更新, 请不要直接将 /usr/src/etc 下的文件覆盖到 /etc 下, 因为有些文件是需要 安装 后才能工作的。 事实上, 并 不是 简单的把 /usr/src/etc 拷贝到 /etc , 有些文件 /etc 中有, /usr/src/etc 却并不存在的。

如果您使用的是 mergemaster(8) (推荐), 您可以直接跳到 下一节

手工合并文件最简单的方法是将文件安装到一个新目录, 然后寻找它们的不同之处。

备份您现有的 /etc:

建议先复制现有的 /etc 到安全的地方, 就像这样:

# cp -Rp /etc /etc.old

其中 -R 参数会递归复制所有文件, -p 参数会保留文件时间和属主等等。

创建一个临时目录将新的 /etc 文件和相关文件安装到里面:

# mkdir /var/tmp/root
# cd /usr/src/etc
# make DESTDIR=/var/tmp/root distrib-dirs distribution

这将建立必要的目录结构并安装文件。 在 /var/tmp/root 目录中, 会有很多子目录是空的, 要删除它们最简单的方法是:

# cd /var/tmp/root
# find -d . -type d | xargs rmdir 2>/dev/null

这样将会删除所有空目录, 同时将有关非空目录的警告信息重定向到 /dev/null 设备。

/var/tmp/root 现在包含了应放在 / 某个位置的文件。 通过这些文件, 比对系统中已存的文件有何差异。

/var/tmp/root 目录下有些 . 开头的文件, 可能需要 ls -a 才能看得到它们。

比较文件的最简单的方法是使用 diff(1)

# diff /etc/shells /var/tmp/root/etc/shells

这个命令将会显示现有的 /etc/shells 和 新 /var/tmp/root/etc/shells 文件的差异。 这个差异决定了您是合并还是直接替换就旧文件。

使用时间戳命名根目录 ( /var/tmp/root ), 这样您可以轻松比较两个版本的不同::

频繁重建系统意味着必须频繁更新 /etc , 而这可能会有些烦。

为了加快这一进程, 可以使用以下过程来保留最后一套被合并的 /etc 文件的副本。

  1. 像通常那样建立 world 。当更新 /etc 及其相关目录时, 可以在目标目录基础上加一个当前日期到名称里:

    # mkdir /var/tmp/root-20130214
    # cd /usr/src/etc
    # make DESTDIR=/var/tmp/root-20130214 \
        distrib-dirs distribution
  2. 在这个目录的基础上进行合并工作, 当合并完成时, 请 不要 删除这个目录。

  3. 下载源代码的最新版本和修改后, 按照第一步, 创建一个反映新日期的目录, 例如使用 /var/tmp/root-20130221

  4. 使用 diff(1) 查看这段相隔的时间里两个目录之间的递归差异:

    # cd /var/tmp
    # diff -r root-20130214 root-20130221

    通常, 这组差异比起 /var/tmp/root-20130221/etc/etc 之间的差异要明显小很多, 也就更容易把这些变更合并到 /etc 中去。

  5. 完成后, 就可以删除较早的 /var/tmp/root-* 目录:

    # rm -rf /var/tmp/root-20130214
  6. 每次需要合并 /etc , 都重复这个流程就可以了。

使用 date(1) 自动生成目录名称:

# mkdir /var/tmp/root-`date "+%Y%m%d"`

23.7.13. 删除过时的文件和目录

Based on notes provided by Anton Shterenlikht.

由于 FreeBSD 的开发周期里, 偶尔会有些文件或内容会过时的情况。 这可能因为这个功能在其他地方实现, 或库版本号变更, 也可能是完全从系统中移除等等。 在更新系统时, 包括旧文件, 旧库文件, 旧目录, 它们都应该被从系统中移除, 这是有好处的, 这样可以保证系统稳定, 同时减少了不必要的磁盘空间及备份空间的浪费, 另外, 如果旧库或文件存在安全或稳定性问题, 系统更新它们可以保证安全, 同时防止因旧库导致的崩溃。 过时的文件, 目录, 库文件都会在 /usr/src/ObsoleteFiles.inc 中列出。 接下来, 将介绍如何删除这些文件。

make installworldmergemaster 命令完成后, 可以使用以下命令检测过时的文件和库文件:

# cd /usr/src
# make check-old

如果检测到任何过时的文件, 则可以使用以下的命令移除:

# make delete-old

Tip:

参阅 /usr/src/Makefile 可以了解更多其他 targets 的功能。

当删除每一个文件之前都会出现一个确认提示。 要跳过提示, 让系统自动删除这些文件, 可以设置 BATCH_DELETE_OLD_FILES 变量, 就像这样:

# make -DBATCH_DELETE_OLD_FILES delete-old

也可以使用 yes 命令配合管道实现类似目的:

# yes|make delete-old

23.7.14. 重启

确认一切都已正确完成后, 就可以使用 shutdown(8) 重启系统了:

# shutdown -r now

23.7.15. 删除过时的库文件

警告:

删除过时的文件可能会破坏现有的依赖于这些过时的文件的应用程序。 尤其对于删除旧库文件这种情况, 多数情况下, 在重新编译所有依赖旧库的 ports 程序之后, make delete-old-libs 删除那些过时的文件。

通用检测共享库依赖的工具 sysutils/libchksysutils/bsdadminscripts 可以通过 Ports Collection 安装。

过时的共享库与新共享库可能发生冲突, 会导致类似的警告消息:

/usr/bin/ld: warning: libz.so.4, needed by /usr/local/lib/libtiff.so, may conflict with libz.so.5
/usr/bin/ld: warning: librpcsvc.so.4, needed by /usr/local/lib/libXext.so, may conflict with librpcsvc.so.5

为了解决这类问题, 需要确定是哪个 port 安装了这个库文件:

# pkg_info -W  /usr/local/lib/libtiff.so
  /usr/local/lib/libtiff.so was installed by package tiff-3.9.4
  # pkg_info -W /usr/local/lib/libXext.so
  /usr/local/lib/libXext.so was installed by package libXext-1.1.1,1

然后卸载, 重新编译和安装的该 port 。 可以使用 ports-mgmt/portmaster 工具自动化完成此过程。 在确认所有 ports 都已经重建, 并不再有需要依赖旧库的情况后, 执行以下命令删除它们:

# make delete-old-libs

你现在应该已经成功升级了 FreeBSD 。 恭喜。

如果进展不顺利, 很容易重建系统的某一部分。 例如, 在升级或合并 /etc 时不小心删除了 /etc/magic , 这将会导致 file(1) 停止工作, 这种情况下, 可以执行以下命令进行修复:

# cd /usr/src/usr.bin/file
# make all install

23.7.16. 提问

23.7.16.1. 是否每个变更都需要重新编译 world ?
23.7.16.2. 我的编译出错了, 并报告 11 (或其它的数字信息)的错误号。 这是什么情况?
23.7.16.3. 完成后可以删除 /usr/obj 吗?
23.7.16.4. 构建过程中断可以恢复吗?
23.7.16.5. 我怎样才能加速构建 world ?
23.7.16.6. 如果出错, 我该怎么办?

23.7.16.1.

是否每个变更都需要重新编译 world ?

这不好说, 主要看是什么性质的变更。 比如: 如果 svn 更新了以下文件:

src/games/cribbage/instr.c
src/games/sail/pl_main.c
src/release/sysinstall/config.c
src/release/sysinstall/media.c
src/share/mk/bsd.port.mk

这样就没必要重建 整个 world 。 只需要进入相应的子目录, 运行 make all install 就可以了。 但是, 如果是重大变更, 如 src/lib/libc/stdlib 发生变更, 那么则需要重建 world , 或至少编译静态链接的那部分。

每天天结束的时候, 你就可以开始编译。 也有些用户会让变更累积两个星期后再重新编译 world 。 您也可以只重新编译变更过的部分, 不过那样你得确认能找出所有依赖关系。

这取决与您希望什么样的升级频率, 以及是否是跟踪 FreeBSD-STABLE 或 FreeBSD-CURRENT 。

23.7.16.2.

我的编译出错了, 并报告 11 (或其它的数字信息)的错误号。 这是什么情况?

这通常表明硬件出错。 (重新)构建 world 是高压测试硬件的有效方式, 常常表现为编译器由于内存错误莫名其妙的终止了。

一个确信的指示器是如果重新开始 make , 并且整个过程中会死在不同的点上。

对于这种情况, 可以更换机器的某个部件, 检测是哪一部分硬件出问题了。

23.7.16.3.

完成后可以删除 /usr/obj 吗?

简单的说, 可以。

/usr/obj 中包含了所有在编译阶段生成的目标文件。 通常情况下, make buildworld 过程的第一步就是删除这个目录重新开始。 在您完成后, 保留 /usr/obj 没多大意义, 删除它还可以释放大约 2 GB 磁盘空间。

高级用户可以指定 make buildworld 跳过这一步, 这可以让后续的构建过程更快写, 因为大部分源代码不再需要重新编译。 这样可能也可能引起一些由于敏感依赖问题编译失败。 在 FreeBSD 常常会有人抱怨他们编译失败了, 但他们往往没想到是自己想偷懒导致的。

23.7.16.4.

构建过程中断可以恢复吗?

这取决于您在出问题前整个过程进行到哪一步了。

一般来说, make buildworld 会构建必备工具的新副本 (如: gcc(1)make(1) 和系统库)。 并在随后使用这些工具再重新编译自己, 然后再开始编译整个系统 (包括 ls(1)grep(1) 这些用户层程序也会随系统被重新构建)。

如果您已经处于最后一个阶段, 则可以相当安全使用:

… fix the problem …
# cd /usr/src
# make -DNO_CLEAN all

这样就不会取消先前的 make buildworld 工作。

如果您看到这样的消息:

--------------------------------------------------------------
Building everything..
--------------------------------------------------------------

如果 make buildworld 输出这样的消息, 则应该不会出什么问题。

如果没有显示这条消息, 或者你不能确定, 则重新开始构建要比继续构建而导致失败的好。

23.7.16.5.

我怎样才能加速构建 world ?

  • 在单用户模式下运行。

  • /usr/src/usr/obj 目录到放到独立的文件系统上。 如何可能, 将他们放在独立的磁盘控制器上会更好。

  • 另外, 可以通过 ccd(4) 将这些文件系统放置在多个磁盘上。

  • 通过 /etc/make.conf 里添加 NO_PROFILE=true 来关闭编译性能分析。

  • 传递 -jn 参数给 make(1) , 强制其并行多个进程, 这样有助于发挥您的单或多处理器计算机的性能。

  • 存放 /usr/src 目录的文件系统使用 noatime 选项挂载。 这样可以防止文件系统记录不必要的访问时间而导致的性能损失。

    # mount -u -o noatime /usr/src

    Warning:

    示例中, 假定 /usr/src 在独立的文件系统上, 如果它是 /usr 的一部分, 则需要使用该挂载点来代替。

  • 存放 /usr/obj 文件系统可以使用 async 选项挂载。 这样会启用磁盘的异步写入, 也就是在数据并不会被立即写入磁盘, 而是延迟几秒后写入磁盘, 这能显著提升性能。

    Warning:

    切记, 此选项会使文件系统变得更加脆弱。 使用这个选项, 会增加突然电源故障或异常重启导致文件系统损坏的机率。

    如果 /usr/obj 是这个文件系统上的唯一目录, 这没有任何问题的。 如果你在文件系统还有其他有价值的数据, 则请备份后启用这个选项。

    # mount -u -o async /usr/obj

    Warning:

    如果 /usr/obj 不是独立的文件系统, 请用合适的挂载点取代例子中的挂载点。

23.7.16.6.

如果出错, 我该怎么办?

请确保系统没有先前构建任何残留:

# chflags -R noschg /usr/obj/usr
# rm -rf /usr/obj/usr
# cd /usr/src
# make cleandir
# make cleandir

是的, make cleandir 确实要运行两次。

然后, 使用 make buildworld 重新开始整个过程。

如果问题仍然存在, 请发送错误信息和 uname -a 信息到 FreeBSD 一般问题邮件列表 。 并准备回答有关您的设置的相关问题。

本文档和其它文档可从这里下载: ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

如果对于FreeBSD有问题,请先阅读 文档,如不能解决再联系 <questions@FreeBSD.org>.

关于本文档的问题请发信联系 <doc@FreeBSD.org>.