官方网站建设 都来磐石网络刷百度关键词排名优化

张小明 2025/12/27 20:38:58
官方网站建设 都来磐石网络,刷百度关键词排名优化,开网店无货源,高端网站设计 上海首先我们这次是做一个协作式多任务的切换#xff0c;任务会自己放弃CPU从而提供给其他任务使用一、前置知识#xff1a;RISC-V 底层核心架构#xff08;上下文切换的基石#xff09;要理解上下文切换#xff0c;必须先吃透 RISC-V 的特权级、寄存器体系和指令集特性#…首先我们这次是做一个协作式多任务的切换任务会自己放弃CPU从而提供给其他任务使用一、前置知识RISC-V 底层核心架构上下文切换的基石要理解上下文切换必须先吃透 RISC-V 的特权级、寄存器体系和指令集特性这些是 OS 底层实现的 “地基”。1. RISC-V 的特权级Privilege LevelRISC-V 定义了 4 个特权级从高到低本实验的内核运行在M-mode机器模式这是最高特权级可访问所有资源特权级缩写用途关键 CSR控制状态寄存器机器模式M-mode底层固件 / 内核本实验mhartid、mscratch、mstatus 等监督模式S-mode普通操作系统内核sscratch、sstatus、stvec 等客户模式U-mode用户程序无特权 CSR超级 visor 模式H-mode虚拟化场景虚拟化相关 CSR因为是极简 OS跳过 S-mode 的复杂权限管控直接用 M-mode 实现内核降低入门难度但真实 OS 会用 S-mode 隔离内核和用户态。2. RISC-V 的寄存器体系上下文的载体RISC-V 的寄存器分为通用寄存器和CSR控制状态寄存器上下文切换的核心就是操作这两类寄存器。1通用寄存器32 个x0~x3132 位 / 64 位RISC-V 的通用寄存器有严格的功能约定不是硬件强制是软件调用约定这直接决定了上下文切换时哪些寄存器需要保存寄存器别名编号功能保存要求上下文切换zerox0硬连线为 0不可修改无需保存值固定rax1返回地址函数调用的返回点必须保存任务暂停的指令指针spx2栈指针指向栈顶RISC-V 栈向下生长必须保存任务独立栈的标识gpx3全局指针指向全局数据区本实验未用仅占位tpx4线程指针本实验存 hartid无需保存全局唯一不随任务变t0~t2x5~x7临时寄存器caller-saved本实验选择保存增强兼容性s0/fpx8保存寄存器 / 帧指针callee-saved必须保存函数调用后需保留原值s1x9保存寄存器callee-saved必须保存a0~a7x10~x17参数 / 返回值寄存器caller-saved本实验选择保存s2~s11x18~x27保存寄存器callee-saved必须保存t3~t6x28~x31临时寄存器caller-savedt6 特殊处理下文详解核心区别caller-saved vs callee-savedcaller-saved调用者保存如 t0~t2、a0~a7、t3~t6函数 A 调用函数 B 时A 需要自己保存这些寄存器的值因为 B 可能会覆盖callee-saved被调用者保存如 s0~s11函数 B 调用前必须保存这些寄存器的值返回后恢复保证 A 的这些寄存器值不变。上下文切换的保存逻辑为了 “无缝恢复任务”即使是 caller-saved 寄存器本实验也全部保存除 gp/tp/x0避免数据丢失。caller-saved 和 callee-saved 的保存逻辑“为什么要保存两次”其实是对 **“保存主体” 和 “保存范围”** 理解有偏差 ——根本不是对同一批寄存器保存两次而是两类寄存器的 “保存责任主体” 不同咱们分两层讲1. 先明确 “谁的寄存器”A 和 B 的寄存器是同一组硬件寄存器RISC-V 的通用寄存器是CPU 的硬件资源不是 “属于 A” 或 “属于 B” 的。比如 A 函数执行时s0 里存的是 A 的局部变量当调用 B 函数后B 如果要使用 s0就会覆盖 A 的 s0 值导致 A 返回后 s0 的值丢失。2. caller-saved 和 callee-saved 的 “单次保存” 责任划分这是 RISC-V软件调用约定不是硬件强制目的是保证函数调用后 “关键数据不丢失”但两类寄存器的保存责任完全不同不存在 “保存两次 A 的上下文”1caller-saved 寄存器t0~t2、a0~a7、t3~t6调用者 A 保存这类寄存器的特点是被调用者 B 可以随意覆盖B 默认 A 已经保存了这些寄存器的值。场景A 要调用 B且 A 后续还需要用 a0 的值那么 A 必须在调用 B前把 a0 的值存到自己的栈里B 执行时可以随便改 a0A 返回后再从栈里恢复 a0 即可。这里的 “保存” 是A 为自己保存和 B 无关。2callee-saved 寄存器s0~s11被调用者 B 保存这类寄存器的特点是B 必须保证调用前后值不变B 如果要使用 s0必须先保存返回前恢复。场景A 的 s0 里存了重要的局部变量调用 B 后B 如果要用到 s0必须先把 s0 当前值A 的 s0 值存到 B 自己的栈里B 用完 s0 后再从栈里恢复 s0 的值这样 A 返回后 s0 的值还是原来的。这里的 “保存” 是B 为 A 的 s 寄存器保存不是 A 自己保存且只保存一次。3. 上下文切换的 “一次性保存”和函数调用的区别印象里的 “只保存一次 A 的上下文” 是任务级的上下文切换和 “函数调用的寄存器保存” 是两个场景函数调用的保存是临时、局部的比如 B 只保存自己要用的 s 寄存器不用的不保存且执行完 B 就恢复上下文切换的保存是完整、持久的把任务 A 的所有通用寄存器都存到 context 里因为任务 A 会被暂停下次恢复时需要完整的执行状态和函数调用的 “局部保存” 本质不同。总结不存在 “保存两次 A 的上下文”caller-saved 是 A 自己保自己的临时寄存器callee-saved 是 B 帮 A 保保存寄存器责任主体和范围都不同是 “分工保存” 而非 “重复保存”。2CSR控制状态寄存器CSR 是 RISC-V 的 “特权寄存器”用于控制 CPU 状态本实验核心用到 3 个mhartidM-mode 的 CPU 核 ID 寄存器多核系统中区分不同 hartstart.S 中用csrr t0, mhartid读取mscratchM-mode 的 “临时保存寄存器”本实验用它存当前任务的 context 指针关键因为汇编指令不能直接访问内存必须用通用寄存器当基址而 mscratch 不参与任务执行不会被覆盖mstatusM-mode 状态寄存器本实验未直接用但真实 OS 会用它控制中断使能、特权级切换。3RISC-V 的指令对齐要求RISC-V 的指令地址必须 4 字节对齐32 位指令栈指针 sp 必须 16 字节对齐调用约定强制要求代码中__attribute__((aligned(16))) task_stack就是为了满足 sp 的对齐要求。3. RISC-V 的 CSR 操作指令上下文切换的关键指令操作 CSR 的指令是上下文切换的 “核心工具”本实验用到csrr/csrw/csrrw指令功能示例本实验csrr rd, csr读 CSR 到通用寄存器 rdcsrr t6, mscratch读 mscratch 到 t6csrw csr, rs写通用寄存器 rs 到 CSRcsrw mscratch, a0写 a0 到 mscratchcsrrw rd, csr, rs原子交换先读 csr 到 rd再写 rs 到 csrcsrrw t6, mscratch, t6交换 t6 和 mscratch 的值原子性的意义csrrw是原子操作不会被中断打断保证上下文切换时 CSR 操作的完整性。二、系统启动的底层细节start.S kernel.cRISC-V M-mode 启动流程系统启动的每一步都和 RISC-V 的底层架构强绑定我们逐行拆解1. start.SM-mode 的硬件入口0x80000000QEMU 将内核加载到0x80000000platform.h 定义的内存布局并从_start开始执行这是 RISC-V 的硬件启动约定。总的代码#include platform.h // 平台相关头文件包含MAXNUM_CPU等硬件配置宏定义 # 每个硬件线程hart的栈大小为1024字节 # hart是RISC-V中硬件线程的核心单元多核心/多线程系统中每个hart独立执行指令 .equ STACK_SIZE, 1024 // 定义栈大小常量等价于C语言#define STACK_SIZE 1024 .global _start // 声明_start为全局符号作为程序入口点链接器识别的入口 .text // 代码段开始RISC-V汇编中.text段存放可执行指令只读且可执行 _start: # 暂停所有hart ID不等于0的硬件线程仅保留hart 0执行核心逻辑 csrr t0, mhartid # 读取当前hart的ID到t0寄存器 # csrr是RISC-V特权指令用于读取控制状态寄存器(CSR) # mhartid是机器模式下的hart ID寄存器每个hart有唯一ID从0开始 mv tp, t0 # 将hart ID保存到tp寄存器线程指针供后续使用 # tp寄存器在RISC-V ABI中约定为线程私有数据指针常用于存储hart ID/线程上下文 bnez t0, park # 如果当前hart ID非0t0≠0跳转到park标签处挂起 # bnez是分支指令Branch if Not Equal to Zero # 多核心启动时通常只让hart 0执行初始化其余hart先挂起 # 将BSS段的所有字节清零 # BSS段用于存放未初始化的全局变量和静态变量标准要求程序启动时必须清零 la a0, _bss_start # 加载BSS段起始地址到a0寄存器 # la是伪指令Load Address实际会翻译成auipcaddi组合指令 la a1, _bss_end # 加载BSS段结束地址到a1寄存器 bgeu a0, a1, 2f # 如果起始地址≥结束地址无BSS段跳转到2号标签跳过清零 # bgeu是无符号大于等于分支2f表示向前(forward)查找2号标签 1: # 1号标签BSS清零循环体入口 sw zero, (a0) # 将0写入a0指向的内存地址4字节 # sw是存储字指令Store Wordzero寄存器恒为0 addi a0, a0, 4 # a0 4指向下一个4字节地址 bltu a0, a1, 1b # 如果a0 a1未清零完跳回1号标签继续循环 # bltu是无符号小于分支1b表示向后(backward)查找1号标签 2: # 2号标签BSS清零完成后的执行点 # 设置栈指针栈从高地址向低地址生长因此将栈指针指向栈空间的末尾 # RISC-V栈遵循满递减栈Full Descendingsp指向最后一个已使用的栈地址压栈时先sp-size再存储 slli t0, t0, 10 # 将hart ID左移10位等价于乘以1024 # slli是逻辑左移指令Shift Left Logical Immediate10位对应2^101024 la sp, stacks STACK_SIZE # 加载栈空间起始地址STACK_SIZE到sphart 0的栈顶 # stacks是栈空间的基地址hart 0的栈范围stacks ~ stacksSTACK_SIZE # sp初始指向栈空间末尾栈顶符合RISC-V栈生长方向 add sp, sp, t0 # 调整sp到当前hart对应的栈顶位置 # 多hart栈空间布局hart N的栈 stacks N*STACK_SIZE ~ stacks (N1)*STACK_SIZE # 例如hart 1的栈顶 stacksSTACK_SIZE 1*STACK_SIZE j start_kernel # hart 0跳转到C语言实现的start_kernel函数执行 # j是无条件跳转指令Jump跳转到全局符号start_kernel park: # 非0号hart的挂起循环 wfi # 等待中断指令Wait For Interrupt # wfi会让hart进入低功耗状态直到有中断/异常触发才唤醒 # 挂起的hart会在此处无限循环直到被中断唤醒 j park # 跳回park标签持续挂起 # RISC-V标准调用约定ABI要求栈指针sp必须保持16字节对齐 # 对齐要求函数调用时sp需是16的倍数保证浮点寄存器/向量指令的内存访问对齐 .balign 16 # 对齐指令后续数据按16字节边界对齐 stacks: # 栈空间的基地址标签 .skip STACK_SIZE * MAXNUM_CPU # 为所有hart分配栈空间跳过指定字节数即预留内存 # .skip是汇编伪指令分配指定大小的未初始化内存 # MAXNUM_CPU是平台最大支持的hart数量来自platform.h .end # 汇编文件结束标记这里有一个小细节就是栈不是向下生长的吗但是代码中提到了一个问题就是la sp, stacks STACK_SIZE这句话其实是直接给所有的hart分配了栈空间然后在把sp寄存器往上移动回到栈的起始地址注意这里是分配空间而不是使用空间因为我们分配了空间所以要回到我们分配的空间的起始位置所以就是往高地址la sp, stacks STACK_SIZE // stacks是所有hart栈的起始地址stacksSTACK_SIZE是0号hart的栈顶初始地址 add sp, sp, t0 // t0是hartid×1024即hartid×STACK_SIZE为每个hart分配独立栈0 号 hartt00 → spstacks10240 → 对应 0 号 hart 的栈顶1 号 hartt01024 → spstacks10241024 → 对应 1 号 hart 的栈顶代码里stacks是所有 hart 栈的起始低地址stacks STACK_SIZE是 0 号 hart 栈的最高地址初始 sp 指向这里完全符合栈向下生长的规则假设stacks的地址是0x80001000STACK_SIZE1024则stacksSTACK_SIZE0x80001400高地址0 号 hart 初始化时 sp0x80001400栈顶初始位置当执行压栈操作比如保存寄存器时先把数据存入sp-40x800013FC再把 sp 更新为 0x800013FCsp 递减栈向下生长。1多核 hart 的初始化与休眠csrr t0, mhartid // 读M-mode的hartid到t0RISC-V架构指令仅M-mode可访问mhartid mv tp, t0 // tp寄存器永久存hartidtp是线程指针RISC-V约定tp不随任务切换 bnez t0, park // 非0号hart跳转到park标签进入休眠为什么只让 0 号 hart 工作本实验是单核 OS多核需处理缓存一致性、锁等复杂问题先简化为单核park 标签的休眠逻辑park: wfi // RISC-V的“等待中断”指令CPU进入低功耗直到中断唤醒本实验0号hart不会唤醒其他hart j park // 无限循环保持休眠2BSS 段清零RISC-V 数据段约定RISC-V 的 ELF 文件中BSS 段是未初始化的全局变量如task_stack硬件不会自动清零必须手动初始化la a0, _bss_start // 加载BSS段起始地址到a0la是RISC-V的“加载地址”伪指令实际是auipcaddi la a1, _bss_end // 加载BSS段结束地址到a1 bgeu a0, a1, 2f // 如果a0 a1BSS为空跳转到2: 1: sw zero, (a0) // 把a0指向的内存写0sw是RISC-V的“存储字”指令32位 addi a0, a0, 4 // 地址432位系统按字对齐 bltu a0, a1, 1b // 未到结束地址则循环 2:RISC-V 的地址指令la是伪指令会被汇编器转为auipc高位地址addi低位偏移因为 RISC-V 的立即数指令范围有限无法直接加载 32 位地址。30 号 hart 的初始栈设置RISC-V 栈对齐要求slli t0, t0, 10 // t0hartid左移10位等价于*1024STACK_SIZE1024 la sp, stacks STACK_SIZE // 加载栈区起始地址栈大小到spsp指向栈顶RISC-V栈向下生长 add sp, sp, t0 // 0号hart的sp stacks1024 0满足16字节对齐 j start_kernel // 跳转到C语言的start_kernelRISC-V的无条件跳转指令栈的 16 字节对齐RISC-V 调用约定强制要求 sp 必须 16 字节对齐stacks标签用.balign 16保证对齐否则函数调用会出错stacks 的内存布局.balign 16 stacks: .skip STACK_SIZE * MAXNUM_CPU // 预分配8个hart的栈空间每个1024字节2. kernel.cC 语言的内核入口M-mode 到 C 的过渡start_kernel是 C 代码的起点每一步初始化都服务于后续的任务切换总代码void start_kernel(void) { uart_init(); // 初始化UARTRISC-V的内存映射IO地址0x10000000见platform.h uart_puts(Hello, RVOS!\n); page_init(); // 初始化页式内存本实验未用任务的动态内存先忽略 sched_init(); // 关键初始化mscratch0M-mode CSR为第一次上下文切换做准备 os_main(); // 创建用户任务task0和task1 schedule(); // 触发第一次上下文切换从内核切换到task0 uart_puts(Would not go here!\n);// 一旦切换到任务PC不会再回到这里ra已被覆盖 while (1) {}; }UART 的底层原理UART 是内存映射 IORISC-V 通过读写0x10000000地址的寄存器如 THR/RHR实现串口通信无需专门的 IO 指令RISC-V 是 “内存 - IO 统一编址”区别于 x86 的独立 IO 空间。三、任务管理的底层实现sched.cRISC-V 任务的 “诞生” 与调度任务的创建和调度本质是为任务分配独立的 “寄存器上下文” 和 “栈空间”我们逐行拆解1. 任务栈与上下文的初始化task_create 函数每个任务需要独立的栈和初始寄存器上下文这是任务独立执行的基础#include os.h // 操作系统核心头文件包含reg_t寄存器类型、context结构体等核心定义 /* * 声明外部函数switch_to由汇编实现的上下文切换核心函数 * 拓展switch_to是任务调度的关键会修改CPU的寄存器如sp/ra实现从当前任务切到下一个任务 * 入参next - 下一个待执行任务的上下文结构体指针 */ extern void switch_to(struct context *next); // 系统最大支持的任务数FIFO调度队列的最大长度 #define MAX_TASKS 10 // 每个任务的栈大小1024字节符合RISC-V栈16字节对齐要求 #define STACK_SIZE 1024 /* * RISC-V标准调用约定ABI强制要求栈指针sp必须始终保持16字节对齐 * __attribute__((aligned(16)))GCC属性强制数组按16字节边界对齐 * 拓展栈对齐保证浮点指令/向量指令的内存访问正确性避免总线错误 */ uint8_t __attribute__((aligned(16))) task_stack[MAX_TASKS][STACK_SIZE]; /* * 任务上下文数组每个元素保存一个任务的核心执行上下文 * 拓展struct context是架构相关的结构体通常在os.h中定义至少包含 * - sp任务的栈指针栈顶地址 * - ra任务的返回地址任务入口/切出时的执行点 * 还可能包含通用寄存器a0-a7/t0-t6、状态寄存器mstatus等 */ struct context ctx_tasks[MAX_TASKS]; /* * 静态全局变量调度器核心状态 * _top标记ctx_tasks中已创建任务的最大下标也表示当前总任务数 * _current指向当前正在执行的任务在ctx_tasks中的下标初始-1表示无任务 */ static int _top 0; static int _current -1; /* * 写RISC-V机器模式的mscratch寄存器 * 拓展mscratchMachine Scratch Register是RISC-V机器模式专用寄存器 * 1. 用于保存临时数据机器模式下中断/异常处理时避免覆盖通用寄存器 * 2. 通常存放当前任务的上下文指针或中断栈地址 * 入参x - 要写入mscratch的数值 */ static void w_mscratch(reg_t x) { // 内联汇编实现CSR写操作csrwControl and Status Register Write // 格式asm volatile(指令模板 : 输出约束 : 输入约束 : 破坏列表) // r(x)表示将变量x放入通用寄存器作为输入传递给汇编指令 asm volatile(csrw mscratch, %0 : : r (x)); } /* * 调度器初始化函数 * 功能初始化调度器核心状态清空mscratch寄存器初始无任务 * 调用时机系统启动时hart 0初始化完成后 */ void sched_init() { w_mscratch(0); // 将mscratch置0表示当前无任务上下文关联 } /* * 实现简单的循环FIFO先进先出调度器 * 核心逻辑按任务创建顺序循环执行无优先级属于非抢占式调度 * 拓展FIFO调度是最基础的调度算法优点是简单、无饥饿问题缺点是响应性差 */ void schedule() { // 边界检查如果无已创建的任务触发panic系统致命错误 if (_top 0) { panic(Num of task should be greater than zero!); // panic是OS核心函数终止系统并打印错误 return; } // 计算下一个要执行的任务下标循环取模实现FIFO循环 // 例如_current2, _top3 → 下一个3%30_current0, _top3 → 下一个1 _current (_current 1) % _top; // 获取下一个任务的上下文指针 struct context *next (ctx_tasks[_current]); // 跳转到汇编实现的上下文切换函数切换到next任务执行 // 拓展switch_to执行时会保存当前任务上下文恢复next任务的寄存器sp/ra等 switch_to(next); } /* * 【任务创建接口】 * 功能创建一个新任务初始化其上下文栈指针、入口地址 * 入参start_routin - 任务的入口函数无参无返回值 * 返回值0-成功-1-失败任务数达到上限 * 拓展任务创建的核心是初始化上下文让调度器能找到任务的执行起点和栈空间 */ int task_create(void (*start_routin)(void)) { // 检查是否还有空闲任务槽位未达到最大任务数 if (_top MAX_TASKS) { /* * 初始化新任务的栈指针指向任务栈的末尾栈顶 * RISC-V栈是满递减栈Full Descending * - 栈空间范围task_stack[_top][0] ~ task_stack[_top][STACK_SIZE-1] * - sp初始指向栈末尾最高地址压栈时先sp-size再存储数据 */ ctx_tasks[_top].sp (reg_t) task_stack[_top][STACK_SIZE]; /* * 初始化新任务的返回地址指向任务入口函数 * 拓展ra寄存器在RISC-V中保存函数返回地址此处设置ra为任务入口 * 当switch_to恢复ra后ret指令会跳转到start_routin执行 */ ctx_tasks[_top].ra (reg_t) start_routin; // 任务数1_top既是下标也是任务总数 _top; return 0; // 创建成功 } else { return -1; // 任务数达到上限创建失败 } } /* * 【任务主动让出CPU接口】 * 功能当前任务主动放弃CPU使用权触发调度器切换到下一个任务 * 拓展属于非抢占式调度的核心接口任务需主动调用才能切换 * 抢占式调度则由定时器中断触发schedule() */ void task_yield() { // 调用调度器切换到下一个任务 schedule(); } /* * 【简单的任务延时函数】 * 功能通过忙等消耗CPU周期实现粗略的延时无定时器中断时的临时方案 * 入参count - 延时计数越大延时越长 * 拓展 * 1. volatile关键字防止编译器优化掉空循环编译器会认为count未被使用直接删除循环 * 2. 忙等缺点CPU利用率100%无低功耗实际OS中应使用定时器中断休眠 * 3. count * 50000放大计数让延时效果更明显适配不同主频的CPU */ void task_delay(volatile int count) { count * 50000; while (count--); // 空循环消耗CPU }// 预分配10个任务的栈每个1024字节16字节对齐满足RISC-V调用约定 uint8_t __attribute__((aligned(16))) task_stack[MAX_TASKS][STACK_SIZE]; // 每个任务的上下文寄存器快照 struct context ctx_tasks[MAX_TASKS]; static int _top 0; // 已创建任务数 static int _current -1;// 当前运行的任务ID int task_create(void (*start_routin)(void)) { if (_top MAX_TASKS) { // 关键1设置任务的sp栈指针指向栈的最末端栈向下生长初始栈顶在高地址 ctx_tasks[_top].sp (reg_t) task_stack[_top][STACK_SIZE]; // 关键2设置任务的ra返回地址指向任务的入口函数如user_task0 ctx_tasks[_top].ra (reg_t) start_routin; _top; return 0; } else { return -1; } }RISC-V 栈的生长方向栈从高地址向低地址生长因此初始 sp 要指向task_stack[_top][STACK_SIZE]栈的最高地址任务执行时 sp 会逐渐减小ra 的底层意义ra 寄存器保存的是函数的返回地址这里把 ra 设为任务入口函数的地址当第一次切换到该任务时ret指令会跳转到 ra 指向的地址即任务开始执行。2. 调度器的底层逻辑schedule 函数调度器的核心是选择下一个任务的上下文并调用switch_to完成寄存器的切换void schedule() { if (_top 0) panic(任务数不能为0); // 循环调度FIFO_current从-1→0→1→0→1...取模实现循环 _current (_current 1) % _top; struct context *next ctx_tasks[_current]; switch_to(next); // 核心切换到next任务的上下文汇编实现 }为什么用 FIFO 调度协作式多任务的调度策略很简单因为任务会主动调用task_yield让出 CPU无需复杂的优先级调度_current 的初始值 - 1第一次调度时_current10切换到 task0符合任务创建顺序。3. 协作式多任务的核心task_yield 函数协作式多任务的本质是任务主动放弃 CPUtask_yield是任务 “主动让权” 的接口void task_yield() { schedule(); // 调用调度器触发上下文切换 }协作式的底层局限性如果任务不调用task_yield比如死循环会永久霸占 CPU其他任务无法执行真实 OS 会用抢占式多任务通过定时器中断强制切换这需要 RISC-V 的mtvec中断向量表和mstatus中断使能支持后续可扩展学习。四、上下文切换的底层核心entry.SRISC-V 汇编的逐行精解switch_to是整个实验的最核心、最底层的函数用 RISC-V 汇编实现寄存器的 “保存” 与 “恢复”我们逐行拆解结合 RISC-V 架构原理分析1. 先理解两个核心宏reg_save/reg_restore这两个宏是批量操作寄存器的 “工具”本质是把通用寄存器按固定偏移存入 / 读出 context 结构体先看struct context的内存布局和宏的偏移严格对应struct context 成员偏移字节reg_save/restore 的操作对应 RISC-V 寄存器ra0*40STORE ra, 0*SIZE_REG(base)x1sp1*44STORE sp, 1*SIZE_REG(base)x2gp2*48未操作本实验不用x3tp3*412未操作全局唯一x4t04*416STORE t0,4*SIZE_REG(base)x5...中间寄存器省略.........t630*4120单独保存reg_save 未处理x31总的代码# 宏定义统一内存访问指令和寄存器宽度 # LOAD定义为lwLoad Word4字节加载指令适配32位RISC-V # STORE定义为swStore Word4字节存储指令 # SIZE_REG通用寄存器宽度4字节对应RV32架构若为RV64则改为8指令换为ld/sd #define LOAD lw #define STORE sw #define SIZE_REG 4 # 宏将除gp、tp外的所有通用寄存器GP保存到上下文结构体 # 功能等价于C代码 # struct context *base ctx_task; // base是上下文结构体指针传入的参数 # base-ra ra; // 依次保存各寄存器到上下文的对应偏移位置 # ...... # 拓展说明 # 1. 不保存gp全局指针和tp线程指针的原因 # - gp用于访问全局数据区系统中所有任务共享无需任务间切换 # - tpRVOS中固定存储hartid硬件线程ID全局唯一且不会变化 # 2. RISC-V寄存器分类ABI约定 # - 调用者保存寄存器t0-t6/a0-a7函数调用时由调用者保存上下文切换必须保存 # - 被调用者保存寄存器s0-s11函数调用时由被调用者保存上下文切换也需保存 # - 特殊寄存器ra/sp/gp/tpra/sp是任务执行核心必须保存gp/tp无需保存 .macro reg_save base # 按上下文结构体的偏移顺序保存寄存器偏移序号*4字节 STORE ra, 0*SIZE_REG(\base) # rax1返回地址偏移0 STORE sp, 1*SIZE_REG(\base) # spx2栈指针偏移4 # 跳过x3(gp)、x4(tp)无偏移2、3 STORE t0, 4*SIZE_REG(\base) # t0x5临时寄存器偏移16 STORE t1, 5*SIZE_REG(\base) # t1x6临时寄存器偏移20 STORE t2, 6*SIZE_REG(\base) # t2x7临时寄存器偏移24 STORE s0, 7*SIZE_REG(\base) # s0x8保存寄存器偏移28 STORE s1, 8*SIZE_REG(\base) # s1x9保存寄存器偏移32 STORE a0, 9*SIZE_REG(\base) # a0x10参数/返回值寄存器偏移36 STORE a1, 10*SIZE_REG(\base) # a1x11参数寄存器偏移40 STORE a2, 11*SIZE_REG(\base) # a2x12参数寄存器偏移44 STORE a3, 12*SIZE_REG(\base) # a3x13参数寄存器偏移48 STORE a4, 13*SIZE_REG(\base) # a4x14参数寄存器偏移52 STORE a5, 14*SIZE_REG(\base) # a5x15参数寄存器偏移56 STORE a6, 15*SIZE_REG(\base) # a6x16参数寄存器偏移60 STORE a7, 16*SIZE_REG(\base) # a7x17参数寄存器偏移64 STORE s2, 17*SIZE_REG(\base) # s2x18保存寄存器偏移68 STORE s3, 18*SIZE_REG(\base) # s3x19保存寄存器偏移72 STORE s4, 19*SIZE_REG(\base) # s4x20保存寄存器偏移76 STORE s5, 20*SIZE_REG(\base) # s5x21保存寄存器偏移80 STORE s6, 21*SIZE_REG(\base) # s6x22保存寄存器偏移84 STORE s7, 22*SIZE_REG(\base) # s7x23保存寄存器偏移88 STORE s8, 23*SIZE_REG(\base) # s8x24保存寄存器偏移92 STORE s9, 24*SIZE_REG(\base) # s9x25保存寄存器偏移96 STORE s10, 25*SIZE_REG(\base) # s10x26保存寄存器偏移100 STORE s11, 26*SIZE_REG(\base) # s11x27保存寄存器偏移104 STORE t3, 27*SIZE_REG(\base) # t3x28临时寄存器偏移108 STORE t4, 28*SIZE_REG(\base) # t4x29临时寄存器偏移112 STORE t5, 29*SIZE_REG(\base) # t5x30临时寄存器偏移116 # 注意此处未保存t6x31因为t6被用作base上下文指针 # t6的保存需要在reg_save宏外部单独处理避免覆盖base导致地址错误 .endm # 宏从上下文结构体恢复除gp、tp外的所有通用寄存器 # 功能与reg_save相反将上下文数据加载回对应寄存器 # struct context *base ctx_task; # ra base-ra; # ...... .macro reg_restore base # 按偏移顺序恢复寄存器与reg_save一一对应 LOAD ra, 0*SIZE_REG(\base) # 恢复返回地址ra LOAD sp, 1*SIZE_REG(\base) # 恢复栈指针sp核心切换任务栈 LOAD t0, 4*SIZE_REG(\base) # 恢复临时寄存器t0 LOAD t1, 5*SIZE_REG(\base) # 恢复t1 LOAD t2, 6*SIZE_REG(\base) # 恢复t2 LOAD s0, 7*SIZE_REG(\base) # 恢复保存寄存器s0 LOAD s1, 8*SIZE_REG(\base) # 恢复s1 LOAD a0, 9*SIZE_REG(\base) # 恢复参数寄存器a0 LOAD a1, 10*SIZE_REG(\base) # 恢复a1 LOAD a2, 11*SIZE_REG(\base) # 恢复a2 LOAD a3, 12*SIZE_REG(\base) # 恢复a3 LOAD a4, 13*SIZE_REG(\base) # 恢复a4 LOAD a5, 14*SIZE_REG(\base) # 恢复a5 LOAD a6, 15*SIZE_REG(\base) # 恢复a6 LOAD a7, 16*SIZE_REG(\base) # 恢复a7 LOAD s2, 17*SIZE_REG(\base) # 恢复s2 LOAD s3, 18*SIZE_REG(\base) # 恢复s3 LOAD s4, 19*SIZE_REG(\base) # 恢复s4 LOAD s5, 20*SIZE_REG(\base) # 恢复s5 LOAD s6, 21*SIZE_REG(\base) # 恢复s6 LOAD s7, 22*SIZE_REG(\base) # 恢复s7 LOAD s8, 23*SIZE_REG(\base) # 恢复s8 LOAD s9, 24*SIZE_REG(\base) # 恢复s9 LOAD s10, 25*SIZE_REG(\base) # 恢复s10 LOAD s11, 26*SIZE_REG(\base) # 恢复s11 LOAD t3, 27*SIZE_REG(\base) # 恢复t3 LOAD t4, 28*SIZE_REG(\base) # 恢复t4 LOAD t5, 29*SIZE_REG(\base) # 恢复t5 LOAD t6, 30*SIZE_REG(\base) # 恢复t6x31偏移12030*4 .endm # 保存/恢复寄存器的关键注意事项 # 1. 使用mscratch寄存器存储当前任务的上下文指针 # mscratch是RISC-V机器模式专用CSR用于临时存储数据不会被普通指令覆盖 # 2. 使用t6作为reg_save/reg_restore的base上下文指针 # - t6是通用寄存器中编号最大的x31加载/存储过程中不会被其他指令覆盖 # - 注意CSR寄存器如mscratch不能作为load/store指令的基址寄存器 # 因此必须先将mscratch的值换到通用寄存器t6中才能使用 .text # 代码段开始存放可执行指令 # 全局函数void switch_to(struct context *next); # 功能上下文切换核心函数将CPU从当前任务切换到next任务 # 入参约定RISC-V ABIa0寄存器存放next指针下一个任务的上下文地址 .globl switch_to # 声明为全局符号供C代码调用 .balign 4 # 指令按4字节对齐RISC-V指令固定4字节 switch_to: # 核心步骤1交换t6和mscratch的值csrrwCSR Read and Write # 指令功能先将mscratch的当前值读入t6再将t6的原值写入mscratch # 作用t6现在指向“当前任务”的上下文指针mscratch中存储的旧值 csrrw t6, mscratch, t6 # 核心步骤2处理第一次调用switch_to的特殊情况 # beqz t6, 1f如果t6为0第一次调用跳转到1号标签 # 背景sched_init()中初始化mscratch为0第一次调用时csrrw会让t60 # 此时无“前一个任务”需要保存直接跳转到恢复新任务的逻辑 beqz t6, 1f # 核心步骤3保存前一个任务的上下文非第一次调用时执行 # reg_save t6将当前CPU的所有通用寄存器保存到t6指向的上下文前一个任务 reg_save t6 # 核心步骤4单独保存t6寄存器因为reg_save中未保存 mv t5, t6 # t5暂存当前任务的上下文指针避免被覆盖 csrr t6, mscratch # 从mscratch中读回t6的原始值交换前的t6 STORE t6, 30*SIZE_REG(t5) # 将t6的值保存到当前任务上下文的t6偏移位置 1: # 1号标签恢复下一个任务的上下文 # 核心步骤5更新mscratch为下一个任务的上下文指针 # csrw将a0next指针写入mscratch后续切换时可通过mscratch获取新任务上下文 csrw mscratch, a0 # 核心步骤6恢复下一个任务的所有寄存器 mv t6, a0 # t6指向新任务的上下文指针a0next reg_restore t6 # 从t6指向的上下文恢复所有通用寄存器 # 关键恢复sp栈指针后CPU的栈已切换到新任务恢复ra后ret会跳转到新任务的执行点 # 核心步骤7完成上下文切换ret指令 # ret等价于jr ra跳转到ra寄存器指向的地址新任务的入口/切出时的执行点 # 此时ra已被reg_restore恢复为新任务的返回地址执行ret即切换到新任务运行 ret .end # 汇编文件结束当前任务A → switch_to(nextB) → 任务B执行 ├─ 保存A的寄存器到A的上下文mscratch→t6→reg_save ├─ 更新mscratch为B的上下文指针 ├─ 从B的上下文恢复所有寄存器包括sp/ra └─ ret跳转到B的ra地址B开始执行1reg_save 宏保存寄存器到 context.macro reg_save base // base是context结构体的指针通用寄存器 STORE ra, 0*SIZE_REG(\base) // ra存入base0 STORE sp, 1*SIZE_REG(\base) // sp存入base4 STORE t0, 4*SIZE_REG(\base) // t0存入base16跳过gp/tp的偏移2/3 STORE t1, 5*SIZE_REG(\base) // t1存入base20 ...中间寄存器省略 STORE t5, 29*SIZE_REG(\base) // t5存入base116 # 不保存t6因为t6被用作base保存t6会覆盖base需单独处理 .endm为什么跳过 gp/tpgp 本实验未用tp 存 hartid全局不变无需保存因此宏中跳过偏移 2 和 3 的位置t6 的特殊处理t6 被用作 basecontext 指针如果在宏中保存 t6会把 base 的值覆盖因此需要在宏外单独保存。2reg_restore 宏从 context 恢复寄存器.macro reg_restore base LOAD ra, 0*SIZE_REG(\base) // 从base0恢复ra LOAD sp, 1*SIZE_REG(\base) // 从base4恢复sp LOAD t0, 4*SIZE_REG(\base) // 从base16恢复t0 ...中间寄存器省略 LOAD t6, 30*SIZE_REG(\base) // 从base120恢复t6单独处理的t6 .endm恢复顺序的意义恢复顺序和保存顺序一致保证寄存器的值和暂停时完全相同t6 在最后恢复避免覆盖 base。2. switch_to 函数的逐行拆解结合 RISC-V 架构函数原型void switch_to(struct context *next)RISC-V 调用约定中函数参数通过a0寄存器传递因此a0是下一个任务的 context 指针。.globl switch_to // 声明为全局函数供C语言调用 .balign 4 // 指令4字节对齐RISC-V强制要求 switch_to: # 步骤1原子交换t6和mscratch的值核心获取当前任务的context指针 csrrw t6, mscratch, t6 // 1. 读mscratch到t62. 写原t6到mscratch原子操作 beqz t6, 1f // 如果t60第一次切换mscratch初始为0跳转到1:标签第一次切换的特殊情况sched_init()把 mscratch 设为 0因此第一次执行csrrw后 t60直接跳过 “保存上一个任务上下文” 的步骤因为还没有任务需要保存。# 步骤2保存上一个任务的上下文非第一次切换时执行 reg_save t6 // 调用宏把上一个任务的寄存器保存到t6指向的context mv t5, t6 // t5暂存上一个任务的context指针防止t6被覆盖 csrr t6, mscratch // 从mscratch读回原t6的值步骤1交换的原t6 STORE t6, 30*SIZE_REG(t5) // 单独保存t6到context的30*4120偏移处reg_save未处理为什么要单独保存 t6步骤 1 中 t6 被替换为 mscratch 的值context 指针原 t6 的值被存到 mscratch因此需要读回并单独保存保证 t6 寄存器的快照完整。1: # 步骤3更新mscratch为下一个任务的context指针 csrw mscratch, a0 // mscratch现在指向next任务的context后续切换时可通过mscratch获取 # 步骤4恢复下一个任务的上下文 mv t6, a0 // t6指向next任务的context作为reg_restore的base reg_restore t6 // 调用宏从context恢复所有寄存器包括t6 # 步骤5完成切换跳转到next任务的ra地址 ret // RISC-V的ret指令PC ranext任务的ra是入口函数/暂停点ret 指令的底层作用ret 指令会把 ra 寄存器的值加载到 PC程序计数器从而让 CPU 执行 next 任务的指令第一次切换时next 任务的 ra 是user_task0的地址ret 后 PC 跳转到 user_task0task0 开始执行非第一次切换时next 任务的 ra 是上次暂停时的地址如 task0 的 task_yield 之后ret 后 task0 从暂停点继续执行。3. 上下文切换的完整内存映射以 task0→task1 为例假设 task0 的 context 在内存0x80010000task1 的 context 在0x80010100切换时的内存变化保存 task0 的 rauser_task0中 task_yield 的下一条指令地址到0x80010000保存 task0 的 sptask_stack[0][1024]的当前值到0x80010004其他寄存器依次存入0x80010008~0x80010078恢复 task1 的 rauser_task1的暂停点到 ra 寄存器恢复 task1 的 sp 到 sp 寄存器ret 指令让 PCratask1 开始执行。
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

