# 集成用户软件进系统镜像

在开发过程中，常常需要将一些配置文件或开发完成的程序直接包含到系统镜像中，以便在设备运行时能够在板端直接使用这些文件或程序。本章将详细介绍两种常用的方法，将用户的软件和文件集成到系统镜像中：

1. **将文件添加到根文件系统的 `system` 分区**

   这种方式适用于需要直接与系统文件一同部署的文件，例如系统库、系统配置文件等。通过将文件放入 `system` 分区，可以确保它们在设备启动时即可被加载和使用。

2. **新增一个独立的分区存放自定义文件**

   通过创建新的分区并将自定义文件、程序存放其中，可以实现更高的灵活性和模块化。新分区会生成独立的镜像文件，方便管理和更新，同时也能避免对根文件系统的影响。

这两种方法各有特点，可根据需求选择合适的集成方式来优化开发和部署流程。以下将分别介绍两种方法的使用。

## 将文件添加到根文件系统的 `system` 分区

将用户自定义文件集成到根文件系统非常简单。只需将准备好的文件复制到路径 `system/buildroot/prebuilt/boot-utils-runtime` 下。在编译 `system` 镜像时，这个目录中的所有内容都会被自动打包到 `system` 分区中，无需额外操作。合并这部分文件的编译代码在 BSP 源码的 `build/mk_system.sh` 脚本中，实现代码如下：

```bash
function build_unpack()
{
        ... ( 省略 ) ...

        # 合并自动启动配置项
        if [ -d "${SYSTEM_ORIG_DIR}/boot-utils-runtime" ]; then
                rm -rf "${SYSTEM_BUILD_DIR}"/etc/dropbear
                cp -arf --remove-destination ${SYSTEM_ORIG_DIR}/boot-utils-runtime/* "${SYSTEM_BUILD_DIR}"
        fi
}
```

**注意事项：**

