当前位置: 首页 > news >正文

RVOS-4.实现上下文切换和协作式多任务

4. 上下文切换和协作式多任务

4.1 多任务与上下文

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

任务切换时需要保存当前任务的上下文**(即x1~x31个寄存器的内容)**

4.2 协作式多任务的设计与实现

  • 协作式多任务 (Cooperative Multitasking):协作式环境下,下一个任务被调度的前提是当前任务主动放弃处理器
  • 抢占式多任务 (Preemptive Multitasking):抢占式环境下,操作系统完全决定任务调度方案,操作系统可以剥夺当前任务对处理器的使用,将处理器提供给其它任务。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(1). 关键函数switch to:

#define LOAD		lw
#define STORE		sw
#define SIZE_REG	4# Save all General-Purpose(GP) registers to context.
# struct context *base = &ctx_task;
# base->ra = ra;
# ......
# These GP registers to be saved don't include gp
# and tp, because they are not caller-saved or
# callee-saved. These two registers are often used
# for special purpose. For example, in RVOS, 'tp'
# (aka "thread pointer") is used to store hartid,
# which is a global value and would not be changed
# during context-switch.
.macro reg_save baseSTORE ra,   0*SIZE_REG(\base)STORE sp,   1*SIZE_REG(\base)STORE t0,   4*SIZE_REG(\base)STORE t1,   5*SIZE_REG(\base)STORE t2,   6*SIZE_REG(\base)STORE s0,   7*SIZE_REG(\base)STORE s1,   8*SIZE_REG(\base)STORE a0,   9*SIZE_REG(\base)STORE a1,  10*SIZE_REG(\base)STORE a2,  11*SIZE_REG(\base)STORE a3,  12*SIZE_REG(\base)STORE a4,  13*SIZE_REG(\base)STORE a5,  14*SIZE_REG(\base)STORE a6,  15*SIZE_REG(\base)STORE a7,  16*SIZE_REG(\base)STORE s2,  17*SIZE_REG(\base)STORE s3,  18*SIZE_REG(\base)STORE s4,  19*SIZE_REG(\base)STORE s5,  20*SIZE_REG(\base)STORE s6,  21*SIZE_REG(\base)STORE s7,  22*SIZE_REG(\base)STORE s8,  23*SIZE_REG(\base)STORE s9,  24*SIZE_REG(\base)STORE s10, 25*SIZE_REG(\base)STORE s11, 26*SIZE_REG(\base)STORE t3,  27*SIZE_REG(\base)STORE t4,  28*SIZE_REG(\base)STORE t5,  29*SIZE_REG(\base)# we don't save t6 here, due to we have used# it as base, we have to save t6 in an extra step# outside of reg_save
.endm# restore all General-Purpose(GP) registers from the context
# except gp & tp.
# struct context *base = &ctx_task;
# ra = base->ra;
# ......
.macro reg_restore baseLOAD ra,   0*SIZE_REG(\base)LOAD sp,   1*SIZE_REG(\base)LOAD t0,   4*SIZE_REG(\base)LOAD t1,   5*SIZE_REG(\base)LOAD t2,   6*SIZE_REG(\base)LOAD s0,   7*SIZE_REG(\base)LOAD s1,   8*SIZE_REG(\base)LOAD a0,   9*SIZE_REG(\base)LOAD a1,  10*SIZE_REG(\base)LOAD a2,  11*SIZE_REG(\base)LOAD a3,  12*SIZE_REG(\base)LOAD a4,  13*SIZE_REG(\base)LOAD a5,  14*SIZE_REG(\base)LOAD a6,  15*SIZE_REG(\base)LOAD a7,  16*SIZE_REG(\base)LOAD s2,  17*SIZE_REG(\base)LOAD s3,  18*SIZE_REG(\base)LOAD s4,  19*SIZE_REG(\base)LOAD s5,  20*SIZE_REG(\base)LOAD s6,  21*SIZE_REG(\base)LOAD s7,  22*SIZE_REG(\base)LOAD s8,  23*SIZE_REG(\base)LOAD s9,  24*SIZE_REG(\base)LOAD s10, 25*SIZE_REG(\base)LOAD s11, 26*SIZE_REG(\base)LOAD t3,  27*SIZE_REG(\base)LOAD t4,  28*SIZE_REG(\base)LOAD t5,  29*SIZE_REG(\base)LOAD t6,  30*SIZE_REG(\base)
.endm# Something to note about save/restore:
# - mscratch: hold a pointer to context of current task
# - t6: as the 'base' for reg_save/reg_restore, because it is the
#   very bottom register (x31) and would not be overwritten during loading.
#   Note: CSRs(mscratch) can not be used as 'base' due to load/restore
#   instruction only accept general purpose registers..text
# void switch_to(struct context *next);
# a0: pointer to the context of the next task
.globl switch_to
.balign 4
switch_to:csrrw	t6, mscratch, t6	# swap t6 and mscratchbeqz	t6, 1f				# Note: the first time switch_to() is# called, mscratch is initialized as zero# (in sched_init()), which makes t6 zero,# and that's the special case we have to# handle with t6reg_save t6					# save context of prev task# Save the actual t6 register, which we swapped into mscratchmv	t5, t6					# t5 points to the context of current taskcsrr	t6, mscratch		# read t6 back from mscratchSTORE	t6, 30*SIZE_REG(t5)	# save t6 with t5 as base1:# switch mscratch to point to the context of the next taskcsrw	mscratch, a0# Restore all GP registers# Use t6 to point to the context of the new taskmv	t6, a0reg_restore t6# Do actual context switching.ret.end

