# 26.6.从源代码更新 FreeBSD

通过从源代码编译更新 FreeBSD 相比于使用二进制更新，提供了几个优势。代码可以根据特定硬件的需求进行编译。部分基本系统可以使用非默认设置进行构建，或者在不需要或不想要的情况下完全省略。尽管构建过程比直接安装二进制更新花费更长的时间，但它允许完全定制，生成一个量身定制的 FreeBSD 版本。

## 26.6.1. 快速开始

这是通过从源代码编译更新 FreeBSD 的典型步骤的快速参考。后续章节将详细描述该过程。

> **警告**
>
> 从 [mergemaster(8)](https://man.freebsd.org/cgi/man.cgi?query=mergemaster\&sektion=8\&format=html) 切换到 [etcupdate(8)](https://man.freebsd.org/cgi/man.cgi?query=etcupdate\&sektion=8\&format=html) 时，第一次运行可能会错误地合并更改，生成虚假的冲突。为防止这种情况，在更新源代码并构建新版本之前，执行以下步骤：
>
> ```sh
> # etcupdate extract ①
> # etcupdate diff    ②
> ```
>
> * ① 引导 **/etc** 文件的数据库；有关更多信息，请参见 [etcupdate(8)](https://man.freebsd.org/cgi/man.cgi?query=etcupdate\&sektion=8\&format=html)。
> * ② 引导数据库后检查差异。修剪任何不再需要的本地更改，以减少未来更新时发生冲突的可能性。

* 更新和构建

  ```sh
  # git -C /usr/src pull   ①
  检查 /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        ⑪
  ```
* ① 获取最新的源代码版本。有关获取和更新源代码的更多信息，请参见 [更新源代码](https://docs.freebsd.org/en/books/handbook/cutting-edge/#updating-src-obtaining-src)。
* ② 检查 **/usr/src/UPDATING** 文件，了解构建源代码之前或之后是否需要手动执行的步骤。
* ③ 进入源代码目录。
* ④ 编译 world，即编译除内核以外的所有内容。
* ⑤ 编译并安装内核。这相当于 `make buildkernel installkernel`。
* ⑥ 重启系统以使用新内核。
* ⑦ 更新并合并 **/etc/** 中的配置文件，准备进行 installworld 安装。
* ⑧ 进入源代码目录。
* ⑨ 安装 world。
* ⑩ 更新并合并 **/etc/** 中的配置文件。
* ⑪ 重启系统以使用新构建的 world 和内核。

## 26.6.2. 准备进行源代码更新

阅读 **/usr/src/UPDATING** 文件。任何在更新之前或之后需要执行的手动步骤，都将在此文件中进行描述。

## 26.6.3. 更新源代码

FreeBSD 的源代码位于 **/usr/src/** 目录。更新此源代码的首选方法是通过 Git 版本控制系统。验证源代码是否已纳入版本控制：

```sh
# 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)](https://man.freebsd.org/cgi/man.cgi?query=git\&sektion=1\&format=html) 更新：

```sh
# git -C /usr/src pull
```

如果目录未被最近更新，更新过程可能需要一些时间。完成后，源代码将是最新的，可以开始下一节描述的构建过程。

> **注意**
>
> 获取源代码：
>
> 如果输出显示 `fatal: not a git repository`，说明文件丢失或使用了不同的方法安装。需要重新克隆源代码。

**表 1. FreeBSD 版本和仓库分支**

| uname ‑r 输出   | 仓库路径         | 描述                                                                                                                                                                                        |
| ------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X.Y-RELEASE` | `releng/X.Y` | 版本发布版，仅包含关键的安全和错误修复补丁。该分支推荐给大多数用户。                                                                                                                                                        |
| `X.Y-STABLE`  | `stable/X`   | 版本发布版，包含该分支的所有开发更新。*STABLE* 指的是应用二进制接口（ABI）不发生变化，因此早期版本编译的软件仍然可以在 FreeBSD 10-STABLE 上运行。例如，编译并运行在 FreeBSD 10.1 上的软件仍可以在后续的 FreeBSD 10-STABLE 上运行。STABLE 分支偶尔会有影响用户的错误或不兼容情况，但通常这些问题会迅速修复。 |
| `X-CURRENT`   | `main`       | FreeBSD 最新的未发布开发版本。CURRENT 分支可能包含重大错误或不兼容，推荐仅供高级用户使用。                                                                                                                                     |

使用 [uname(1)](https://man.freebsd.org/cgi/man.cgi?query=uname\&sektion=1\&format=html) 确定正在使用的 FreeBSD 版本：

```sh
# uname -r   
13.2-RELEASE 
```

根据 [FreeBSD 版本和仓库分支](https://docs.freebsd.org/en/books/handbook/cutting-edge/#updating-src-obtaining-src-repopath)，更新 `13.2-RELEASE` 的源代码使用的仓库路径为 `releng/13.2`。此路径在克隆源代码时使用：

```sh
# mv /usr/src /usr/src.bak ①
# git clone --branch releng/13.2 https://git.FreeBSD.org/src.git /usr/src ②
```

* ① 将旧目录移到其他位置。如果该目录中没有本地修改，可以删除它。
* ② [FreeBSD 版本和仓库分支](https://docs.freebsd.org/en/books/handbook/cutting-edge/#updating-src-obtaining-src-repopath) 中的路径已添加到仓库 URL 中。第三个参数是源代码在本地系统上的目标目录。

## 26.6.4. 从源代码构建

*world*（即操作系统的所有部分，除了内核）需要先进行编译，以提供更新的工具来构建内核。然后，内核本身会被编译：

```sh
# cd /usr/src
# make buildworld
# make buildkernel
```

编译后的代码会写入 **/usr/obj** 目录。

这些是基本步骤。控制构建的其他选项将在下面描述。

### 26.6.4.1. 执行干净的构建

FreeBSD 构建系统的某些版本会将先前编译的代码保留在临时对象目录 **/usr/obj** 中。这可以通过避免重新编译未更改的代码来加速后续的构建。要强制重新构建所有内容，请在开始构建之前使用 `cleanworld`：

```sh
# make cleanworld
```

### 26.6.4.2. 设置构建任务的数量

在多核处理器上增加构建任务的数量可以提高构建速度。使用 `sysctl hw.ncpu` 来确定核心数量。由于处理器和不同版本的 FreeBSD 使用的构建系统有所不同，因此测试是唯一能确定不同任务数量对构建速度影响的可靠方法。作为起点，考虑使用核心数量的一半到两倍。可以使用 `-j` 来指定任务数量。

**示例 1. 增加构建任务数量**

使用四个任务来构建 world 和内核：

```sh
# make -j4 buildworld buildkernel
```

### 26.6.4.3. 仅构建内核

如果源代码发生更改，必须先完成 `buildworld`。之后，可以随时运行 `buildkernel` 来构建内核。要仅构建内核：

```sh
# cd /usr/src
# make buildkernel
```

### 26.6.4.4. 构建自定义内核

标准的 FreeBSD 内核基于一个名为 **GENERIC** 的 *内核配置文件*。**GENERIC** 内核包含了最常用的设备驱动程序和选项。有时，为了适应特定需求，构建自定义内核是有用的或必要的，可以添加或删除设备驱动程序或选项。

例如，对于一个开发小型嵌入式计算机的人员，若其 RAM 严重受限，可以删除不需要的设备驱动程序或选项，以使内核稍微小一些。

内核配置文件位于 **/usr/src/sys/arch/conf/** 目录中，其中 *arch* 是 `uname -m` 命令的输出。在大多数计算机上，`arch` 为 `amd64`，因此配置文件目录为 **/usr/src/sys/amd64/conf/**。

> **技巧**
>
> 可以删除或重新创建 **/usr/src**，因此最好将自定义内核配置文件保存在一个单独的目录中，例如 **/root**。将内核配置文件链接到 **conf** 目录。如果该目录被删除或覆盖，可以将内核配置文件重新链接到新目录中。

可以通过复制 **GENERIC** 配置文件来创建一个自定义配置文件。例如，假设新的自定义内核是用于存储服务器，因此命名为 **STORAGESERVER**：

```sh
# cp /usr/src/sys/amd64/conf/GENERIC /root/STORAGESERVER
# cd /usr/src/sys/amd64/conf
# ln -s /root/STORAGESERVER .
```

然后编辑 **/root/STORAGESERVER**，根据 [config(5)](https://man.freebsd.org/cgi/man.cgi?query=config\&sektion=5\&format=html) 添加或删除设备或选项。

通过在命令行中设置 `KERNCONF` 来构建自定义内核：

```sh
# make buildkernel KERNCONF=STORAGESERVER
```

## 26.6.5. 安装编译后的代码

完成 `buildworld` 和 `buildkernel` 步骤后，安装新的内核和 world：

```sh
# cd /usr/src
# make installkernel
# shutdown -r now
# cd /usr/src
# make installworld
# shutdown -r now
```

如果构建了自定义内核，则必须设置 `KERNCONF` 以使用新的自定义内核：

```sh
# 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)](https://man.freebsd.org/cgi/man.cgi?query=etcupdate\&sektion=8\&format=html) 合并配置文件

[etcupdate(8)](https://man.freebsd.org/cgi/man.cgi?query=etcupdate\&sektion=8\&format=html) 是一个用于管理文件更新的工具，这些文件不作为 `installworld` 的一部分进行更新，例如位于 **/etc/** 目录中的文件。它通过对这些文件进行三方合并，将对文件所做的更改与本地版本进行比对。[etcupdate(8)](https://man.freebsd.org/cgi/man.cgi?query=etcupdate\&sektion=8\&format=html) 旨在最小化用户干预的次数。

> **注意**
>
> 一般情况下，[etcupdate(8)](https://man.freebsd.org/cgi/man.cgi?query=etcupdate\&sektion=8\&format=html) 不需要任何特定的参数来完成任务。然而，有一个方便的中间命令可以用于在首次使用 [etcupdate(8)](https://man.freebsd.org/cgi/man.cgi?query=etcupdate\&sektion=8\&format=html) 时进行健康检查：
>
> ```sh
> # etcupdate diff
> ```
>
> 此命令允许用户审计配置更改。

如果 [etcupdate(8)](https://man.freebsd.org/cgi/man.cgi?query=etcupdate\&sektion=8\&format=html) 无法自动合并某个文件，则可以通过手动交互来解决合并冲突，方法是运行：

```sh
# etcupdate resolve
```

> **警告**
>
> 从 [mergemaster(8)](https://man.freebsd.org/cgi/man.cgi?query=mergemaster\&sektion=8\&format=html) 切换到 [etcupdate(8)](https://man.freebsd.org/cgi/man.cgi?query=etcupdate\&sektion=8\&format=html) 时，首次运行可能会错误地合并更改，从而生成虚假的冲突。为了避免这种情况，请在更新源代码并构建新系统前执行以下步骤：
>
> ```sh
> # etcupdate extract ①
> # etcupdate diff    ②
> ```
>
> * ① 引导 **/etc** 文件的数据库；更多信息，请参见 [etcupdate(8)](https://man.freebsd.org/cgi/man.cgi?query=etcupdate\&sektion=8\&format=html)。
> * ② 在引导后检查 diff。修剪任何不再需要的本地更改，以减少未来更新时的冲突可能性。

### 26.6.6.2. 检查过时的文件和库

更新后，可能会有一些过时的文件或目录残留。这些文件可以通过以下命令定位：

```sh
# make check-old
```

并删除：

```sh
# make delete-old
```

一些过时的库也可能残留，可以通过以下命令检测：

```sh
# make check-old-libs
```

并通过以下命令删除：

```sh
# make delete-old-libs
```

仍然使用这些旧库的程序将在库被删除后停止工作。删除旧库后，必须重新编译或替换这些程序。

> **技巧**
>
> 当确认所有旧文件或目录可以安全删除时，通过设置 `BATCH_DELETE_OLD_FILES` 参数，可以避免按下 `y` 并按 Enter 键删除每个文件。例如：
>
> ```sh
> # make BATCH_DELETE_OLD_FILES=yes delete-old-libs
> ```

### 26.6.6.3. 更新后重启

更新的最后一步是重新启动计算机，以使所有更改生效：

```sh
# shutdown -r now
```
