Skip to content

快速上手 ​

快速演示 ​

不需要开发板也能看到 RMP 运行。

x86 Linux ​

32 位 Linux 移植位于 Project/GCCMF-X86L。 用它自带的 Makefile 构建并运行生成的二进制即可;RMP 借助 ptrace 和信号 实现上下文切换,因此速度不快,但所有原语都能工作,完整性能输出会打印到终端。

红白机 / NES ​

下载 RMP.nes, 放进任何支持南梦宫 163扩展芯片(iNES mapper 019)的 NES 模拟器运行。 带南梦宫 163 芯片的烧录卡也可以在实机上运行。必须使用该扩展芯片,因为 RMP 依赖它内部可读取的 IRQ 计时器来测量周期。

RMP 在红白机上运行
RMP 性能测试通过南梦宫 163 烧录卡在红白机上运行

前置条件 ​

  • 一块受支持的开发板。STM32 Nucleo 与 MSP430 Launchpad 是最容易上手的选择。
  • 受支持的工具链:GCC/Clang-LLVM、Keil uVision 5、IAR EWARM、Code Composer Studio 或 MPLAB X。见支持平台。
  • 部分工程需要来自 M0A00_Library 的厂商 HAL 包。

不要使用 QEMU

QEMU 无法正确复现这些移植依赖的中断与时序行为,部分演示在其上根本无法 正确运行。请使用真实硬件或 Linux 移植。

仓库结构 ​

M5P01_Prokaron/
├─ Include/
│  ├─ rmp.h                  统一入口头文件
│  ├─ Kernel/rmp_kernel.h    完整的公共 API
│  ├─ Platform/              每个架构家族一个目录
│  │  ├─ A6M/ A6M_RVM/       Cortex-M0/M0+
│  │  ├─ A7M/ A7M_RVM/       Cortex-M3/M4/M7
│  │  ├─ A8MB/ A8MM/         Cortex-M23 / M55
│  │  ├─ A7R/                Cortex-R4/R5
│  │  ├─ RV32P/ RV32P_RVM/   RISC-V
│  │  └─ AVR/ MSP430/ RL78/ C28X/ DSPIC/ MP32P/ M6502/ UNSP/ X86L/
│  └─ Test/                  每颗芯片的基准与 WCET 头文件
├─ Source/
│  ├─ Kernel/rmp_kernel.c    内核、内存管理、控件与 FXAA
│  ├─ Hook/                  初始化钩子,含 RVM 版本
│  └─ Test/                  基准、覆盖率与 lwIP 测试
├─ Project/                  五十余个可直接打开的构建工程
└─ Document/                 用户手册与演示素材

编译 ​

不需要额外安装构建系统。Project/ 下的每个目录都是面向一颗芯片、一种工具链 的完整工程:

  • Keil —— 打开 RVMDK-* 下的 .uvprojx 文件。
  • IAR —— 打开 EWARM-* 下的 .eww 工作区。
  • GCC —— 在 GCCMF-* 目录中执行 make,Makefile 是自包含的。
  • CCS / MPLAB / 厂商 IDE —— 从对应目录导入工程。

如果工程提示缺少头文件,几乎总是因为缺少厂商 HAL,请从 M0A00_Library 获取。

运行测试 ​

下载镜像并启动调试会话。部分演示会用 LED 显示调度器活动,此时需要为你的 板卡补上 LED 包装函数。性能输出通过日志宏(RMP_LOG_*)打印到该移植提供的 控制台。

要使用图形库,请参阅用户手册——控件库由宏在编译期打开,并使用应用提供的 帧缓冲。

第一个线程 ​

C
#include "rmp.h"

static volatile struct RMP_Thd Thd_1;
static rmp_stack_t Stack_1[512];

void Func_1(void* Param)
{
    RMP_LOG_S("Parameter passed is ");
    RMP_LOG_H((ptr_t)Param);
    RMP_LOG_S("\r\n");
    while(1)
    {
        RMP_Thd_Delay(30000);
        RMP_LOG_S("Delayed 30000 cycles\r\n\r\n");
    };
}

void RMP_Init_Hook(void)
{
    RMP_Thd_Crt(&Thd_1, Func_1, &Stack_1, sizeof(Stack_1),
                (void*)0x12345678, 1, 5);
}

RMP_Init_Hook 在内核初始化完成、调度开始之前运行,所有线程、信号量和队列 都在这里创建。

线程间通信 ​

C
void Sender(void* Param)
{
    ptr_t Time = 0;
    while(1)
    {
        RMP_Thd_Delay(30000);
        RMP_Thd_Snd(&Thd_2, Time, RMP_SLICE_MAX);
        Time++;
    }
}

void Receiver(void* Param)
{
    ptr_t Data;
    while(1)
    {
        RMP_Thd_Rcv(&Data, RMP_SLICE_MAX);
        RMP_LOG_S("Received ");
        RMP_LOG_I(Data);
        RMP_LOG_S("\n");
    }
}

发送者与接收者都是普通线程,在调度开始前创建:

C
RMP_Thd_Crt(&Thd_1, Sender,   &Stack_1, sizeof(Stack_1), 0, 1, 5);
RMP_Thd_Crt(&Thd_2, Receiver, &Stack_2, sizeof(Stack_2), 0, 1, 5);

计数信号量的用法完全对称:

C
void RMP_Init_Hook(void)
{
    RMP_Sem_Crt(&Sem_1, 0);
    RMP_Thd_Crt(&Thd_1, Sender,   &Stack_1, sizeof(Stack_1), 0, 1, 5);
    RMP_Thd_Crt(&Thd_2, Receiver, &Stack_2, sizeof(Stack_2), 0, 1, 5);
}

void Sender(void* Param)
{
    while(1)
    {
        RMP_Thd_Delay(30000);
        RMP_Sem_Post(&Sem_1, 1);
    }
}

void Receiver(void* Param)
{
    while(1)
    {
        RMP_Sem_Pend(&Sem_1, RMP_SLICE_MAX);
        RMP_LOG_S("Semaphore successfully acquired!\r\n\r\n");
    }
}

内核还提供 FIFO、消息队列、阻塞消息队列、周期闹钟和内存池:

C
/* 内存池:初始化一次后即可分配/释放,不会产生碎片 */
RMP_Mem_Init(Pool, Pool_Size);
Mem = RMP_Malloc(Pool, Alloc_Size);
RMP_Free(Pool, Mem);

部署 ​

在把 RMP 放进产品之前,请仔细阅读 用户手册 并按平台配置内核宏——tick 来源、调度策略、栈检查以及可选子系统都是编译期 选项。默认配置是保守的,但不能替代阅读手册。

用编码智能体做移植

平台层刻意做得很小:一次上下文切换、一个 tick 来源和少量寄存器访问函数。 借助主流的编码智能体,把 RMP 移植到新的架构和工具链是一项边界清晰的工作—— 项目估计一个完整且经过测试的移植大约需要 10M tokens。

下一步 ​

本软件是 EDI 的官方作品,属于公有领域。

GitHub·Discord·QQ 群 1038895132·rtospro.com