(2). 创建和初始化第 1 号任务

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(3). 踏出 context switch 的第一步,切换到第一个用户任务

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(4). 协作式多任务 - 调度

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 定义了最大任务数 MAX_TASKS 和每个任务的栈大小 STACK_SIZE(---->这里都是静态分配内存,放在全局数据段.data 段(已初始化数据段).bss 段(未初始化数据段))。
  • 为每个任务分配了栈空间 task_stack 和上下文 ctx_tasks
  • 使用 _top_current 变量来管理任务的创建和调度。
  • schedule 函数实现了基本的任务调度逻辑,通过循环遍历任务列表来选择下一个要运行的任务。
  • task_yield 函数允许当前任务主动放弃CPU,触发调度器选择下一个任务运行。

(5). 协作式多任务 - 初始化和任务创建

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(6). 协作式多任务 - 任务运行

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

shed.c:

#include "os.h"/* defined in entry.S */
extern void switch_to(struct context *next);/** In the standard RISC-V calling convention, the stack pointer sp* is always 16-byte aligned.* 使用 __attribute__((aligned(16))) 属性确保栈的起始地址是 16 字节对齐的。* 这是 RISC-V 调用约定的要求,以确保栈指针 sp 总是 16 字节对齐。*/
#define STACK_SIZE 	1024
#define MAX_TASKS 	10uint8_t __attribute__((aligned(16))) task_stack[MAX_TASKS][STACK_SIZE];
struct context ctx_tasks[MAX_TASKS];/** _top is used to mark the max available position of ctx_tasks* _current is used to point to the context of current task*/
static int _top = 0;
static int _current = -1;static void w_mscratch(reg_t x)
{asm volatile("csrw mscratch, %0" : : "r" (x));
};
void sched_init()
{w_mscratch(0);
}/** implment a simple cycle FIFO schedular* 简单的FIFO调度器*/
void schedule()
{if (_top <= 0) {panic("Num of task should be greater than zero!");return;}_current = (_current + 1) % _top;struct context *next = &(ctx_tasks[_current]);switch_to(next);
}/** DESCRIPTION* 	Create a task.* 	- start_routin: task routine entry* RETURN VALUE* 	0: success* 	-1: if error occured*/
int task_create(void (*start_routin)(void))
{if (_top < MAX_TASKS) {ctx_tasks[_top].sp = (reg_t) &task_stack[_top][STACK_SIZE];ctx_tasks[_top].ra = (reg_t) start_routin;_top++;return 0;} else {return -1;}
}/** DESCRIPTION* 	task_yield()  causes the calling task to relinquish the CPU and a new * 	task gets to run.*/
void task_yield()
{schedule();
}/** a very rough implementaion, just to consume the cpu*/
void task_delay(volatile int count)
{count *= 50000;while (count--);
}

