从零手写操作系统之RVOS协作式多任务切换实现-03

news2025/1/8 4:53:15

从零手写操作系统之RVOS协作式多任务切换实现-03

  • 任务(task)
  • 多任务 (Multitask)
  • 任务上下文(Context)
  • 多任务系统的分类
    • 协作式多任务
  • 创建和初始化第 1 号任务
  • 切换到第一号任务执行
  • 协作式多任务 - 调度
    • 初始化和任务创建
    • 任务运行


本系列参考: 学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春 整理而来,主要作为xv6操作系统学习的一个前置基础。

RVOS是本课程基于RISC-V搭建的简易操作系统名称。

课程代码和环境搭建教程参考github仓库: https://github.com/plctlab/riscv-operating-system-mooc/blob/main/howto-run-with-ubuntu1804_zh.md

前置知识:

  • RVOS环境搭建-01
  • RVOS操作系统内存管理简单实现-02

任务(task)

所谓任务,简单来说就是一个函数的调用过程,也就是一个指令的执行流,也就是运行起来的程序,也就是我们常说的一个进程或者线程的概念。
在这里插入图片描述

多任务 (Multitask)

多任务就是存在多个核并行执行多个指令流的过程:
在这里插入图片描述
本节中使用单核配合分时复用,完成多个任务的切换运行。


任务上下文(Context)

由于一个核通常关联一套通用寄存器,每个进程运行时,都会使用寄存器来保持当前执行流中相关变量的值,所以当进程A需要切换到进程B执行时,就需要将通用寄存器的值保存到内存中进程A对应的Context对象中,而将进程B关联的Context对象中保存的寄存器z值恢复到当前通用寄存器上:
在这里插入图片描述

/* task management */
struct context {
	/* ignore x0 */
	reg_t ra;
	reg_t sp;
	reg_t gp;
	reg_t tp;
	reg_t t0;
	reg_t t1;
	reg_t t2;
	reg_t s0;
	reg_t s1;
	reg_t a0;
	reg_t a1;
	reg_t a2;
	reg_t a3;
	reg_t a4;
	reg_t a5;
	reg_t a6;
	reg_t a7;
	reg_t s2;
	reg_t s3;
	reg_t s4;
	reg_t s5;
	reg_t s6;
	reg_t s7;
	reg_t s8;
	reg_t s9;
	reg_t s10;
	reg_t s11;
	reg_t t3;
	reg_t t4;
	reg_t t5;
	reg_t t6;
};

上面代码存在于03节的os.h文件中


多任务系统的分类

在这里插入图片描述

协作式多任务

本节我们来讲解一下协作式多任务的实现流程,首先我们先来复习一下call指令和ret指令:

call label
  • 其中,label 是目标子程序的标签。call 指令会将当前指令的下一条指令的地址保存到链接寄存器(link register)中,并跳转到目标子程序的地址。
  • 在RISC-V中,链接寄存器是 ra(x1),它通常用于保存函数的返回地址。因此,在执行 call 指令之前,程序需要将函数的参数准备好,并将它们存储在适当的寄存器中。
  • 当子程序执行完毕后,通过 ret 指令返回到调用位置。ret 指令会从链接寄存器中获取保存的返回地址,并跳转到该地址继续执行。
  • 需要注意的是,RISC-V中的 callret 指令没有显式地处理参数传递和局部变量的保存。这些任务通常通过约定和编程规范来实现。例如,参数可以通过寄存器传递,而局部变量可以在堆栈上分配和访问。编程者需要根据具体的编程规范和约定来管理参数和局部变量的传递。

下面我们先来看看任务切换的实现流程:
在这里插入图片描述
我们可以在程序中调用switch_to函数,手动完成任务的切换,由于任务切换十分频繁,所以这里使用汇编来实现switch_to函数:

# void switch_to(struct context *next);
# a0: pointer to the context of the next task
# 汇编编写的switch_to函数可以看做c语言中的: void switch_to(struct context *next);
# 其中函数有一个context指针作为参数,而实际上由a0作为参数寄存器,存放该context指针的值
.globl switch_to
.align 4
switch_to:
    # 交换mscratch和t6寄存器的值--t6指向进程的Context上下文地址
	csrrw	t6, mscratch, t6	# swap t6 and mscratch
	# 判断switch_to函数是否是首次调用(t6==0),如果是则跳到标签1处执行
	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
	# t5承担t6的职责,指向当前进程的Context上下文地址
	mv	t5, t6		# t5 points to the context of current task
	# 恢复t6寄存器原本的值
	csrr	t6, mscratch	# read t6 back from mscratch
	# 将t6的值保存到Context上下文中
	sw	t6, 120(t5)	# save t6 with t5 as base

