开发一个RISC-V上的操作系统(五)—— 协作式多任务
目录
往期文章传送门
一、什么是多任务
二、代码实现
三、测试
往期文章传送门
开发一个RISC-V上的操作系统(一)—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统(二)—— 系统引导程序(Bootloader)_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统(三)—— 串口驱动程序(UART)_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统(四)—— 内存管理_Patarw_Li的博客-CSDN博客
本节的代码在仓库的03_MUTI_TASK目录下,仓库链接:riscv_os: 一个RISC-V上的简易操作系统
本文代码的运行调试会在前面开发的RISC-V处理器上进行,仓库链接:cpu_prj: 一个基于RISC-V指令集的CPU实现
一、什么是多任务
一个任务可以看作一个任务函数的执行流,如在一些简单的单片机系统中,只有一个任务,即main函数:
int main(void)
{/* 初始化 */while(1){/* 循环处理多项事情 */}
}
那么,什么是多任务呢?百度百科是这样解释的:
当多任务操作系统使用某种任务调度策略允许两个或更多任务并发共享一个处理器时,事实上处理器在某一时刻只会给一件任务提供服务。因为任务调度机制保证不同任务之间的切换速度十分迅速,因此给人多个任务同时运行的错觉。
因此,多任务可以看作多个任务函数的执行流,但光有多个任务还不够,还要实现任务的并发执行:
并发可以理解为分时复用,就像把一段时间切成多个小段,每个任务轮流执行一个小段的时间,在宏观上这段时间内有多个任务同时执行,在微观上某一时刻只有一个任务在执行,这就是任务的并发执行,要实现任务的并发就涉及到一个非常重要的操作——任务的切换。
任务的切换的步骤为,保存当前任务的上下文,找到下一个任务,恢复下一个任务的上下文,开始执行下一个任务。那么什么是任务的上下文呢?
任务的上下文简单来说就是任务的执行时环境,对于简单的多任务操作系统(我们这里就是),任务的上下文仅仅包含一些通用寄存器,我们将当前任务的各个通用寄存器保存起来,等待再次执行时先恢复各个通用寄存器的内容,再开始执行,从而实现任务的切换。如果是复杂一点的操作系统的话上下文还包含一些进程打开的文件、内存信息等等。
多任务系统分为协作式多任务和抢占式多任务,我们这里要实现的是协作式多任务,即任务自己主动放弃处理器的模式:
二、代码实现
先来讲解一下协作式多任务系统切换流程。
如下图所示,TASK A 和 TASK B是两个任务,Context A 和 Context B为对应任务的上下文,中间的switch_to为切换函数:

开始执行任务A时,csr寄存器mscratch指向任务A的上下文:

执行到call switch_to时,代表任务A让出cpu,调用任务切换函数switch_to:

首先要保存任务A的上下文(保存到 Context A 结构体中),其中ra寄存器中保存的是当前任务A执行的位置:

然后再切换上下文,mscratch寄存器指向 Context B,再取任务B的上下文(从 Context B 结构体中获取),然后将上下文恢复到对应的寄存器中,这里ra寄存器的内容为任务B上次执行的位置, 当我们恢复ra寄存器内容后,再调用ret指令后,PC就会跳转到任务B上一次执行的位置继续执行,从而实现任务的切换:


