> For the complete documentation index, see [llms.txt](https://handbook.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://handbook.bsdcn.org/di-27-zhang-freebsd-geng-xin-yu-sheng-ji/27.6.-cong-yuan-dai-ma-geng-xin-freebsd.md).

# 27.6.从源代码更新 FreeBSD

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

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

## 27.6.2. 准备源代码更新

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

## 27.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 13-STABLE 上运行。例如，编译并运行在 FreeBSD 13.1 上的软件仍可以在后续的 FreeBSD 13-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 版本和存储库分支](/di-27-zhang-freebsd-geng-xin-yu-sheng-ji/27.6.-cong-yuan-dai-ma-geng-xin-freebsd.md)，更新 `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 版本和存储库分支](/di-27-zhang-freebsd-geng-xin-yu-sheng-ji/27.6.-cong-yuan-dai-ma-geng-xin-freebsd.md) 中的路径已添加到存储库 URL 中。第三个参数是源代码在本地系统上的目标目录。

## 27.6.4. 从源代码构建

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

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

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

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

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

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

```sh
# make cleanworld
```

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

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

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

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

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

### 27.6.4.3. 仅构建内核

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

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

### 27.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
```

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

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

```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
```

## 27.6.6. 完成更新

几个最终任务可完成整个更新过程：修改过的配置文件需要与新版本合并，需要定位并删除过时的库，然后重新启动系统。

### 27.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)。
> * ② 在引导后检查差异。删除任何不再需要的本地更改，以减少未来更新时的冲突可能性。

### 27.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
> ```

### 27.6.6.3. 更新后重启

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

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