under construction!#
上下文存储#
/// 纤程上下文存储了关于栈的信息。struct modm_context_t{ uintptr_t *sp; ///< 当前上下文的栈指针 (仅在上下文不运行时有效) uintptr_t *bottom; ///< 栈的底部地址 uintptr_t *top; ///< 栈的顶部地址(超出一字)};
上下文切换相关函数#
/** * @ingroup modm_processing_fiber * @defgroup modm_processing_fiber_context 纤程上下文函数 * * 这些函数实现了与架构相关的底层操作,用于初始化、启动、挂起和结束纤程。 * 它们可以用于实现替代的调度策略,或在 modm 之外使用纤程功能。 * @{ */
/** * 使用存储在顶部的函数指针和参数初始化上下文。 * `ctx->sp` 被设置为栈顶下两个 `uintptr_t`。 * 必须仅调用一次来初始化栈。 * * @param fn 函数指针,指向一个 `void(*)(uintptr_t)` 函数。 */voidmodm_context_init(modm_context_t *ctx, uintptr_t *bottom, uintptr_t *top, uintptr_t fn, uintptr_t arg);
/** * 重置上下文的栈指针到正确的偏移位置,为跳转到纤程做准备。 * 必须在初始化之后调用,并且在每次从头开始执行函数时调用。 */voidmodm_context_reset(modm_context_t *ctx);
/** * 将控制权从主上下文切换到纤程上下文。 * 该函数会初始化硬件,然后从调用方上下文跳转到 `to` 纤程。 */uintptr_tmodm_context_start(modm_context_t *to);
/** * 将上下文压栈到 `from->sp`,并从 `to->sp` 弹出上下文, * 从一个纤程跳转到另一个纤程。 */voidmodm_context_jump(modm_context_t *from, modm_context_t *to);
/** * 将控制权从纤程上下文切换回主上下文。 * 控制流将继续在主上下文中,从 `modm_context_start()` 函数返回。 */void modm_noreturnmodm_context_end(uintptr_t retval);
/** * 将寄存器文件清零,并为栈的其余部分设置水印。 * 你可以在调用 `modm_context_reset()` 之前或之后调用此函数, * 但**不能**在纤程运行时调用! */voidmodm_context_stack_watermark(modm_context_t *ctx);
/** * 通过从栈底开始搜索水印位置,返回栈的使用量。 * 你可以在调用 `modm_context_stack_watermark()` 之后的任何时候调用此函数。 */size_tmodm_context_stack_usage(const modm_context_t *ctx);/// @}
涉及的寄存器#
sp
(栈指针):sp
是当前活跃栈的栈指针,指向栈的顶部。它是 ARM 架构中用于栈操作的核心寄存器。- 在该代码中,
sp
会根据上下文切换的需要发生变化,指向不同的栈区域。
pc
(程序计数器):pc
是当前执行指令的地址。程序计数器保存着程序执行的下一条指令的地址。- 在上下文切换时,
pc
会被保存和恢复,以确保恢复执行时从正确的位置继续。
r0
到r3
(通用寄存器):- ARM 架构提供了一些通用寄存器(
r0
到r12
)。它们用于存储数据、地址等信息。在这段代码中,r0
和r1
等寄存器用来传递函数参数或存储栈指针。 r0
:用于传递参数,在modm_context_start
和modm_context_jump
中,用来传递modm_context_t *
(上下文结构体)。r1
:用来存储上下文切换的控制信息(例如,控制寄存器的值)。
- ARM 架构提供了一些通用寄存器(
lr
(链接寄存器):lr
用于保存函数调用的返回地址。当函数调用时,返回地址会被存入lr
,并且可以在函数返回时通过bx lr
恢复。- 在上下文保存时,
lr
会被保存到栈上。
d8
到d15
(双精度浮点寄存器):- ARM 架构中的
d8
到d15
是双精度浮点寄存器(VFP)。这些寄存器用于浮点运算。 vpush
和vpop
指令用于保存和恢复这些浮点寄存器的内容。由于这些寄存器可能存储了重要的上下文信息,必须在上下文切换时保存和恢复。
- ARM 架构中的
control
(控制寄存器):control
寄存器控制栈指针的选择和权限。- 该寄存器包含
SPSEL
位,它决定了使用主堆栈指针 (MSP) 还是进程堆栈指针 (PSP):SPSEL = 0
使用主堆栈指针 (MSP)。SPSEL = 1
使用进程堆栈指针 (PSP)。
- 在上下文切换时,
control
寄存器的值需要更新以切换栈指针。
涉及的汇编指令#
ldm
(Load Multiple):ldm sp, {r0, pc}
:从栈中加载多个寄存器的值。ldm
指令将栈顶的值加载到r0
和pc
寄存器中。- 具体来说,这个指令是用来实现上下文恢复的,它将栈上的值恢复到
r0
和pc
中,r0
存储函数的参数,pc
则恢复程序计数器,跳转到目标函数。 ldm sp, {r0, pc}
主要用在modm_context_entry
中,用于恢复上下文并跳转到协程的入口函数。
mrs
(Move Register to Status Register):mrs r1, control
:将control
寄存器的值加载到r1
寄存器中。mrs
指令用于从状态寄存器读取值,在这里用于读取control
寄存器的值,之后会修改它来切换栈指针。
orr
(OR Operation):orr r1, r1, #2
:将r1
寄存器的值与常数 2 进行按位或操作,并将结果存回r1
。- 这条指令用于设置
control
寄存器的SPSEL
位,即选择使用进程栈指针(PSP)。设置后,新的栈指针将指向用户级进程的栈,而不是默认的主栈。
msr
(Move to Status Register):msr control, r1
:将r1
寄存器的值写入control
寄存器。- 这条指令将
orr
操作后的结果存回control
寄存器,实际切换栈指针到 PSP。
ldr
(Load Register):ldr sp, [r0]
:从r0
寄存器指向的地址加载值到sp
。- 该指令用于设置栈指针为目标上下文的栈指针,恢复上下文时需要将栈指针
sp
设置为目标上下文的sp
。
str
(Store Register):str sp, [r0]
:将栈指针的值存储到r0
寄存器指向的地址。- 这条指令将当前上下文的栈指针
sp
存储到from->sp
,以便下次恢复。
cmp
(Compare):cmp sp, r3
:比较sp
和r3
的值。- 在
modm_context_jump
中用于检查当前栈指针是否在合法的范围内,防止栈溢出。如果栈指针 (sp
) 比bottom
小,则说明栈已经溢出。
b
(Branch):b modm_context_end
:无条件跳转到modm_context_end
,用于上下文切换过程中检查栈溢出的情况,确保程序继续执行正确的代码。
vpush
和vpop
:vpush {d8-d15}
:将浮点寄存器d8
到d15
的内容压入栈。vpop {d8-d15}
:从栈中弹出浮点寄存器d8
到d15
的内容。- 这些指令用于保存和恢复浮点寄存器的值,确保上下文切换时,浮点寄存器的值不会丢失。
上下文切换的实现#
#define modm_naked __attribute__((naked)) // 禁止编译器插入栈帧管理代码
#define MODM_PUSH_CONTEXT() \ "push {r4-r11, lr} \n\t" \ "vpush {d8-d15} \n\t"
#define MODM_POP_CONTEXT() \ "vpop {d8-d15} \n\t" \ "pop {r4-r11, pc} \n\t"
constexpr size_t StackWordsReset = 1;constexpr size_t StackWordsStorage = 2;constexpr size_t StackWordsRegisters = 25;constexpr size_t StackWordsAll = StackWordsStorage + StackWordsRegisters;constexpr size_t StackSizeWord = sizeof(uintptr_t);constexpr uintptr_t StackWatermark = 0xf00d'cafe;
void modm_nakedmodm_context_entry(){ asm volatile ( "ldm sp, {r0, pc} \n\t" // 从栈中加载 R0 和程序计数器(跳转到函数) );}
void modm_context_init(modm_context_t *ctx, uintptr_t *bottom, uintptr_t *top, uintptr_t fn, uintptr_t fn_arg){ ctx->bottom = bottom; ctx->top = top;
ctx->sp = top; *--ctx->sp = fn; // 保存函数地址 *--ctx->sp = fn_arg; // 保存函数参数}
- 功能:初始化上下文,将函数和参数压入栈中。
- 流程:
- 设置栈的上下边界。
- 将栈指针
sp
指向栈顶。 - 函数地址和参数压栈,为
modm_context_entry
的入口调用准备。
void modm_context_reset(modm_context_t *ctx){ *ctx->bottom = StackWatermark; // 标记栈底 ctx->sp = ctx->top - StackWordsStorage; *--ctx->sp = (uintptr_t) modm_context_entry; // 保存入口函数地址 ctx->sp -= StackWordsRegisters - StackWordsReset; // 为寄存器区域保留栈空间}
- 功能:重置上下文的栈,确保栈底设置了特定标记(
StackWatermark
),并预留寄存器存储空间。 - 用处:提供干净的栈和入口点,用于上下文启动。
void modm_context_stack_watermark(modm_context_t *ctx){ for (auto *word = ctx->top - StackWordsAll; word < ctx->top - StackWordsStorage - StackWordsReset; word++) *word = 0; // 清空寄存器区域栈空间
for (auto *word = ctx->bottom; word < ctx->top - StackWordsAll; word++) *word = StackWatermark; // 将栈空间填充为水印值}
- 功能:将栈空间分为寄存器区和普通区,并用
StackWatermark
初始化普通区,用于后续的栈使用检测。
size_t modm_context_stack_usage(const modm_context_t *ctx){ for (auto *word = ctx->bottom; word < ctx->top; word++) if (StackWatermark != *word) return (ctx->top - word) * StackSizeWord; return 0;}
- 功能:计算栈的使用情况。检测到第一个被改写的
StackWatermark
值后,返回栈使用量。
uintptr_t modm_nakedmodm_context_start(modm_context_t*){ asm volatile ( MODM_PUSH_CONTEXT() // 保存当前上下文 "mrs r1, control \n\t" // 获取 CONTROL 寄存器 "orr r1, r1, #2 \n\t" // 设置 CONTROL 的 SPSEL 位,切换到 PSP "msr control, r1 \n\t"
"ldr sp, [r0] \n\t" // 从上下文加载新栈指针 MODM_POP_CONTEXT() // 恢复新上下文 );}
- 功能:启动一个新的上下文,将栈指针设置为新上下文的栈。
- 流程:
- 保存当前上下文(
MODM_PUSH_CONTEXT
)。 - 切换到 Process Stack Pointer (PSP)。
- 恢复新上下文的寄存器状态。
- 保存当前上下文(
void modm_nakedmodm_context_jump(modm_context_t*, modm_context_t*){ asm volatile ( MODM_PUSH_CONTEXT() // 保存当前上下文 "ldr r3, [r0, #4] \n\t" // 加载当前上下文的栈底地址 "str sp, [r0] \n\t" // 保存当前栈指针 "cmp sp, r3 \n\t" // 检查栈是否溢出
"ldr sp, [r1] \n\t" // 加载目标上下文的栈指针 MODM_POP_CONTEXT() // 恢复目标上下文 );}
- 功能:实现上下文切换。
- 流程:
- 保存当前上下文状态(
MODM_PUSH_CONTEXT
)。 - 检查当前上下文的栈是否溢出(与
StackWatermark
对比)。 - 恢复目标上下文的栈指针和状态。
- 保存当前上下文状态(