发散创新:手搓一个 RISC-V 用户态 Shell —— 从零实现 rvsh,深度融入 RISC-V 生态

RISC-V 不再只是教科书里的指令集架构(ISA)。随着 QEMU、Spike、OpenTitan、Kendryte K210、StarFive VisionFive 2 等软硬件平台的成熟,真正的 RISC-V 开发者闭环正在形成:从汇编调试 → C 运行时 → libc 移植 → 用户空间工具链 → 定制化系统服务。本文不讲“RISC-V 是什么”,而是带你亲手实现一个轻量、可调试、可扩展的 RISC-V 用户态 Shell —— rvsh,它运行在 riscv64-unknown-elf-gcc 编译的裸机环境(如 Spike + pk)或 Linux 用户态模拟器中,代码完全开源、无依赖、支持管道、重定向、内置命令与外部 ELF 执行。


为什么是 Shell?—— RISC-V 生态的“最后一公里”

当前 RISC-V 生态存在明显断层:
✅ 工具链完备(riscv64-unknown-elf-gcc, binutils, gdb
✅ 模拟器稳定(Spike, QEMU, Renode)
✅ OS 支持活跃(Linux mainline, Zephyr, RT-Thread)
缺乏面向开发者的一线交互界面 —— 大多数裸机 demo 仍靠 printf 打印状态,或硬编码测试逻辑。

一个能 lscat|> 的 shell,是验证 syscall 接口、测试 libc 实现、调试用户程序行为的最直接载体。它不是玩具,而是生态成熟度的温度计。


架构设计:极简但不失扩展性

rvsh 采用分层设计,全部用标准 C11 编写,零第三方依赖,仅需 riscv64-unknown-elf-gcc -static 即可构建:

┌───────────────────────┐
│        rvsh           │ ← 主循环:read-eval-exec-print
├───────────────────────┤
│  builtin_cmd.c        │ ← cd / exit / echo / help
├───────────────────────┤
│  parser.c             │ ← tokenize("ls -l | grep main") → AST
├───────────────────────┤
│  executor.c           │ ← fork/execve + pipe()/dup2() + waitpid()
├───────────────────────┤
│  syscalls.h           │ ← 封装 __NR_write, __NR_openat 等(基于 Linux ABI)
└───────────────────────┘

✅ 所有 syscall 调用均通过 inline asm volatile ("ecall") 直接触发,绕过 glibc,真实暴露 RISC-V ecall ABI 行为


核心代码:30 行实现管道执行(|

关键逻辑在 executor.c 中:

// exec_pipe: 执行形如 "cmd1 | cmd2" 的管道
int exec_pipe(char *cmd1[], char *cmd2[]) {
    int pipefd[2];
        pid_t pid1, pid2;
    if (pipe(pipefd) == -1) return -1;
    pid1 = fork();
        if (pid1 == 0) { // child 1: cmd1 → stdout → pipe write
                close(pipefd[0]);
                        dup2(pipefd[1], STDOUT_FILENO);
                                close(pipefd[1]);
                                        execvp(cmd1[0], cmd1);
                                                _exit(127);
                                                    }
    pid2 = fork();
        if (pid2 == 0) { // child 2: pipe read → stdin → cmd2
                close(pipefd[1]);
                        dup2(pipefd[0], STDIN_FILENO);
                                close(pipefd[0]);
                                        execvp(cmd2[0], cmd2);
                                                _exit(127);
                                                    }
    // parent waits
        close(pipefd[0]); close(pipefd[1]);
            waitpid(pid1, NULL, 0);
                waitpid(pid2, NULL, 0);
                    return 0;
                    }
                    ```
⚠️ 注意:在裸机 `pk`(proxy kernel)环境下,`fork()` 和 `execvp()` 由 `newlib` 或自研 libc 提供;若运行于 Linux 用户态(`riscv64-linux-gnu-gcc`),则直接调用 glibc。

---

## 快速上手:5 分钟跑起来(Spike + pk)

### 步骤 1:准备环境(Ubuntu 22.04)

```bash
sudo apt install -y build-essential device-tree-compiler
git clone https://github.com/riscv/riscv-gnu-toolchain
cd riscv-gnu-toolchain && ./configure --prefix=/opt/riscv --enable-multilib && make -j$(nproc)
export PATH=/opt/riscv/bin:$PATH

步骤 2:编译 rvsh

git clone https://github.com/yourname/rvsh && cd rvsh
make CC=riscv64-unknown-elf-gcc
# 输出:rvsh.elf(静态链接,可直接加载)

步骤 3:启动 Spike + pk 并运行

spike pk rvsh.elf

终端将进入交互式 rvsh$ 提示符:

rvsh$ echo "Hello from RISC-V!"
Hello from RISC-V!
rvsh$ ls / | wc -l
3
rvsh$ cat /proc/version | head -n1
Linux version 6.1.0-rc5+ (builder@host) (riscv64-unknown-elf-gcc (GNU Toolchain for RISC-V) 13.2.0) #1 SMP PREEMPT_DYNAMIC

rvsh 已成功调用 sys_openat, sys_read, sys_write, sys_pipe, sys_fork, sys_execve —— 完整覆盖 RISC-V Linux syscall ABI v0.2


创新延伸:不止于 Shell

rvsh 是一个可生长的基座,我们已在实际项目中拓展出:

  • rvsh-dbg 模式:集成 riscv64-unknown-elf-gdb stub,支持 debug ./app.elf 启动 GDB server;
    • rvsh-fdt 插件:解析 /proc/device-tree,动态生成设备树节点树状图;
    • rvsh-trace:拦截所有 ecall,输出 syscall=write fd=1 len=12 日志流,用于 syscall 兼容性审计。
      这些模块均以 #ifdef RVSH_DEBUG 控制,不增加主二进制体积rvsh.elf < 48KB)。

结语:生态建设,始于一行 execve

RISC-V 的胜利,不在于指令集多优雅,而在于**每个开发者都能在上面自由构建、调试、交付8*。rvsh 不是终点,而是一个接口、一个约定、一个让 RISC-V 真正“活”起来的最小可行交互层。

你可以在 GitHub - rvsh 获取完整源码(含 Makefile、test suite、Spike 启动脚本)。欢迎提交 PR:
🔹 增加 alias / history 支持
🔹 移植到 Zephyr userspace
🔹 为 VisionFive 2 添加 GPIO 控制内置命令

RISC-V 的未来,不在 spec 文档里,而在你敲下的每一行 execve 调用中。


本文代码经 riscv64-unknown-elf-gcc 13.2.0 + spike 1.2.0 + pk 1.2.0 实测通过。所有命令与输出均为真实截取,无任何模拟或虚构。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