1:
	# switch mscratch to point to the context of the next task
	# 将mscratch指向要切换的上下文地址
	csrw	mscratch, a0

	# Restore all GP registers
	# Use t6 to point to the context of the new task
	# t6寄存器指向要切换的上下文地址
	mv	t6, a0
	# 将t6指向的上下文中的寄存器值进行恢复操作
	reg_restore t6

	# Do actual context switching.
	ret
  1. call switch_to将要切换的上下文Context地址作为参数传入,由a0参数寄存器保存
  2. switch_to函数首先判断是否为首次调用switch_to函数,如果是,则直接进行上下文恢复操作
  3. 如果不是,则首先保存当前上下文,然后再进行上下文恢复操作

隐藏操作:

  1. call指令调用时,会将当前函数返回地址保存到ra寄存器中
  2. ret指令调用时,会调回到ra寄存器保存的地址处继续执行
  3. ra寄存器中的值同样会随着Context上下文的保存和恢复进行切换,从而达到任务切换执行的效果

问题解答:

  • 函数调用过程中的寄存器使用约定
    在这里插入图片描述
  • 为什么非要交换mscratch和t6呢?不能直接将mscratch传入reg_save宏吗?
    在这里插入图片描述
  • reg_save宏定义怎么保存通用寄存器(GP)的值保存到上下文中的呢?