背景全屏网站app安装软件

从设计到制造:在 EasyEDA 上做好嘉立创 PCB 布线的实战指南 你有没有遇到过这种情况?花了一周时间画好原理图、布完板子,兴冲冲导出 Gerber 文件上传嘉立创,结果 DFM 检测报告弹出十几个红色警告——“间距不足”、“焊盘太小”、…

张小明 2025/12/26 14:52:05 网站建设

影视公司网站是做什么的个人简历范文

深入了解CVS:命令行语法、选项、环境变量及日期格式 1. CVS命令行选项 CVS提供了丰富的命令行选项,用于满足不同的操作需求。以下是一些常用选项的介绍: - 通用选项 - -T directory :指定用于存储临时文件的目录,该选项会覆盖环境变量或 .cvsrc 文件中的设置。 …

张小明 2025/12/26 14:51:32 网站建设

如何搭建微网站网页游戏网站建设

jQuery EasyUI 菜单与按钮 - 创建分割按钮(Split Button) jQuery EasyUI 的 splitbutton 组件是一种特殊的菜单按钮,它将按钮分为两个部分: 左侧主体:可点击执行默认主要操作(例如“保存”)。…

张小明 2025/12/27 15:07:02 网站建设

广州定制网站开发全国失信人名单查询

小型全自动气象站是一种集成化、智能化的气象监测设备,能够自主采集、记录、存储和传输多种气象参数,适用于农业、环保、科研、城市管理等多个领域。多参数实时监测监测温度、湿度、气压、风速、风向、降水量等基础气象要素,部分型号可扩展至…

张小明 2025/12/26 14:50:26 网站建设

母了猜猜看游戏做网站深圳房产信息网

Venera漫画阅读器终极使用指南:从入门到精通的全方位教程 【免费下载链接】venera A comic app 项目地址: https://gitcode.com/gh_mirrors/ve/venera 还在为找不到心仪的漫画而烦恼吗?Venera漫画阅读器将彻底改变你的阅读体验!这款开…

张小明 2025/12/27 20:06:22 网站建设

怎样用dw做网站导航条网站建设官网

compressO:你的专属视频瘦身专家 【免费下载链接】compressO Convert any video into a tiny size. 项目地址: https://gitcode.com/gh_mirrors/co/compressO 还在为视频文件太大而烦恼吗?compressO 是一款专为视频"瘦身"设计的开源工具…

张小明 2025/12/26 14:48:45 网站建设