user.c:

#include "os.h"#define DELAY 1000void user_task0(void)
{uart_puts("Task 0: Created!\n");while (1) {uart_puts("Task 0: Running...\n");task_delay(DELAY);task_yield();}
}void user_task1(void)
{uart_puts("Task 1: Created!\n");while (1) {uart_puts("Task 1: Running...\n");task_delay(DELAY);task_yield();}
}/* NOTICE: DON'T LOOP INFINITELY IN main() */
void os_main(void)
{task_create(user_task0);task_create(user_task1);
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

练习 9-1

要求:参考 code/os/04-multitask,在此基础上进⼀步改进任务管理功能。具体要求:改进 task_create(),提供更多的参数,具体改进后的函数如下所⽰:

int task_create(void (*task)(void* param),void *param, uint8_t priority);

其中:param 用于在创建任务执行函数时可带入参数,如果没有参数则传入 NULL。priority 用于指定任务的优先级,目前要求最多⽀持 256 级,0 最高,依次类推。同时修改任务调度算法,在原先简单轮转的基础上⽀持按照优先级排序,优先选择优先级高的任务运行,同⼀级多个任务再轮转。

增加任务退出接口 task_exit(),当前任务可以通过调用该接口退出执行,内核负责将该任务回收,并调度下⼀个可运行任务。建议的接⼝函数如下:

void task_exit(void);

练习 9-2

⽬前 code/os/04-multitask 实现的任务调度中,前⼀个用户任务直接调用 task_yield() 函数并最终调用switch_to() 切换到下⼀个⽤户任务。task_yield() 作为内核路径借用了用户任务的栈,当用户任务的函数调用层次过多或者 task_yield() 本⾝函数内部继续调⽤函数,可能会导致用户任务的栈空间溢出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

参考"mini-riscv-os" 的 03-MultiTasking 的实现,为内核调度单独实现⼀个任务,在任务切换中,前⼀个用户任务首先切换到内核调度任务,然后再由内核调度任务切换到下⼀个⽤户任务,这样就可以避免前⾯提到的问题了.

这次练习有点难,先放一下吧,后续尝试实现一下,最近玩的实在太多了,要收心学习啦!!!被聪哥追上好多了,学如逆水行舟,不进则退!!!


http://www.mrgr.cn/news/98225.html

相关文章:

  • 大模型到底是怎么产生的?一文了解大模型诞生全过程
  • KTransformers安装笔记 利用docker安装KTransformers
  • 句句翻译。
  • mysql安装-MySQL MGR(Group Replication)+ ProxySQL 架构
  • 【初入职场】文件地狱大逃亡:运维侠Python自动化逆袭之路4整理术(日省3h摸鱼真经)
  • 探秘数据库连接池:HikariCP与Tomcat JDBC
  • 第16届蓝桥杯c++省赛c组个人题解
  • Rasa 模拟实现超简易医生助手(适合初学练手)
  • Google 官方提示工程 (Prompt Engineering)白皮书 总结
  • JavaWeb-04-Web后端基础(SpringBootWeb、HTTP协议、分层解耦、IOC和DI)
  • Agent革命:Google AI白皮书解密未来智能体的进化之路
  • 双指针、滑动窗口
  • FTXUI 笔记(五)——基本交互组件
  • Java—— 文字版格斗游戏
  • 一种基于学习的多尺度方法及其在非弹性碰撞问题中的应用
  • 【Linux实践系列】:匿名管道收尾+完善shell外壳程序
  • # Shell脚本参数设计规范(DeepSeek指导)
  • 大模型到底是怎么产生的?一文揭秘大模型诞生全过程
  • Redis之缓存更新策略
  • Ubuntu系统美化