下面是切换函数switch_to的代码,是使用汇编写的,在 03_MUTI_TASK/entry.S文件中:
# 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 basesw ra, 0(\base)sw sp, 4(\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# restore all General-Purpose(GP) registers from the context
# except gp & tp.
# struct context *base = &ctx_task;
# ra = base->ra;
# ......
.macro reg_restore baselw ra, 0(\base)lw sp, 4(\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# Something to note about save/restore:
# - We use mscratch to hold a pointer to context of current task
# - We use 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
.align 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 mscratchsw t6, 120(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
.macro 定义两个宏函数,reg_save base 和 reg_restore base,reg_save base 作用是把通用寄存器内容存储到以base为基地址的空间中,即保存上下文;而 reg_restore base 则是把以base为基地址的通用寄存器内容取出放到各个寄存器中,即恢复上下文。
下面是任务创建、调度相关的函数,在 03_MUTI_TASK/sched.c 文件中:
#include "inc/os.h"/* defined in entry.S */
extern void switch_to(context *next);#define MAX_TASKS 4
#define STACK_SIZE 128
/** In the standard RISC-V calling convention, the stack pointer sp* is always 16-byte aligned.*/
uint8_t __attribute__((aligned(16))) task_stack[MAX_TASKS][STACK_SIZE];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 uint8_t _top = 0;
static uint8_t _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*/
void schedule()
{if (_top <= 0) {panic("Num of task should be greater than zero!");return;}_current = (_current + 1) % _top;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*/
uint8_t 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--);
}
- sched_init() 函数用于初始化mscratch寄存器。
- schedule() 函数则用于切换任务。
- task_create(void (*start_routin)(void)) 函数用于创建任务,传入的参数为任务函数的入口地址。
下面是任务的定义,在 03_MUTI_TASK/user.c 文件中:
#include "inc/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);
}
其中,os_main函数仅仅用于创建两个任务,之后不会执行。两个任务执行的内容为,先打印信息,然后delay,最后让出cpu给另外一个任务执行,依此循环。
三、测试
为了测试多任务执行效果,03_MUTI_TASK/kernal.c 的内容如下:
#include "inc/os.h"extern void os_main(void);void start_kernel(void){uart_init();uart_puts("Hello World!\n");page_init();sched_init();os_main();schedule();uart_puts("Would not go here!\n");while(1){}; // stop here!
}
然后编译烧录程序到RISC-V处理器上执行(这一步看我前面的文章),运行效果如下:

