# 第一章 环境与工具链
> 本篇是纯文字版,不含配图与录像。想看带图、带终端录像和视频的完整版,去课程网站:
>
> - 完整讲义:第一章 环境与工具链 · 讲义 — RISC-V Linux 课程
> - 完整实验:第一章 · 本章实验 · 三个实验
> - 课程总览:Ruyi RISC-V Linux 课程 · 总览
## 讲义
### 开始之前
本章目标是把开发环境与上板通道真正打通:你能在电脑上安装并使用 RuyiSDK 交叉工具链,把 RevyOS 烧到荔枝派 4A,用串口或 SSH 登录板子,并理解为什么必须用交叉编译才能生成板子可执行的程序。学完本章,你应当能在主机上交叉编出一份 RISC-V 架构的 CoreMark,并用 `file` 确认产物架构正确。
整章可以看成一条链:先认清板子与工具(1.1),再把系统烧进去并打通登录与传文件的通道(1.2),最后弄清交叉编译并用 Makefile 编出 CoreMark(1.3)。
**阅读与跟做顺序:**按 1.1 → 1.2 → 1.3 进行。每一节都含跟做命令;建议边做边保存关键终端输出。每节约 90–120 分钟。
#### 你需要准备什么
- **电脑**:Linux x86_64(推荐 Ubuntu 22.04+)。可联网,磁盘至少留 10GB。
- **荔枝派 4A**:一块开发板。配套:电源适配器、USB 数据线(能传数据)、3.3V USB-TTL 串口模块、网线或 Wi-Fi。
- **软件**:课程指定的 RevyOS 镜像、RuyiSDK(命令入口是 `ruyi`)、RISC-V 交叉编译工具链。安装步骤在 1.1–1.2。
#### 本章你会学到什么
- **1.1 荔枝派 4A 与 RuyiSDK**:认清板子接口;安装 ruyi;验证交叉编译器能编出 RISC-V 程序。
- **1.2 烧录、开发通道与一键准备/一键编译**:把系统写到板子上;打通串口、SSH、SCP;掌握一键准备与一键编译的用法。
- **1.3 交叉编译、Makefile 与 CoreMark**:弄清本机编译与交叉编译的差别;用 Makefile 交叉编出 CoreMark;用 `file` 确认产物是 RISC-V。
**学习建议:**每一步都对照「你应该看到…」核对输出;对不上就先停在当前命令查路径与工具链。
### 本章术语:先说清,再往下
下面这些词会在本章反复出现。先扫一遍,知道「它是什么、为什么需要它」;读到再见到就不慌,也可以随时翻回这一页。它们不是堆砌——每引入一个词,都是因为我们到了必须说清那一步的关口。
| 术语 | 是什么 | 为什么需要它 |
| — | — | — |
| **指令集** | CPU 认识的机器指令清单 | 决定一种 CPU 能执行哪些操作 |
| **x86_64 / riscv64** | 两种不同指令集 | 电脑和板子各用一套,程序不能互换 |
| **RISC-V** | 开放的指令集架构 | 本课板子的 CPU 用它 |
| **RevyOS** | 面向 RISC-V 的 Linux 发行版 | 烧进板子、跑在板子上的系统 |
| **交叉编译** | 在一台机器上编出另一架构的程序 | 电脑编出板子可运行的 RISC-V 程序 |
| **工具链** | 编译器 + 链接器 + 库的集合 | 把源码一步步变成可执行文件 |
| **三元组(target triple)** | 「CPU-厂商-系统」的简写 | 告诉编译器「给谁编」 |
| **ELF** | 可执行文件的一种容器格式 | `file` 靠它读出「这是什么架构」 |
| **镜像** | 整套系统的打包 | 烧录的对象 |
| **烧录** | 把镜像按分区写入板子存储 | 给板子装系统 |
| **UART / 串口** | 逐字节收发的物理接口 | 本地调试、看开机日志 |
| **SSH** | 加密的远程登录通道 | 日常登录板子敲命令 |
| **SCP** | 复用 SSH 传文件 | 电脑与板子之间拷程序 |
| **虚拟环境** | `ruyi venv` 建的开发环境 | 自动配好工具链路径与 sysroot |
| **sysroot** | 目标系统的运行库目录 | 链接时找 RISC-V 版 C 库 |
| **CoreMark** | 测处理器算力的基准程序 | 用一份真程序验证整条工具链通没通 |
| **Makefile** | 描述编译规则的文本 | 敲一个 `make` 就按规则编译 |
### 1.1 荔枝派 4A 与 RuyiSDK
#### 认识荔枝派 4A
**荔枝派 4A** 是本课用的 RISC-V 开发板:一块带 CPU、内存、存储接口和排针的电路板,用来跑 Linux、接传感器和执行器。
**指令集**是 CPU 认识的机器指令清单——规定「加、跳、读内存」等操作怎么编码成二进制。电脑常见 **x86_64** 指令集;荔枝派 4A 跑的是 **riscv64** 指令集。两边指令编码不同,所以电脑上用普通 `gcc` 编好的程序,不能直接丢到板子上跑。
**RISC-V** 是一种开放的指令集架构:规范公开,任何人可以做兼容芯片。本课板子上的 Linux 系统是 **RevyOS**——面向 RISC-V 板子的 Linux 发行版。
```text
RISC-V 指令集(规定 CPU 认哪些指令)
↓
荔枝派 4A(开发板:CPU + 内存 + 接口)
↓
RevyOS(板子上的 Linux 系统)
↓
RuyiSDK / ruyi(电脑上装工具链与镜像的入口)
```
动手前先在板子上标出:电源口、串口排针、网口、启动介质(eMMC / SD)。串口模块必须是 **3.3V** 电平——电平是引脚高低电压的约定;5V 可能损坏板子。
| 项目 | 要求 | 检查方式 |
| — | — | — |
| 电脑系统 | Linux x86_64 | `uname -a` |
| 开发板 | 荔枝派 4A | 核对板卡丝印与包装 |
| USB 线 | 能传数据(不只充电) | 换已知数据线试 |
| 串口模块 | 3.3V USB-TTL | 看模块规格丝印 |
| 网络 | 能访问 RuyiSDK 软件源 | 浏览器或连通性测试 |
| 基础命令 | `curl` / `wget`、`sha256sum`、`file` | `command -v curl` |
**常见陷阱**
· 部分 USB-C 线只供电、不传数据 → 电脑认不出设备时先换线
· 串口电平必须 3.3V → 5V 可能损坏板卡
· 工具链前缀选 `linux-gnu`(跑 Linux 的程序)→ `unknown-elf` 是裸机工具链,本课不用
#### 安装 RuyiSDK
**RuyiSDK** 是面向 RISC-V 开发的工具包集合;命令入口叫 `ruyi`,用来查包、装工具链、查设备支持。真正把 C 代码编成机器码的是里面的 **GCC**(GNU 编译器)。关系可以记成:ruyi 管「装什么」,GCC 管「怎么编」。
**三元组**(target triple)是编译器目标平台的简写,格式大致是「CPU-厂商-系统」。本课统一用 `riscv64-ruyisdk-linux-gnu`(经 RuyiSDK 的 gnu-ruyisdk 提供):意思是「为 64 位 RISC-V CPU、跑 Linux、用 GNU 用户态约定」生成程序。
把三元组从左到右拆开看,每一段都在说一件事:
| 字段 | 含义 | 本课取值 |
| — | — | — |
| **CPU** | 给哪种指令集生成代码 | `riscv64`(64 位 RISC-V) |
| **厂商/来源** | 谁提供的这套约定 | `ruyisdk` |
| **系统** | 跑在什么系统上 | `linux` |
| **用户态** | 用哪套库与 ABI | `gnu`(GNU / glibc) |
把用户态一段换成 `elf`(如 `riscv64-unknown-elf`),就变成裸机工具链——没有 Linux 的加载与运行库,编出的程序跑不了本课这样的 Linux 用户程序。
安装包管理器本身请以 RuyiSDK 官方安装文档为准。官方推荐使用预编译二进制;对于已配置 Python 环境的 x86 Linux 主机,也可以通过 PyPI 安装。下面按 PyPI 路径写出本课步骤。
1. **记录电脑环境**:在 **x86 Linux** 上执行 `uname -m`,确认输出为 `x86_64`。
2. **安装 Ruyi 包管理器**:预编译二进制的下载与放置见官方文档。本课在已配置 Python 的 x86 Linux 上走 PyPI:
\`\`\`bash
python3 -m pip install --user ruyi
\`\`\`
若 \`pip\` 把可执行文件装到 \`\~/.local/bin\`,请确认该目录已在 \`PATH\` 中(可把 \`export PATH="$HOME/.local/bin:$PATH"\` 写入 \`\~/.bashrc\` 后 \`source \~/.bashrc\`)。其它发行版安装方式见官方文档。
3. **PATH 检查与版本验证**:`command -v ruyi` 应给出绝对路径(例如 `/home/你的用户名/.local/bin/ruyi`);`ruyi --version` 应打印版本号(本课验收样例主机为 `Ruyi 0.46.0`,你机器上的次版本可以更新,但必须能运行)。
4. **更新并安装 gnu-ruyisdk 工具链**:`ruyi update`,再 `ruyi install gnu-ruyisdk`。
5. **创建并激活虚拟环境**:(目录自定,下文以家目录为例):
\`\`\`bash
ruyi venv -t gnu-ruyisdk manual venv-gnu-ruyisdk
. ~/venv-gnu-ruyisdk/bin/ruyi-activate
\`\`\`
每次新开终端都要重新激活。磁盘紧张时可加 \`--without-sysroot\`(仍能交叉编译本课 CoreMark)。
6. **确认交叉编译器**:`riscv64-ruyisdk-linux-gnu-gcc -v`;`riscv64-ruyisdk-linux-gnu-gcc -dumpmachine` 应打印 `riscv64-ruyisdk-linux-gnu`。
```bash
# 以下均在 x86 Linux 上执行
$ uname -m
x86_64
$ python3 -m pip install --user ruyi
$ export PATH=“$HOME/.local/bin:$PATH” # 若尚未写入 shell 配置
$ command -v ruyi
/home/你的用户名/.local/bin/ruyi
$ ruyi --version
Ruyi 0.46.0
# …版权与许可信息略…
$ ruyi update
$ ruyi install gnu-ruyisdk
$ ruyi venv -t gnu-ruyisdk manual venv-gnu-ruyisdk
$ . ~/venv-gnu-ruyisdk/bin/ruyi-activate
$ riscv64-ruyisdk-linux-gnu-gcc -dumpmachine
riscv64-ruyisdk-linux-gnu
```
#### 最小编译验证
写一个空程序,用交叉编译器编一下,再用 `file` 看产物是不是 RISC-V。**ELF** 是 Linux 上可执行文件与目标文件的常见容器格式;`file` 会读出里面的架构字段。
```bash
$ printf ‘int main(void) { return 0; }\n’ > mini.c
$ riscv64-ruyisdk-linux-gnu-gcc mini.c -o mini-riscv
$ file mini-riscv
mini-riscv: ELF 64-bit LSB executable, UCB RISC-V, …
```
在电脑上直接 `./mini-riscv` 会失败——指令集不同。验收看 `file` 输出里有 RISC-V 字样即可。
**本节成果:**荔枝派 4A 接口标注 · 个人环境清单 · `ruyi` 已安装且 PATH 可用 · 版本记录 · 三元组 `riscv64-ruyisdk-linux-gnu` · 最小 RISC-V 可执行文件与 `file` 日志。
### 1.2 烧录、开发通道与一键准备/一键编译
#### 烧录系统
**烧录**是把系统镜像按分区写入板子存储(本课用板载 eMMC)。镜像里通常有:引导程序(U-Boot)、启动分区(内核与设备树等)、根文件系统(RevyOS 用户空间)。
**本课指定组合(与验收样机一致):**
- 硬件:Sipeed **LicheePi 4A 16G**(设备树 model 为 `Sipeed Lichee Pi 4A 16G`)
- 系统:**RevyOS 20251226**(板端 `/etc/revyos-release` 中 `RELEASE_ID=20251226`、`BOARD_NAME=lpi4a`)
- 主路径:用 RuyiSDK 的 `ruyi device provision` 交互式下载并写入(底层走 fastboot;包索引中对应 `revyos-sipeed-lpi4a` + `uboot-revyos-sipeed-lpi4a-16g`)
对照资料(排障与手工备用时查阅):
- RuyiSDK Support Matrix · LicheePi 4A / RevyOS(当前 good 记录即 20251226;含镜像目录与手工 fastboot 命令)
- Sipeed 官方烧录文档(BOOT 键进烧录模式、接线说明)
```text
主机:ruyi device provision
├─ 选设备:Sipeed LicheePi 4A
├─ 选规格:16G RAM
└─ 选系统:RevyOS 20251226
↓ 下载镜像(含校验)
按住 BOOT → 插入 USB-C → fastboot 写入 eMMC
↓ 松开 BOOT,重新上电
串口 / SSH 出现登录提示(用户 debian)
```
##### 环境准备
- 主机已按 1.1 装好 `ruyi`,并能 `ruyi --version`。
- 主机安装 `fastboot`(Debian/Ubuntu 示例:`sudo apt install -y fastboot` 或包名 `android-tools-fastboot`,以发行版为准)。
- USB-C 数据线能传数据(仅充电线无法进烧录模式);另备 USB-UART 串口线(3.3V)便于看首次启动日志。
- 板子供电充足;烧录会覆盖 eMMC 上的系统,先备份需要保留的文件。
##### 用 ruyi device provision 烧录(主路径)
1. **更新索引并启动向导**:在主机执行 `ruyi update`,再 `ruyi device provision`。按提示确认后继续。
2. **选择设备与规格**:设备选 **Sipeed LicheePi 4A**;内存规格选 **16G**(8G 板不要选 16G 的 U-Boot 包)。
3. **选择系统镜像版本**:选 **RevyOS**,版本选 **20251226**(与 Support Matrix 当前 good 记录、本课验收样机一致)。向导会下载并校验对应镜像;下载目录以终端提示为准。
4. **进入烧录模式**:板子断电。按住板上 **BOOT** 键不放,再插入连向主机的 USB-C;主机应能枚举到 fastboot 设备。保持按住直至 `fastboot devices` 能看到设备(向导也会提示时机)。
5. **写入并观察预期输出**:按向导确认后开始写入。成功时终端会出现各分区 flash 完成、无 error 的提示;失败则根据报错检查线材、BOOT 时机与 `fastboot devices`。写入完成后按提示松开 BOOT、重新上电。
```bash
# 主机:启动一键准备(交互菜单以本机 ruyi 为准)
$ ruyi update
$ ruyi device provision
# 选择:Sipeed LicheePi 4A → 16G → RevyOS 20251226
# 进入烧录模式后,另开终端可自检(可选)
$ sudo fastboot devices
# 期望:出现一列设备序列号 + fastboot
```
**手工 fastboot 备用:**若课堂要求对照 Support Matrix 手工写入,镜像目录为 20251226 镜像目录。下载 `u-boot-with-spl-lpi4a-16g.bin`、`boot-lpi4a-20251225_175338.ext4.zst`、`root-lpi4a-20251225_175338.ext4.zst`,用 `zstd -d` 解压后,按 Support Matrix 中的 `fastboot flash` 顺序写入。细节以该 README 与 Sipeed 文档为准。
##### 首次启动验证(验收样机实输出)
默认用户 / 密码多为 `debian` / `debian`(以镜像说明为准)。串口或 SSH 登录后执行:
```bash
$ cat /sys/firmware/devicetree/base/model; echo
Sipeed Lichee Pi 4A 16G
$ cat /etc/revyos-release
BUILD_ID=20251225_175338
BUILD_DATE=20251225
BOARD_NAME=lpi4a
RELEASE_ID=20251226
COMMIT_ID=65b79515309b7d77816def1e83f0d5a95178ab70
RUNNER_ID=20508594622
$ uname -m
riscv64
$ hostname
revyos-lpi4a
$ free -h | head -2
total used free shared buff/cache available
Mem: 15Gi …
$ ip -br addr
# 期望:除 lo 外,有网口(如 wlan0 / end0)拿到局域网 IP
```
| 检查项 | 命令 | 期望(本课样机) |
| — | — | — |
| 板型 | `cat /sys/firmware/devicetree/base/model` | 含 `Lichee Pi 4A 16G` |
| RevyOS 发行标识 | `cat /etc/revyos-release` | `RELEASE_ID=20251226`、`BOARD_NAME=lpi4a` |
| 架构 | `uname -m` | `riscv64` |
| 内存规格 | `free -h` | 总量约 15–16 Gi(16G 板) |
| 账户 | `id` | 常见为用户 `debian` |
| IP 地址 | `ip -br addr` | 有有效局域网 IP |
| 路由 | `ip route` | 有默认网关 |
| 时间 | `timedatectl` | 时间正确(错了会导致 TLS 失败) |
| 软件源 | `sudo apt update` | 更新成功 |
**说明:**`/etc/os-release` 可能显示底层 Debian 代号(如 forky/sid),确认「是不是本课这套 RevyOS」请看 `/etc/revyos-release`,不要只看 `PRETTY_NAME`。
#### 三条开发通道
- **串口(UART)——本地调试线**:**UART** 是按位串行收发的接口。接线:GND-GND、TX→RX、RX→TX,3.3V。不依赖网络,直接看启动日志。
- **SSH——远程主通道**:**SSH** 是加密的远程终端。日常开发用它登录板子。本课用 Ed25519 密钥登录;首次连接须核对主机指纹。
- **SCP——文件传输**:**SCP** 复用 SSH 连接拷文件。上传后在板端跑 `sha256sum`,与电脑端哈希一致即传输完整。
**为什么需要三条:**三条通道解决三类不同的问题,按场景挑:串口不依赖网络,板子起不来、SSH 还连不上时用它看开机日志;SSH 是日常的加密登录;SCP 专门在电脑与板子之间传文件。
| 通道 | 怎么连 | 要网络吗 | 加密吗 | 典型场景 |
| — | — | — | — | — |
| **串口 UART** | 物理线直连板子排针 | 否 | 否 | 开机引导日志、SSH 连不上时救急 |
| **SSH** | 走网络连板子 IP | 是 | 是 | 日常登录敲命令 |
| **SCP** | 复用 SSH 连接 | 是 | 是 | 电脑与板子之间传文件 |
```bash
# 板端启用 SSH
$ sudo systemctl enable --now ssh
# 电脑生成密钥
$ ssh-keygen -t ed25519 -C “riscv-course”
# 首次连接——核对指纹后再输入 yes
$ ssh user@board-ip
# 部署公钥并免密验证
$ ssh-copy-id user@board-ip
$ ssh user@board-ip ‘hostname; uname -m’
```
```bash
# SCP 上传并校验
$ printf ‘transfer test\n’ > test.txt
$ sha256sum test.txt
$ scp test.txt user@board-ip:/tmp/
$ ssh user@board-ip ‘sha256sum /tmp/test.txt’
```
**指纹变更:**可能是板子重装系统,也可能连错了设备。先核对 IP 与串口里的主机指纹,再清理 `known_hosts` 旧记录。
#### 一键准备与一键编译
手工烧录与手工 SSH/SCP 之外,ruyi 提供两类自动化能力:
- **一键准备**:按设备支持列表自动下载镜像并写入板子存储,适合设备明确受支持、写入目标介质正确的场景。
- **一键编译**:封装交叉工具链调用与常见构建参数,适合快速验证工具链或标准化构建。
```bash
# 以本机 ruyi 帮助为准
$ ruyi device --help
$ ruyi device provision --help
```
**本课定位:**烧录主路径是 `ruyi device provision`(选 LicheePi 4A / 16G / RevyOS 20251226)。一键编译与手工 SCP/SSH 也要掌握。Support Matrix 与 Sipeed 文档用于对照与排障。
**本节成果:**provision 选型记录 · 烧录成功日志或截图 · 首次启动检查表(含 `revyos-release`)· 串口接线记录 · SSH 密钥登录 · SCP 双向哈希一致。
### 1.3 交叉编译、Makefile 与 CoreMark
#### 为何必须交叉编译
开发电脑的 CPU 执行 **x86_64**(少数机器是 ARM)机器码;荔枝派 4A 执行 **riscv64** 机器码。两边指令编码不同。
在电脑上用本机 `gcc main.c -o app`,得到的是本机架构的 ELF,只能在本机跑。拷到板端,Linux 内核会拒绝加载,常见报错是 `Exec format error`。板端编好的 RISC-V 程序拷回电脑,同样跑不了。
**为什么内核会拒绝?**可执行文件开头有一段 **ELF 头**,其中的 `e_machine` 字段写明这份文件属于哪种架构。内核加载程序时检查这个字段:和当前 CPU 架构不一致就拒绝加载,报 `Exec format error`。`file` 读的正是同一个字段——所以 `file` 说它是 RISC-V,就预判了它在 x86 电脑上跑不了;说它是 x86-64,就预判了它在板子上跑不了。
在荔枝派上实测(本课真板):
```bash
# 把两种产物都拷到板子 /tmp 再运行
$ scp mini-riscv mini-host debian@板子IP:/tmp/
$ ssh debian@板子IP ‘cd /tmp && ./mini-riscv; echo “exit=$?”’
# exit=0 —— RISC-V 产物在板端正常跑
$ ssh debian@板子IP ‘cd /tmp && ./mini-host; echo “exit=$?”’
bash: line 1: ./mini-host: cannot execute binary file: Exec format error
# exit=126 —— x86 产物被内核拒绝,报 Exec format error
```
**交叉工具链**仍在电脑上运行,但专门生成 RISC-V 机器码,并链接面向 RISC-V Linux 的 C 库。本课经 RuyiSDK 安装,前缀是 `riscv64-ruyisdk-linux-gnu-`。**三元组** `riscv64-ruyisdk-linux-gnu` 标明:CPU 为 riscv64、系统为 Linux、用户态约定为 GNU。选成 `unknown-elf` 会得到裸机工具链,不适合本课跑 Linux 用户程序。
#### 同一份源码,三条编译路径
同一份 `main.c` 可以走出三种产物。分清「编给谁、能在哪跑」:
```text
同一份 main.c
│
├───────────────┬──────────────────┬───────────────
│ │ │
▼ ▼ ▼
电脑自带 gcc riscv64-ruyisdk- 板端自带 gcc
(本机工具链) linux-gnu-gcc (板子上的 RISC-V gcc)
│ │ │
│ 生成 x86_64 │ 生成 RISC-V │ 生成 RISC-V
▼ ▼ ▼
app-host app-riscv app-board
只能在电脑跑 架构正确,须传到板端 可在板端就地跑
上板 → Exec format 编给板子 (对照路径)
error
```
抓住三点:① 编译器选错,架构就错;② 用 `file` 读 ELF 架构字段验收;③ **交叉编译只解决「编对架构」**——把文件传到板端并运行,是传输与加载的另一步。
交叉工具链内部仍是编译 → 汇编 → 链接,目标一律是 RISC-V:产出 RISC-V 汇编与 `.o`,再链上 RISC-V 版 C 运行库(sysroot)。
```text
交叉编译内部(都在电脑上执行)
main.c
↓ 编译器:C → RISC-V 汇编
main.s
↓ 汇编器:汇编 → 目标文件
main.o
↓ 链接器:.o + RISC-V C 库 → 可执行文件
app-riscv(ELF,riscv64)
```
板端若已安装 `gcc`,可在板子上直接编译就地运行(上图第三条路)。本课后续章节会用到;板端原生编译可作对照,但不能代替主机交叉产物。
#### 亲眼看到指令差异:反汇编
「指令集不同」光听概念记不牢。把同一份 `main.c` 用两种编译器各编一份,再用 `objdump -d` 把机器码反回汇编,两边从助记符到编码完全不同:
```bash
# 交叉编译 → RISC-V 反汇编
$ riscv64-ruyisdk-linux-gnu-objdump -d mini-riscv | sed -n ‘/:/,/^$/p’
0000000000000614 :
614: 1141 addi sp,sp,-16
616: e406 sd ra,8(sp)
61c: 4781 li a5,0
61e: 853e mv a0,a5
# 本机编译 → x86-64 反汇编
$ objdump -d mini-host | sed -n ‘/:/,/^$/p’
0000000000001129 :
1129: f3 0f 1e fa endbr64
112d: 55 push %rbp
1131: b8 00 00 00 00 mov $0x0,%eax
1137: c3 ret
```
同样是「把 0 放进返回值」这一个动作,RISC-V 用 `li a5, 0` 加 `mv a0, a5` 两条指令,x86 用 `mov $0x0, %eax` 一条——助记符、寄存器、机器码编码全不一样。这就是为什么两边产物不能互换:内核检查 ELF 头时直接拒收。
#### Makefile 跟做:本机 vs 交叉
**Makefile** 描述如何编译工程;敲 `make` 按规则调用编译器。把交叉前缀写进 Makefile,避免每次手敲冗长命令。
```bash
# Makefile — 交叉编译模板
CROSS_COMPILE := riscv64-ruyisdk-linux-gnu-
CC := $(CROSS_COMPILE)gcc
CFLAGS := -O2 -Wall
TARGET := app
$(TARGET): main.c
$(CC) $(CFLAGS) main.c -o $(TARGET)
clean:
rm -f $(TARGET)
.PHONY: clean
```
用同一份源码对照两条路径(讲义跟做到 `file` 为止,不必 SCP):
```bash
$ printf ‘int main(void) { return 0; }\n’ > main.c
$ gcc main.c -o app-host
$ file app-host
# … ELF … x86-64 …(本机架构)
$ ./app-host
# 本机可执行
$ make
# 或:riscv64-ruyisdk-linux-gnu-gcc main.c -o app
$ file app
# … ELF … UCB RISC-V …
$ ./app
# 本机应失败(指令集不对)
```
**常见陷阱:**忘记设 `CC` / `CROSS_COMPILE` 会默默用本机 GCC,产物在板端报 `Exec format error`。先用 `file` 查架构。Makefile 配方行必须以 Tab 缩进。
#### 构建 CoreMark(讲义收口:编出 + file)
**CoreMark** 是测处理器算力的小基准程序,输出可比较的分数。本章用它走通工具链:先在主机交叉编出 RISC-V 可执行文件,再传到板端并跑出分数。
```bash
# 在 x86 Linux 上:先激活虚拟环境,再进入课程仓库
$ . ~/venv-gnu-ruyisdk/bin/ruyi-activate
$ cd chapters/ch01/code
$ make
$ file coremark/coremark.exe
coremark/coremark.exe: ELF 64-bit LSB executable, UCB RISC-V, …
```
源码已放在 `chapters/ch01/code/coremark/`(官方 CoreMark)。Makefile 在 **x86 Linux** 上自动选用交叉编译器;在**荔枝派**上则自动使用板载 `gcc`。
**本节收口:**产物文件名因版本而异(常见 `coremark.exe`)。请确认 `file` 输出中含 RISC-V 或 UCB RISC-V 字样,并保存该可执行文件与对应终端日志。请打开本章实验继续。
**本节成果:**能口述指令集差异与交叉编译原因 · 能画三条路径并说明本章实验走哪条 · Makefile 交叉模板 · 本机/交叉 `file` 对照 · CoreMark 的 RISC-V 可执行文件(尚未上板)。
## 实验
**本章实验 · 三个实验** —— 实验一 板端原生跑通 probe · 实验二 架构陷阱(编错就上不了板)· 实验三 上板通道验收(CoreMark)
### 本章三个实验一览
本章全部围绕「程序怎么到板子上跑起来」这一件事,拆成三个递进的实验。实验一、实验二只需要软件(板端 gcc),没有硬件器材;实验三是正式的通道验收。三个实验都必须在板上跑通、留下可复查证据才算过关。
| 实验 | 层次 | 一句话目标 | 跑在哪 |
| — | — | — | — |
| **实验一**
板端原生:自己编、自己跑 | 软件 | 最小程序 `probe` 在板端用 `make` 编出 RISC-V 产物并跑通 | 荔枝派(原生) |
| **实验二**
架构陷阱:编错就上不了板 | 软件 | 把主机 gcc 编出的 x86 产物传上板,亲眼看到 `Exec format error`,理解为何要「对架构编译」 | 主机 + 荔枝派 |
| **实验三**
上板通道验收(CoreMark) | 通道验收 | 交叉编 CoreMark → SCP 上传 → 板端跑分,归档整套通道证据 | 主机交叉 + 荔枝派 |
**器材:**实验一、实验二只需要「已能 SSH 登录的荔枝派 4A + 板端自带 gcc/make」(讲义 1.2 之后已具备),**不需要任何外设或接线**。
### 实验一 · 板端原生:自己编、自己跑(probe)
荔枝派跑的是一套完整的 Linux,板子上自带 `gcc` 与 `make`(讲义 1.3「三条开发路径」中的板端原生路径)。本实验让板子「自己编、自己跑」一个最小程序,并用 `file` / `readelf` 认一认产物确实是 RISC-V。
#### 步骤
源码在仓库 `chapters/ch01/code/probe/`(一个 `probe.c` + 一个 `Makefile`)。先把它传到板子:
```bash
# 主机上执行:把 probe 目录整个传到板端家目录
$ scp -r chapters/ch01/code/probe user@board-ip:~/
```
然后登录板子,编译、认架构、运行:
```bash
# 板端 shell
$ cd ~/probe
$ make
$ file probe
# 期望输出里含 UCB RISC-V;若没有,说明 make 用的不是板端 gcc
$ readelf -h probe | grep Machine
# 期望:Machine: RISC-V
$ ./probe
# 期望:
# arch = riscv
# ptr size = 8 bytes
# probe done.
```
**本实验成功判据:**`file probe` 出现 RISC-V;`readelf -h` 的 Machine 为 RISC-V;`./probe` 正常打印三行并退出。把三处输出抄进下表。
| 记录字段 | 你的记录 |
| — | — |
| 板端 `uname -m` | |
| `file probe` 一行摘要 | |
| `readelf -h probe \| grep Machine` 输出 | |
| `./probe` 输出(三行) | |
**验收:**上表字段齐全、值正确,即通过实验一。
(配图说明:板端证据(SSH → probe):原生 `make`、`file` 为 RISC-V、`./probe` 跑通)
### 实验二 · 架构陷阱:编错就上不了板
实验一证明「板端原生 gcc 编的能跑」。现在反过来看:**主机 gcc 编的程序**,架构是 x86-64,直接传上板子会怎样?本实验把同一份 `probe.c` 在主机编一次、在板端编一次,两份都拿到板子上跑,亲自确认「对架构编译」这件事。
#### 步骤
```bash
# 主机上执行(这台是 x86 Linux):make 得到 x86-64 产物
$ cd chapters/ch01/code/probe
$ make
$ file probe
# 期望:x86-64 / Advanced Micro Devices X86-64,而不是 RISC-V
$ readelf -h probe | grep Machine
# 期望:Machine: Advanced Micro Devices X86-64
$ scp probe user@board-ip:/tmp/probe.x86
```
```bash
# 板端 shell:先确保自己有 riscv 版,再试跑传上来的 x86 版
$ cd ~/probe && make # 若实验一已编过,这步会跳过
$ file probe
# 期望:UCB RISC-V
$ chmod +x /tmp/probe.x86
$ /tmp/probe.x86
# 期望:cannot execute binary file: Exec format error(退出码 126)
# —— x86 程序在 RISC-V 板上跑不起来,而不是「程序崩溃」
$ ./probe
# 期望:正常打印 arch = riscv —— 板端自己编的就能跑
```
**理解:**`Exec format error` 不是程序写错,而是**产物架构和 CPU 对不上**。要让板子跑,程序必须编成 RISC-V:要么在板端原生编,要么在主机用交叉工具链编(实验三用后者)。讲义 1.3 的反汇编对比,让你看到同一份 C 在不同架构下生成的指令根本不同。
| 记录字段 | 你的记录 |
| — | — |
| 主机 `file probe` 一行摘要(x86-64) | |
| 板端 `file probe` 一行摘要(RISC-V) | |
| 板端跑 `/tmp/probe.x86` 的错误行(`cannot execute binary file: Exec format error`) | |
| 板端跑 `./probe` 的输出首行 | |
| 一句话结论(为什么 x86 产物上不了板) | |
**验收:**两张 `file` 摘要架构不同;x86 产物在板上报 `Exec format error`;riscv 产物正常跑;结论句写对。
(配图说明:板端证据(SSH → 架构陷阱):`/tmp/probe.x86` → Exec format error;板端 `./probe` 正常)
### 实验三 · 上板通道验收(CoreMark)
实验一、实验二已经证明:板端能原生编、能跑;主机默认 gcc 的产物上不了板。实验三把整条「主机交叉编译 → 传输 → 板端运行」通道用到正式程序 CoreMark 上,并留下可复查的分数与证据。
### 本实验要做什么
本实验要证明一件事:你在主机上用交叉工具链编出的 RISC-V 程序,能够完整传到荔枝派 4A,并在板端真正跑起来。我们用 CoreMark 作为这份程序:你将留下编译产物、传输核对与板端运行的完整证据,使整条通道可以被复查。分数用来确认程序确实执行成功;可复查的通道证据,才是本章实验要交付的成果。
开始之前,请确认讲义 1.1 至 1.3 已经做完:交叉工具链可用,板子可以登录,主机上已经交叉编出 CoreMark,并且用 `file` 确认产物架构是 RISC-V。交叉编译的完整命令以讲义为准;本页从「核对已有产物」开始,完成传输、跑分与归档。
你将依次完成这些事:在主机上再次用 `file` 与 `sha256sum` 核对产物;用 SCP 把文件传到板子并确认两端哈希一致;在板端加上执行权限并跑出 CoreMark 分数;把分数、日志与通道自证输出写入记录表。
**通过标准:**记录表与验收表中的项目全部齐全。本章实验采用主机交叉编译得到的可执行文件完成上板;讲义中介绍的板端原生编译可作为对照理解,但本章实验的通过条件以交叉编译产物为准。
### 前提自检
请先确认下表每一项都已具备。若有缺项,回到对应讲义小节补齐后再继续本页步骤。
| 前提 | 你怎么确认「已经具备」 | 对应讲义 | □ |
| — | — | — | — |
| ruyi 与交叉工具链已安装 | 在 **x86 Linux** 上激活 `venv-gnu-ruyisdk` 后执行 `riscv64-ruyisdk-linux-gnu-gcc -dumpmachine`,输出为 `riscv64-ruyisdk-linux-gnu` | 1.1 | |
| 荔枝派 4A 已烧录并可启动 | 上电能登录;`cat /etc/revyos-release` 中 `RELEASE_ID=20251226`;`uname -m` 为 `riscv64`(讲义 1.2) | 1.2 | |
| 串口或 SSH 至少一种可登录板端 | 能进入板端 shell,并执行简单命令(如 `hostname`) | 1.2 | |
| 主机上已有 RISC-V 架构的 CoreMark 可执行文件 | 对产物执行 `file`,输出中含 `RISC-V` 或 `UCB RISC-V`(架构为 x86-64 则需回到讲义 1.3 重编) | 1.3 | |
### 步骤
下面是本实验的主体步骤。若交叉编译命令需要重做,请回到讲义 1.3。
#### 1. 在主机确认 CoreMark 产物架构
在 **x86 Linux** 上进入 `chapters/ch01/code`(或板上同步目录 `~/ruyi-riscv-embedded/ch01-lab` 的主机交叉产物):
```bash
$ file coremark/coremark.exe
# 成功:一行摘要里应出现 UCB RISC-V 或 RISC-V 字样
# 失败:若出现 x86-64 / Intel 80386,说明没用交叉工具链,回讲义 1.3 重编
$ sha256sum coremark/coremark.exe
# 记下这串哈希,稍后与板端对照,证明 SCP 传的是同一份文件
```
**本步成功判据:**`file` 确认是 RISC-V ELF;你已把 `sha256sum` 结果抄进下方「必记字段」表。
(配图说明:图 1 在 x86 Linux 上激活 `venv-gnu-ruyisdk` 后 `make`:产物为 RISC-V ELF)
#### 2. SCP 上传到荔枝派 4A 并核对完整性
**SCP**(Secure Copy)通过 SSH 通道把文件拷到远端。把下面命令里的 `user` 和 `board-ip` 换成你板子的真实用户名与局域网 IP(可先在板端执行 `ip a` 查看)。
```bash
$ scp coremark/coremark.exe user@board-ip:/tmp/
$ ssh user@board-ip ‘sha256sum /tmp/coremark.exe’
# 板端打印的哈希必须与主机上一步完全一致
```
**本步成功判据:**两端哈希一致;板端路径 `/tmp/coremark.exe`(或你实际选用的路径)可被列出。
#### 3. 板端赋予执行权限并跑分
```bash
$ ssh user@board-ip
# 已进入板端 shell 后:
$ chmod +x /tmp/coremark.exe
$ /tmp/coremark.exe
```
程序会运行一段时间后打印结果。请从输出中抄录 CoreMark 分数(常见字段为 `Iterations/Sec`,或汇总行里的 CoreMark 总分),并把完整终端输出另存为日志文件(截图或重定向均可),同时写入下方「必记字段」表。
**本步成功判据:**程序正常结束并打出分数;你已把分数与运行日期写入「必记字段」表,并保存完整日志。
(配图说明:图 2 板端证据(SSH → CoreMark):产物为 RISC-V,跑分通过校验(并列原生路径;验收仍以交叉+SCP 为准))
本实验验收走步骤 2–3 的 SCP 路径。图 2 是板上自己编译/跑分的对照,两条都会即可。
**排错:**板端报 `Exec format error` 时,先回主机用 `file` 核对是否误用了主机 GCC;再核对 SCP 目标路径与 `chmod +x` 是否做过。若 SSH 连不上,回讲义 1.2 检查网线、IP 与防火墙。命令细节以讲义 1.2 / 1.3 为准。
#### 必记字段
| 字段 | 你的记录 |
| — | — |
| 产物文件名 | |
| 主机 `file` 一行摘要 | |
| 主机与板端 `sha256sum`(须一致) | |
| 板端可执行文件路径 | |
| CoreMark 分数(含字段名) | |
| 运行日期 / 板卡型号 | |
#### 4. 通道与工具链自证
除跑分外,还要留下三份「通道还活着」的证据。每条保留一行真实输出(可粘贴到实验报告)。
```bash
# 板端:证明 SSH 能登录,且架构是 riscv64
$ ssh user@board-ip ‘hostname; uname -m’
# uname -m 的期望输出:riscv64
# 主机:证明交叉工具链三元组正确
$ riscv64-ruyisdk-linux-gnu-gcc -dumpmachine
# 期望输出:riscv64-ruyisdk-linux-gnu
```
**本步成功判据:**三行输出均已保存;`uname -m` 为 `riscv64`;`-dumpmachine` 为 `riscv64-ruyisdk-linux-gnu`。
### 验收表
全部勾选且材料齐全,即完成本章实验。缺证据视为未通过。
| 验收项 | 证据 | □ |
| — | — | — |
| 烧录后荔枝派 4A 可启动 | 启动日志或串口/SSH 登录截图 | |
| SSH 可登录板端 | `ssh … ‘hostname; uname -m’` 输出 | |
| `uname -m` 为 `riscv64` | 同上日志中的架构行 | |
| 工具链三元组正确 | `-dumpmachine` → `riscv64-ruyisdk-linux-gnu` | |
| CoreMark 交叉编译产物为 RISC-V | 主机 `file` 输出 | |
| SCP 上板成功且文件完整 | 两端 `sha256sum` 一致 | |
| 板端跑出 CoreMark 分数并已记录 | 完整运行日志 + 「必记字段」表 | |
**完成标志:**验收表全部勾选;CoreMark 分数与三项自证输出已归档。通道打通后进入第二章。