# save all General-Purpose(GP) registers to context
# struct context *base = &ctx_task;
# base->ra = ra;
# ......
.macro reg_save base
	sw ra, 0(\base)
	sw sp, 4(\base)
	sw gp, 8(\base)
	sw tp, 12(\base)
	sw t0, 16(\base)
	sw t1, 20(\base)
	sw t2, 24(\base)
	sw s0, 28(\base)
	sw s1, 32(\base)
	sw a0, 36(\base)
	sw a1, 40(\base)
	sw a2, 44(\base)
	sw a3, 48(\base)
	sw a4, 52(\base)
	sw a5, 56(\base)
	sw a6, 60(\base)
	sw a7, 64(\base)
	sw s2, 68(\base)
	sw s3, 72(\base)
	sw s4, 76(\base)
	sw s5, 80(\base)
	sw s6, 84(\base)
	sw s7, 88(\base)
	sw s8, 92(\base)
	sw s9, 96(\base)
	sw s10, 100(\base)
	sw s11, 104(\base)
	sw t3, 108(\base)
	sw t4, 112(\base)
	sw t5, 116(\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

在这里插入图片描述

  • reg_restore怎么恢复上下文的呢?
# restore all General-Purpose(GP) registers from the context
# struct context *base = &ctx_task;
# ra = base->ra;
# ......
.macro reg_restore base
	lw ra, 0(\base)
	lw sp, 4(\base)
	lw gp, 8(\base)
	lw tp, 12(\base)
	lw t0, 16(\base)
	lw t1, 20(\base)
	lw t2, 24(\base)
	lw s0, 28(\base)
	lw s1, 32(\base)
	lw a0, 36(\base)
	lw a1, 40(\base)
	lw a2, 44(\base)
	lw a3, 48(\base)
	lw a4, 52(\base)
	lw a5, 56(\base)
	lw a6, 60(\base)
	lw a7, 64(\base)
	lw s2, 68(\base)
	lw s3, 72(\base)
	lw s4, 76(\base)
	lw s5, 80(\base)
	lw s6, 84(\base)
	lw s7, 88(\base)
	lw s8, 92(\base)
	lw s9, 96(\base)
	lw s10, 100(\base)
	lw s11, 104(\base)
	lw t3, 108(\base)
	lw t4, 112(\base)
	lw t5, 116(\base)
	lw t6, 120(\base)
.endm

在这里插入图片描述

  • mscratch(Machine Scratch)这个CSR寄存器有什么作用?

mscratch 是 RISC-V 架构中的一个控制和状态寄存器(Control and Status Register),用于保存机器模式下的临时数据或上下文相关的信息。它的作用是提供一个通用的、临时的存储位置,供软件使用。

具体而言,mscratch 寄存器通常用于以下情况:

  • 上下文切换:当处理器从一个上下文切换到另一个上下文时,可以将当前的 mscratch 寄存器的值保存到保存的上下文中。在切换到新的上下文后,可以将先前保存的 mscratch 寄存器的值恢复,以便继续使用其中的数据。
  • 异步事件处理:当处理器在处理中断或异常时,可能需要保存一些临时数据,以便在恢复正常执行后继续使用。mscratch 寄存器提供了一个方便的位置来存储这些临时数据,以避免污染其他重要的寄存器。
  • 调试和跟踪:在调试和跟踪应用程序时,mscratch 寄存器可以用于存储调试器或跟踪工具的临时数据,例如断点信息、调试状态等。

需要注意的是,mscratch 寄存器的使用是由软件决定的,它没有特定的预定义用途。软件可以根据需要将 mscratch 寄存器用于临时存储和处理数据。然而,由于 mscratch 寄存器的值可能会被上下文切换或其他操作修改,因此软件在使用 mscratch 寄存器时应注意保存和恢复其中的数据。

总结:mscratch 寄存器是 RISC-V 架构中的一个控制和状态寄存器,用于保存机器模式下的临时数据或上下文相关的信息。它可以用于上下文切换、异步事件处理、调试和跟踪等情况,提供一个通用的临时存储位置供软件使用。


  • beqz t6, 1f 指令中1后面的f是什么意思?

在汇编语言中,标签通常以 . 或一个数字开头,并可以在其后加上后缀来表示不同的类型。

后缀 f 表示前向引用(forward reference)。在这种情况下,数字后面的 f 表示标签是前向引用,即在当前位置之后定义的标签。这种用法允许在代码中跳转到稍后定义的标签。

在给定的示例中,1f 表示跳转到标签 1 所在的位置,而 1 是在当前位置之后定义的标签。这样的标签定义可以简化代码中的跳转逻辑。


创建和初始化第 1 号任务

在这里插入图片描述

#define STACK_SIZE 1024
//当前任务上下文的用户栈
uint8_t task_stack[STACK_SIZE];
//当前任务上下文
struct context ctx_task;

static void w_mscratch(reg_t x){
	asm volatile("csrw mscratch, %0" : : "r" (x));
}

void user_task0(void);
//调度任务初始化
void sched_init(){
    //初始化mscratch为0
	w_mscratch(0);
    //初始化第一个任务的栈地址
	ctx_task.sp = (reg_t) &task_stack[STACK_SIZE - 1];
	//保存任务1的地址到当前任务上下文的ra寄存器中(内存中)
	ctx_task.ra = (reg_t) user_task0;
}

/*
 * a very rough implementaion, just to consume the cpu
 */
void task_delay(volatile int count)
{
	count *= 50000;
	while (count--);
}

void user_task0(void)
{ 
    //通过串口输出任务创建信息
	uart_puts("Task 0: Created!\n");
	//每隔1s输出一条信息
	while (1) {
		uart_puts("Task 0: Running...\n");
		task_delay(1000);
	}
}

切换到第一号任务执行

在这里插入图片描述

void schedule()
{
    //获取要切换执行的任务上下文地址
	struct context *next = &ctx_task;
	//调用switch_to函数进行任务切换
	switch_to(next);
}

内核启动:

void start_kernel(void){
	uart_init();
	uart_puts("Hello, RVOS!\n");
	page_init();
	//1号调度任务初始化
	sched_init();
	//切换到1号任务执行
	schedule();
	//如果下面这段文字输出了,说明任务切换实现的有bug
	uart_puts("Would not go here!\n");
	while (1) {}; // stop here!
}

测试:
在这里插入图片描述


协作式多任务 - 调度

到目前为止,我们只实现了操作系统启动后切换到1号任务执行的效果,还无法实现多任务切换,本节我们在上一节的基础上进行改进,实现多任务切换效果:

在这里插入图片描述

  • 首先,为了支持多个同时存在多个任务,我们需要增加任务上下文容器来保持多个任务上下文,还有就是多个内核栈
//这里我们最多支持创建10个任务
#define MAX_TASKS 10
#define STACK_SIZE 1024
uint8_t task_stack[MAX_TASKS][STACK_SIZE];
struct context ctx_tasks[MAX_TASKS];
  • top标识我们一共创建了多少个任务,current指向当前任务
/*
 * _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;
  • 任务调度采用轮询策略实现任务调度
/*
 * implment a simple cycle FIFO schedular
 */
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);
}
  • 在schedule基础上封装得到task_yield函数,用户程序调用该函数实现主动让出CPU使用权的效果