- `system` 分区的容量具体大小可能因软件版本有所不同。如果添加的文件导致分区总大小超过预设容量，镜像生成将失败。为避免此问题，可以通过修改分区表来调整 `system` 分区的大小。详细方法请参考[分区配置](../board_bring_up.html#span-id-bsp-part-conf)。

在操作前，请确认 `system` 分区容量限制，并确保添加的文件不会超出可用空间。

## 新增独立分区存放自定义文件

为更灵活地管理程序或配置文件，可以通过新增一个独立分区专门存放这些内容。这种方式特别适合需要对程序进行独立编译、管理和升级的场景。本节以配置 `app` 分区为例，详细说明操作步骤（`app` 分区默认已支持）。

### 注意事项

1. **关于分区表最后一个分区**

   如果新增的分区位于分区表的最后一个分区之后（例如在 `userdata` 分区之后），需要注意以下行为：

   - 系统启动时，`Uboot` 阶段会根据硬件存储设备（如 eMMC 或 NAND Flash）的实际容量，自动将最后一个分区扩展至存储设备的末尾，以最大化利用存储空间。

   - 首次启动时，`Kernel` 检测到最后一个分区未被格式化或没有有效的文件系统时，会自动执行格式化操作。

2. **`userdata` 分区默认设置**

   - 系统镜像制作时，`userdata` 分区通常是最后一个分区，不包含数据，且分区大小设置较小（默认 50MB），以减小镜像体积。
   - 启动后，`Uboot` 会自动扩展 `userdata` 分区到剩余存储空间，首次进入系统时会格式化该分区。
   - 如果在 `userdata` 之后新增分区，请调整 `userdata` 的大小以满足实际需求。

3. **关于空分区**

   - 如果新增分区在镜像制作时为空（无有效数据，如 `userdata` 或 `private`），需要在系统首次启动时进行格式化。

   - 格式化逻辑可以参考 `/etc/init.d/S65mountall` 脚本，将新分区名添加到 `force_format_part_list` 变量中，例如：

     ```text
     force_format_part_list="app userdata log private"
     ```

### 实现步骤

#### 1. 修改分区表

在分区表中定义新分区。以 `device/horizon/x5/board_cfg/soc/x5-soc-debug-gpt.json` 为例，添加如下配置：

```json
"app": {
    "fs_type": "ext4",
    "part_type": "GOLDEN",
    "size": "700m"
},
```

- `fs_type`：分区文件系统类型，设置为 `ext4`。
- `size`：分区大小，单位为 MB（示例为 `700m`）。

有关分区表配置的更多信息，请参考[分区配置](../board_bring_up.html#span-id-bsp-part-conf)。

#### 2. 准备分区内容

创建一个用于存放分区文件的目录（如 `app`），并在其中添加需要的文件。例如，添加一个名为 `startup.sh` 的启动脚本：

```bash
# 在 BSP 源码根目录下执行以下命令
mkdir app
cd app
echo "#!/bin/sh" > startup.sh
echo 'echo "This is the test code"' >> startup.sh
chmod 777 startup.sh
```

若新增分区为空，此步骤可跳过。当前系统中 `/app` 和 `/userdata`  目录下的  `startup.sh` 程序会被 `/etc/init.d/S99auto_startup` 服务在系统启动时调用执行。

#### 3. 验证生成分区镜像

参考以下编译程序，根据分区目录内容生成镜像，例如：

```bash
./build/mk_app.sh
```

生成的镜像文件位于 `out/product/app.img`。若新增分区为空，可跳过此步骤。

#### 4. 集成编译脚本

在 `xbuild.sh` 中添加分区编译逻辑：

- 在 `avail_func` 中添加分区选项：

  ```
  avail_func=("all" "lunch" "miniboot" "uboot" "factory" "boot" "hbre" "system" "app" "pack" "otapackage")
  ```

- 定义 `build_app` 函数：

  ```
  function build_app {
      is_exist=$(get_part_exist app)
      if [ "$is_exist" = "0" ]; then
          return
      fi
      build_component "app" "${HR_LOCAL_DIR}/mk_app.sh" "$@"
  }
  ```

- 在整体编译逻辑中加入 `app` 分区：

  ```
  if [ -d "${HR_TOP_DIR}/app" ]; then
      build_app "$opt"
  fi
  ```

以上代码添加完成后，可通过 `./bd.sh app` 或 `./mk_app.sh`  编译生成 `app.img`。

#### 5. 自动挂载新分区

在系统中添加新分区后，为确保分区能在启动时自动挂载，需要在 `system/buildroot/prebuilt/boot-utils-runtime/etc/hb-fstab` 文件中添加对应的挂载配置。例如，新增 `app` 分区的挂载配置如下：

```
/dev/block/platform/by-name/app  /app  ext4  defaults  0  1
```

以下是各字段的含义及相关注意事项：

- **设备路径** (`/dev/block/platform/by-name/app`)

  - 指定分区设备的路径。

  - 根据设备和分区配置，此路径可能有所不同，请确保与实际设备路径一致。

- **挂载点** (`/app`)

  - 指定分区挂载的目标目录。

  - 系统启动时，分区内容将挂载到此目录下。

  - 需要确保该目录已在镜像制作过程中创建，否则系统将无法挂载分区，可能导致启动失败。例如在 `mk_system.sh` 的 `build_unpack` 函数中添加目录创建逻辑：

    ```
    mkdir -p ${SYSTEM_BUILD_DIR}/{app,log,userdata,usr/hobot,data,private}
    ```

- **文件系统类型** (`ext4`)

  - 指定分区的文件系统类型。

  - 根据分区的实际文件系统格式选择合适的值（如 `ext4`、`vfat` 等）。

  - 此处必须与分区表中配置的 `fs_type` 一致。

- **挂载选项** (`defaults`)

  - 使用默认挂载选项。`defaults`是一个预定义的选项集合，它包括了以下特性：
    - **`rw`（读写模式）**：允许对分区进行读写操作。
    - **`suid`（允许设置用户 ID）**：允许执行文件的 SUID（Set User ID）和 SGID（Set Group ID）权限。这允许某些程序在执行时以文件所有者的权限运行。
    - **`dev`（允许设备文件）**：允许分区中存在设备文件（如`/dev`目录中的文件）。
    - **`exec`（允许执行文件）**：允许在分区中执行文件。
    - **`auto`（自动挂载）**：允许分区在系统启动时自动挂载。
    - **`nouser`（不允许普通用户挂载）**：只有 root 用户或具有特定权限的用户才能挂载该分区。
    - **`async`（异步 I/O）**：允许异步 I/O 操作，提高性能。

  - 在`app`分区的挂载配置中，`defaults`选项的具体含义如下：
    - 分区设备`/dev/block/platform/by-name/app`将以读写模式挂载到挂载点`/app`。
    - 分区中的文件可以设置 SUID/SGID 权限，以支持特定程序的权限需求。
    - 分区中的文件可以被系统执行，确保应用程序的正常运行。
    - 分区将在系统启动时自动挂载，无需手动干预。
    - 只有 root 用户或具有特定权限的用户可以挂载该分区，以增强系统的安全性。
    - I/O 操作将以异步方式执行，从而提高分区的性能表现。

  - `defaults` 选项可以确保分区在挂载时具备基本的读写和执行权限。

  - 如果需要自定义挂载参数，可根据需求替换为实际参数。例如：
    - `ro`：只读挂载。
    - `rw`：读写挂载（默认）。
    - `noexec`：禁止执行分区中的二进制文件。
    - `nosuid`：禁止设置 SUID 和 SGID 标志位。

- **备份标志** (`0`)

  - 指定该分区是否需要 `dump` 备份。

  - 设置为 `0` 表示不备份，是通常设置为 `0`。

- **文件系统检查顺序** (`1`)

  - 指定系统启动时检查文件系统的顺序。

  - `0` 表示不检查该分区。

  - 非零值表示检查顺序，数字越小，优先级越高。

#### 6. 打包分区进系统镜像

完成所有配置后，可以通过编译脚本生成分区镜像，并把新增的分区镜像 `pack` 到系统镜像中。

使用以下命令执行完整编译和打包流程：

```
./bd.sh
```

如果新增的分区是空分区（即在镜像生成时未包含实际数据，如 `userdata` 分区），可以选择跳过该分区的镜像打包操作。在 `xbuild.sh` 脚本的 `truncate_fill_image` 函数中，可以按如下方式调整逻辑：

```
# FIXME: If there is actual data in the partition behind the mirror, pack will be skipped.
# 先固定跳过 log 和 userdata 分区，要优化成根据配置来找到最后一个有数据的分区
case "${part_name}" in
    log*|userdata*|app*)
        rm -f "${HR_TARGET_PRODUCT_DIR}/${part_name}".img
        ;;
esac
```

完成以上步骤后，新增的分区（如 `app` 分区）将被编译并合并到系统镜像中。
如需验证，可以检查镜像文件 `out/product/app.img` 是否存在，并通过刷机或启动系统验证新增分区的功能是否正常。

### 验证

完成刷机后：

- 系统启动时如果用户通过串口连接设备，那么可以在串口的打印日志上看到 `This is the test code` 的日志输出，说明 `/app/startup.sh` 已经运行。

- 使用 `fdisk -l` 查看分区表，确认 `app` 分区存在，大小为 `700MB`（分区大小需要和实际的分区表配置对齐）。

- 使用 `mount` 确认分区已挂载至 `/app`。

- 使用 `ls /app` 查看分区内容，确认 `startup.sh` 存在并可执行。

  示例输出：

  ```
  # ls /app/
  startup.sh  lost+found
  ```
