快速上手
快速演示
不需要开发板也能看到 RMP 运行。
x86 Linux
32 位 Linux 移植位于 Project/GCCMF-X86L。 用它自带的 Makefile 构建并运行生成的二进制即可;RMP 借助 ptrace 和信号 实现上下文切换,因此速度不快,但所有原语都能工作,完整性能输出会打印到终端。
红白机 / NES
下载 RMP.nes, 放进任何支持南梦宫 163扩展芯片(iNES mapper 019)的 NES 模拟器运行。 带南梦宫 163 芯片的烧录卡也可以在实机上运行。必须使用该扩展芯片,因为 RMP 依赖它内部可读取的 IRQ 计时器来测量周期。

前置条件
- 一块受支持的开发板。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_*)打印到该移植提供的 控制台。
要使用图形库,请参阅用户手册——控件库由宏在编译期打开,并使用应用提供的 帧缓冲。
第一个线程
#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 在内核初始化完成、调度开始之前运行,所有线程、信号量和队列 都在这里创建。
线程间通信
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");
}
}发送者与接收者都是普通线程,在调度开始前创建:
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 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、消息队列、阻塞消息队列、周期闹钟和内存池:
/* 内存池:初始化一次后即可分配/释放,不会产生碎片 */
RMP_Mem_Init(Pool, Pool_Size);
Mem = RMP_Malloc(Pool, Alloc_Size);
RMP_Free(Pool, Mem);


部署
在把 RMP 放进产品之前,请仔细阅读 用户手册 并按平台配置内核宏——tick 来源、调度策略、栈检查以及可选子系统都是编译期 选项。默认配置是保守的,但不能替代阅读手册。
用编码智能体做移植
平台层刻意做得很小:一次上下文切换、一个 tick 来源和少量寄存器访问函数。 借助主流的编码智能体,把 RMP 移植到新的架构和工具链是一项边界清晰的工作—— 项目估计一个完整且经过测试的移植大约需要 10M tokens。
