当前位置: 首页 > 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 base
	STORE 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 base
	LOAD 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 mscratch
	beqz	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 t6
	reg_save t6					# save context of prev task

	# Save the actual t6 register, which we swapped into mscratch
	mv	t5, t6					# t5 points to the context of current task
	csrr	t6, mscratch		# read t6 back from mscratch
	STORE	t6, 30*SIZE_REG(t5)	# save t6 with t5 as base

1:
	# switch mscratch to point to the context of the next task
	csrw	mscratch, a0

	# Restore all GP registers
	# Use t6 to point to the context of the new task
	mv	t6, a0
	reg_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 	10

uint8_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 1000

void 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 的实现,为内核调度单独实现⼀个任务,在任务切换中,前⼀个用户任务首先切换到内核调度任务,然后再由内核调度任务切换到下⼀个⽤户任务,这样就可以避免前⾯提到的问题了.

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

相关文章:

  • 力扣每日打卡 50. Pow(x, n) (中等)
  • 玩转Docker | 使用Docker部署PDF阅读器PdfDing
  • JavaScript:BOM编程
  • 【吾爱出品】[Windows] 鼠标或键盘可自定义可同时多按键连点工具
  • 【Game】Powerful——Equipments
  • Kubernetes控制平面组件:APIServer 准入控制机制详解
  • Visual Studio Code 开发 树莓派 pico
  • 深入浅出一下Python面向对象编程的核心概念与实践应用
  • FPGA时序分析与约束(11)——时钟组
  • instructor 实现 reranker 功能
  • AtCoder Beginner Contest 401
  • Python yield关键字
  • 基于统计方法的水文数据分析及AI拓展
  • windows中搭建Ubuntu子系统
  • [极客大挑战 2019]Upload
  • redis单机安装
  • 智能指针之设计模式1
  • Vue--常用组件解析
  • C#容器源码分析 --- Dictionary<TKey,TValue>
  • 测试复习题目(1)
  • 海军“吉祥方舟”号医院船开展海上卫勤演练
  • 证监会副主席李明:支持符合条件的外资机构申请新业务、设立新产品
  • 中国驻美大使:远离故土的子弹库帛书正随民族复兴踏上归途
  • 美国考虑让移民上真人秀竞逐公民权,制片人称非现实版《饥饿游戏》
  • 中欧互动中的合作与分歧:务实需求将克服泛安全化的“政治钟摆”
  • 中国社联成立95周年,《中国社联期刊汇编》等研究丛书出版