/*
 * DESCRIPTION
 * 	task_yield()  causes the calling task to relinquish the CPU and a new 
 * 	task gets to run.
 */
void task_yield()
{
	schedule();
}

初始化和任务创建

在这里插入图片描述

  • 调度程序初始化此时只需要清空mscratch寄存器即可
void sched_init()
{
	w_mscratch(0);
}
  • 任务创建
/*
 * 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 - 1];
		ctx_tasks[_top].ra = (reg_t) start_routin;
		_top++;
		return 0;
	} else {
		return -1;
	}
}
  • 系统启动时,调用os_main创建两个初始化任务执行
#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);
}

任务运行

void start_kernel(void)
{
	uart_init();
	uart_puts("Hello, RVOS!\n");

	page_init();

	sched_init();

	os_main();

	schedule();

	uart_puts("Would not go here!\n");
	while (1) {}; // stop here!
}

测试:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/608890.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

字典树算法(C/C++)

目录 一、字典树算法的概念介绍 二、字典树算法的实现 三、例题 &#xff08;注&#xff1a;借鉴蓝桥杯国赛特训营&#xff09; 一、字典树算法的概念介绍 首先我们看下字典的组织方式 Trie 的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效…

实训总结-----Scrapy爬虫

1.安装指令 pip install scrapy 2.创建 scrapy 项目 任意终端 进入到目录(用于存储我们的项目) scrapy startproject 项目名 会在目录下面 创建一个以 项目名 命名的文件夹 终端也会有提示 cd 项目名 scrapy genspider example example.com 3.运行爬虫指令 scrapy craw…

ffmpeg之AVFormatContext结构体详细解释

AVFormatContext 作用 AVFormatContext主要起到了管理和存储媒体文件相关信息的作用。它是一个比较重要的结构体&#xff0c;在FFmpeg中用于表示媒体文件的格式上下文&#xff0c;其中包含了已经打开的媒体文件的详细信息&#xff0c;包括媒体文件的格式、媒体流的信息、各个媒…

【笔记】使用电脑连接树莓派 并在电脑屏幕上显示树莓派桌面(无需额外为树莓派购买显示器)

一、前言 想在树莓派上跑 yolo5&#xff0c;为了方便地看到代码的检测结果&#xff0c;需要为树莓派外接显示器&#xff0c;但是手头并没有额外的显示器&#xff0c;于是想在电脑屏幕上显示树莓派的桌面&#xff0c;对解决的过程作一些记录。 二、基本流程 树莓派系统的烧录…

c++11 标准模板(STL)(std::bitset)(三)

定义于头文件 <bitset> template< std::size_t N > class bitset; 类模板 bitset 表示一个 N 位的固定大小序列。可以用标准逻辑运算符操作位集&#xff0c;并将它与字符串和整数相互转换。 bitset 满足可复制构造 (CopyConstructible) 及可复制赋值 (CopyAssign…

【SpringMVC】请求与响应

1&#xff0c;PostMan工具的使用 1. PostMan简介 代码编写完后&#xff0c;我们要想测试&#xff0c;只需要打开浏览器直接输入地址发送请求即可。发送的是GET请求可以直接使用浏览器&#xff0c;但是如果要发送的是POST请求呢? 如果要求发送的是post请求&#xff0c;我们就…

基于前推回代法的连续潮流计算研究【IEEE33节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

从零搭建微服务-网关中心(一)

写在最前 如果这个项目让你有所收获&#xff0c;记得 Star 关注哦&#xff0c;这对我是非常不错的鼓励与支持。 源码地址&#xff1a;https://gitee.com/csps/mingyue 文档地址&#xff1a;https://gitee.com/csps/mingyue/wikis 新建 mingyue-gateway 在 【从零搭建微服务…

09 Redis与MySQL数据双写一致性工程落地案例

canal 是什么 canal [kə’nl]&#xff0c;中文翻译为 水道/管道/沟渠/运河&#xff0c;主要用途是用于 MySQL 数据库增量日志数据的订阅、消费和解析&#xff0c;是阿里巴巴开发并开源的&#xff0c;采用Java语言开发&#xff1b;历史背景是早期阿里巴巴因为杭州和美国双机房…

23种设计模式之职责链模式(Chain of Responsibility Pattern)

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章将23种设计模式中的访问者模式&#xff0c;此篇文章为一天学习一个设计模式系列文章&#xff0c;后面会分享其他模式知识。 如果文章有什么需要改进的地方还请大佬…

集群化环境前置准备

集群化环境前置准备 介绍 需要完成集群化环境的前置准备&#xff0c;包括创建多台虚拟机&#xff0c;配置主机名映射&#xff0c;SSH免密登录等等。 部署 配置多台Linux虚拟机 安装集群化软件&#xff0c;首要条件就是要有多台Linux服务器可用。 我们可以使用VMware提供的…

冈萨雷斯DIP第1章知识点

文章目录 1.1 什么是数字图像处理1.3 数字图像处理技术应用领域实例1.4 数字图像处理的基本步骤 1.1 什么是数字图像处理 图像、数字图像 一幅图像可以定义为一个二维函数 f ( x , y ) f(x,y) f(x,y)&#xff0c; 其中 x x x 和 y y y 是空间(平面)坐标&#xff0c; 在坐标…

你真的会性能测试吗?资深测试总结全链路压测(详全)卷起来...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 什么是全链路压测…

多分类问题练习

练习3&#xff1a;多分类问题 介绍 在本练习中&#xff0c;我们将使用逻辑回归来识别手写数字&#xff08;0到9&#xff09;。我们将扩展在练习2中对逻辑回归的实现&#xff0c;并将其应用于一对多的分类问题。 在开始练习前&#xff0c;需要下载如下的文件进行数据上传&…

【Python开发】FastAPI 08:Security 登录认证

FastAPI 在 fastapi.security 模块中提供了诸多安全性的工具&#xff0c;简化了各种安全机制的使用方法&#xff0c;可用于处理安全性、身份认证和授权等问题&#xff01; 目录 1 介绍 1.1 OAuth2 1.2 OpenAPI 2 安全基础 2.1 使用 Bearer ① OAuth2PasswordBearer ② 使…

开关电源关键参数计算方法

1、源调整率&#xff08;Line Regulation&#xff09;&#xff1a;将待测开关电源以额定输入电压及额定负载状况下热机15 分钟稳定后&#xff0c;分别于输入电压的下限、额定输入电压(Normal)、输入电压的上限测量并记录各自对应的输出电压值为 V1、V0&#xff08;normal&#…

Linux NGINX服务 ReWrite^location

ReWrite^location 从功能看 rewrite 和 location 似乎有点像&#xff0c;都能实现跳转&#xff0c;主要区别在于 rewrite 是在同一域名内更改获取资源的路径&#xff0c;而 location 是对一类路径做控制访问或反向代理&#xff0c;还可以proxy_pass 到其他机器。 rewrite 对访问…

Nginx正则表达式、location匹配、Rewrite重写详解

Nginx正则表达式、location匹配、Rewrite重写详解 一、常用的Nginx正则表达式二、location匹配概述1、location大致可以分为三类2、location常用的匹配规则3、location 优先级4、location 示例说明5、实际网站使用中&#xff0c;至少有三个匹配规则定义 三、rewrite重写1、rewr…

果推断16--基于反事实因果推断的度小满额度模型学习笔记

目录 一、原文地址 二、一些问题 2.1如何从RCT随机样本过渡到观测样本因果建模&#xff1f; 2.2反事实学习的核心思想 2.3度小满的连续反事实额度模型 Mono-CFR 2.4Mono-CFR代码实现&#xff08;待补充&#xff09; 2.5CFR学习 2.5.1CFR 2.5.2DR-CFR 参考 一、原文地…

Spring Cloud Alibaba — Nacos 构建服务注册中心

文章目录 Nacos Server下载启动登录创建命名空间 Nacos Client启动样例Nacos 服务发现配置项 集成 OpenFeign 远程接口调用添加 OpenFeign 依赖开启 EnableFeignClients 注解编写远程服务接口远程接口调用 集成 Sentinel 熔断降级添加 Sentinel 依赖开启 Sentinel 熔断降级编写…