可以看到 task 1 和 task 0 分时执行,这样我们的多任务部分就验证成功啦!
遇到问题欢迎加群 892873718 交流~
相关文章:
开发一个RISC-V上的操作系统(五)—— 协作式多任务
目录 往期文章传送门 一、什么是多任务 二、代码实现 三、测试 往期文章传送门 开发一个RISC-V上的操作系统(一)—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客 开发一个RISC-V上的操作系统(二)—— 系统引导程序&a…...
Mybatis-plus集合
目录 mybatis-plus集合1、简介2、特性3、开始使用4、QueryWrapper的使用5、补充 mybatis-plus集合 1、简介 MyBatis-Plus (简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 m…...
C++ 结构体和联合体
1.结构体 结构体是一种特殊形态的类,它和类一样,可以有自己的数据成员和函数成员,可以有自己的构造函数和析构函数,可以控制访问权限,可以继承,支持包含多态,结构体定义的语法和类的定义语法几…...
使用TensorFlow训练深度学习模型实战(下)
大家好,本文接TensorFlow训练深度学习模型的上半部分继续进行讲述,下面将介绍有关定义深度学习模型、训练模型和评估模型的内容。 定义深度学习模型 数据准备完成后,下一步是使用TensorFlow搭建神经网络模型,搭建模型有两个选项…...
lucene、solr、es的区别以及应用场景
目录 1. Lucene:2. Solr:3. Elasticsearch: Lucene、Solr 和 Elasticsearch(ES) 都是基于 Lucene 引擎的搜索引擎,它们之间有相似之处,但也有一些不同之处。 Lucene 是一个低级别的搜索引擎库,它提供了一种用于创建和维护全文索引的 API&…...
Java方法的使用(重点:形参和实参的关系、方法重载、递归)
目录 一、Java方法 * 有返回类型,在方法体里就一定要返回相应类型的数据。没有返回类型(void),就不要返回!! * 方法没有声明一说。与C语言不同(C语言是自顶向下读取代码)&#…...
登录页的具体实现 (小兔鲜儿)【Vue3】
登录页 整体认识和路由配置 整体认识 登录页面的主要功能就是表单校验和登录登出业务 准备模板 <script setup></script><template><div><header class"login-header"><div class"container m-top-20"><h1 cl…...
大学如何自学嵌入式开发?
1. C语言:C语言是基础中的基础,刚开始学习不用太深入,一本常用的C语言的教材即可,注意不是当教科书看,而是看完一节过后,打开电脑把后面的习题都写出来,并且编译运行一遍,一定要动手…...
pytorch学习——线性神经网络——1线性回归
概要:线性神经网络是一种最简单的神经网络模型,它由若干个线性变换和非线性变换组成。线性变换通常表示为矩阵乘法,非线性变换通常是一个逐元素的非线性函数。线性神经网络通常用于解决回归和分类问题。 一.线性回归 线性回归是一种常见的机…...
00 - RAP 开发环境配置
文章目录 [1] Eclipse - ADT[2] BTP / S4HC[3] Add ABAP Env. Service[4] Conn. to BTP [1] Eclipse - ADT 关于如何安装配置,参见文章: Install ABAP Development Tools (ADT) and abapGit Plugin Eclipse Eclipse - ADT Eclipse - abapGit Plugin [2] BTP / S4…...
山西电力市场日前价格预测【2023-08-01】
日前价格预测 预测明日(2023-08-01)山西电力市场全天平均日前电价为310.15元/MWh。其中,最高日前电价为335.18元/MWh,预计出现在19: 45。最低日前电价为288.85元/MWh,预计出现在14: 00。 价差方向预测 1:实…...
QT--day5(网络聊天室、学生信息管理系统)
服务器: #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//给服务器指针实例化空间servernew QTcpServer(this); }Widget::~Widget() {delete ui; …...
【用IDEA基于Scala2.12.18开发Spark 3.4.1 项目】
目录 使用IDEA创建Spark项目设置sbt依赖创建Spark 项目结构新建Scala代码 使用IDEA创建Spark项目 打开IDEA后选址新建项目 选址sbt选项 配置JDK debug 解决方案 相关的依赖下载出问题多的话,可以关闭idea,重启再等等即可。 设置sbt依赖 将sbt…...
HEVC 速率控制(码控)介绍
视频编码速率控制 速率控制: 通过选择一系列编码参数,使得视频编码后的比特率满足所有需要的速率限制,并且使得编码失真尽量小。速率控制属于率失真优化的范畴,速率控制算法的重点是确定与速率相关的量化参数(Quantiz…...
四大软件测试策略的特点和区别(单元测试、集成测试、确认测试和系统测试)
四大软件测试策略分别是单元测试、集成测试、确认测试和系统测试。 一、单元测试 单元测试也称为模块测试,它针对软件中的最小单元(如函数、方法、类、模块等)进行测试,以验证其是否符合预期的行为和结果。单元测试通常由开发人…...
ingress-nginx controller安装
文章目录 一、ingress-nginx controller安装环境 1.1 部署yaml1.2 镜像1.3 安装操作 一、ingress-nginx controller安装 环境 kubernetes版本:1.27.1操作系统:CentOS7.9 1.1 部署yaml deploy.yaml apiVersion: v1 kind: Namespace metadata:labels:…...
开源快速开发平台:做好数据管理,实现流程化办公!
做好数据管理,可以提升企业的办公协作效率,实现数字化转型。开源快速开发平台是深受企业喜爱的低代码开发平台,拥有多项典型功能,是可以打造自主可控快速开发平台,实现一对一框架定制的软件平台。在快节奏的社会中&…...
基于深度学习的裂纹图像分类研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
TypeScript入门学习汇总
1.快速入门 1.1 简介 TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。 TypeScript 由微软开发的自由和开源的编程语言。 TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在…...
Vue3使用vxetable进行表格的编辑、删除与新增
效果图如下: vxetable4传送门 一、引入插件 package.json中加入"vxe-table": "4.0.23",终端中执行npm i导入import {VXETable, VxeTableInstance...
到底什么是 AI 测试?AI 测试与传统测试的区别?
过去两年,AI已经从"加分项"变成了"必选项"。 不只是大厂,二线公司、甚至传统行业的测试团队都在要求:"能熟练使用AI工具提效"。 更关键的是,面试的玩法也变了。现在的技术面试早就跳出了 “考 AI 零…...
国产多模态大模型 vs DALL-E:本土化突围与全球竞技
国产多模态大模型 vs DALL-E:本土化突围与全球竞技 引言 在AIGC浪潮席卷全球的当下,OpenAI的DALL-E系列无疑是图像生成领域的耀眼明星,其惊人的创造力和对自然语言的深刻理解,定义了“文生图”的新高度。然而,当我们聚…...
手把手教你用8255和12864 LCD搞定微机原理课设:一个公交报站器的完整实现
从零构建基于8255与12864 LCD的智能公交报站系统:硬件驱动与状态机设计实战 在微机原理课程设计中,将理论知识转化为实际项目是检验学习成果的关键。本文将带你完整实现一个具备动态显示、交互控制和状态管理的公交报站系统,重点剖析8255并行…...
森林-服务器存档
对于想要自建游戏服务器的玩家,云鸢互联是一个不错的专业联机平台选择。它提供稳定、低延迟且724小时在线的服务器环境,助你轻松打造专属游戏世界。平台主打极致的新手友好——全图形化控制面板,无需编写代码,也无需掌握Linux命令…...
从概率拟合到内生心智:七层投影架构重构AGI数字生命新范式
自2017年Transformer架构问世以来,人工智能领域正式迈入大模型迭代时代。十余年间,千亿、万亿参数模型不断涌现,依托自注意力机制的概率拟合算法,AI在文本生成、多模态交互、逻辑问答等领域实现了规模化突破,彻底改变了…...
免费文档下载终极方案:如何优雅获取百度文库等30+平台资源
免费文档下载终极方案:如何优雅获取百度文库等30平台资源 【免费下载链接】kill-doc 看到经常有小伙伴们需要下载一些免费文档,但是相关网站浏览体验不好各种广告,各种登录验证,需要很多步骤才能下载文档,该脚本就是为…...
机器学习评价指标之综合指标的关系
综合指标的关系宏平均考虑每个类别的个别表现,并对它们的评价指标(比如准确率、召回率等)进行平均。每个类别 被视为同等重要,无论类别的大小或样本数量。微平均则关注整体表现,它将所有类别的预测结果合并起来&#x…...
10个sd-webui-regional-prompter实用技巧:从基础分割到高级2D区域配置
10个sd-webui-regional-prompter实用技巧:从基础分割到高级2D区域配置 【免费下载链接】sd-webui-regional-prompter set prompt to divided region 项目地址: https://gitcode.com/gh_mirrors/sd/sd-webui-regional-prompter sd-webui-regional-prompter是一…...
Topit:Mac窗口置顶终极指南 - 三步打造高效多任务工作环境
Topit:Mac窗口置顶终极指南 - 三步打造高效多任务工作环境 【免费下载链接】Topit Pin any window to the top of your screen / 在Mac上将你的任何窗口强制置顶 项目地址: https://gitcode.com/gh_mirrors/to/Topit 还在为Mac上频繁切换窗口而烦恼吗&#x…...
剪映专业版教程:制作堆排序算法原理演示视频
前言 今天教大家用剪映制作堆排序算法的原理演示视频。堆排序的原理是:先将无序序列构建成一个小根堆(堆顶元素是整个堆中最小的),然后反复取出堆顶元素放到有序序列末尾,再将剩余元素重新调整成小根堆,重…...
