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

开发一个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  basereg_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上的操作系统&#xff08;一&#xff09;—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客 开发一个RISC-V上的操作系统&#xff08;二&#xff09;—— 系统引导程序&a…...

Mybatis-plus集合

目录 mybatis-plus集合1、简介2、特性3、开始使用4、QueryWrapper的使用5、补充 mybatis-plus集合 1、简介 MyBatis-Plus &#xff08;简称 MP&#xff09;是一个 MyBatis的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 m…...

C++ 结构体和联合体

1.结构体 结构体是一种特殊形态的类&#xff0c;它和类一样&#xff0c;可以有自己的数据成员和函数成员&#xff0c;可以有自己的构造函数和析构函数&#xff0c;可以控制访问权限&#xff0c;可以继承&#xff0c;支持包含多态&#xff0c;结构体定义的语法和类的定义语法几…...

使用TensorFlow训练深度学习模型实战(下)

大家好&#xff0c;本文接TensorFlow训练深度学习模型的上半部分继续进行讲述&#xff0c;下面将介绍有关定义深度学习模型、训练模型和评估模型的内容。 定义深度学习模型 数据准备完成后&#xff0c;下一步是使用TensorFlow搭建神经网络模型&#xff0c;搭建模型有两个选项…...

lucene、solr、es的区别以及应用场景

目录 1. Lucene:2. Solr:3. Elasticsearch: Lucene、Solr 和 Elasticsearch(ES) 都是基于 Lucene 引擎的搜索引擎&#xff0c;它们之间有相似之处&#xff0c;但也有一些不同之处。 Lucene 是一个低级别的搜索引擎库&#xff0c;它提供了一种用于创建和维护全文索引的 API&…...

Java方法的使用(重点:形参和实参的关系、方法重载、递归)

目录 一、Java方法 * 有返回类型&#xff0c;在方法体里就一定要返回相应类型的数据。没有返回类型&#xff08;void&#xff09;&#xff0c;就不要返回&#xff01;&#xff01; * 方法没有声明一说。与C语言不同&#xff08;C语言是自顶向下读取代码&#xff09;&#…...

登录页的具体实现 (小兔鲜儿)【Vue3】

登录页 整体认识和路由配置 整体认识 登录页面的主要功能就是表单校验和登录登出业务 准备模板 <script setup></script><template><div><header class"login-header"><div class"container m-top-20"><h1 cl…...

大学如何自学嵌入式开发?

1. C语言&#xff1a;C语言是基础中的基础&#xff0c;刚开始学习不用太深入&#xff0c;一本常用的C语言的教材即可&#xff0c;注意不是当教科书看&#xff0c;而是看完一节过后&#xff0c;打开电脑把后面的习题都写出来&#xff0c;并且编译运行一遍&#xff0c;一定要动手…...

pytorch学习——线性神经网络——1线性回归

概要&#xff1a;线性神经网络是一种最简单的神经网络模型&#xff0c;它由若干个线性变换和非线性变换组成。线性变换通常表示为矩阵乘法&#xff0c;非线性变换通常是一个逐元素的非线性函数。线性神经网络通常用于解决回归和分类问题。 一.线性回归 线性回归是一种常见的机…...

00 - RAP 开发环境配置

文章目录 [1] Eclipse - ADT[2] BTP / S4HC[3] Add ABAP Env. Service[4] Conn. to BTP [1] Eclipse - ADT 关于如何安装配置&#xff0c;参见文章: Install ABAP Development Tools (ADT) and abapGit Plugin Eclipse Eclipse - ADT Eclipse - abapGit Plugin [2] BTP / S4…...

山西电力市场日前价格预测【2023-08-01】

日前价格预测 预测明日&#xff08;2023-08-01&#xff09;山西电力市场全天平均日前电价为310.15元/MWh。其中&#xff0c;最高日前电价为335.18元/MWh&#xff0c;预计出现在19: 45。最低日前电价为288.85元/MWh&#xff0c;预计出现在14: 00。 价差方向预测 1&#xff1a;实…...

QT--day5(网络聊天室、学生信息管理系统)

服务器&#xff1a; #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 解决方案 相关的依赖下载出问题多的话&#xff0c;可以关闭idea&#xff0c;重启再等等即可。 设置sbt依赖 将sbt…...

HEVC 速率控制(码控)介绍

视频编码速率控制 速率控制&#xff1a; 通过选择一系列编码参数&#xff0c;使得视频编码后的比特率满足所有需要的速率限制&#xff0c;并且使得编码失真尽量小。速率控制属于率失真优化的范畴&#xff0c;速率控制算法的重点是确定与速率相关的量化参数&#xff08;Quantiz…...

四大软件测试策略的特点和区别(单元测试、集成测试、确认测试和系统测试)

四大软件测试策略分别是单元测试、集成测试、确认测试和系统测试。 一、单元测试 单元测试也称为模块测试&#xff0c;它针对软件中的最小单元&#xff08;如函数、方法、类、模块等&#xff09;进行测试&#xff0c;以验证其是否符合预期的行为和结果。单元测试通常由开发人…...

ingress-nginx controller安装

文章目录 一、ingress-nginx controller安装环境 1.1 部署yaml1.2 镜像1.3 安装操作 一、ingress-nginx controller安装 环境 kubernetes版本&#xff1a;1.27.1操作系统&#xff1a;CentOS7.9 1.1 部署yaml deploy.yaml apiVersion: v1 kind: Namespace metadata:labels:…...

开源快速开发平台:做好数据管理,实现流程化办公!

做好数据管理&#xff0c;可以提升企业的办公协作效率&#xff0c;实现数字化转型。开源快速开发平台是深受企业喜爱的低代码开发平台&#xff0c;拥有多项典型功能&#xff0c;是可以打造自主可控快速开发平台&#xff0c;实现一对一框架定制的软件平台。在快节奏的社会中&…...

基于深度学习的裂纹图像分类研究(Matlab代码实现)

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

TypeScript入门学习汇总

1.快速入门 1.1 简介 TypeScript 是 JavaScript 的一个超集&#xff0c;支持 ECMAScript 6 标准。 TypeScript 由微软开发的自由和开源的编程语言。 TypeScript 设计目标是开发大型应用&#xff0c;它可以编译成纯 JavaScript&#xff0c;编译出来的 JavaScript 可以运行在…...

Vue3使用vxetable进行表格的编辑、删除与新增

效果图如下: vxetable4传送门 一、引入插件 package.json中加入"vxe-table": "4.0.23",终端中执行npm i导入import {VXETable, VxeTableInstance...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】&#xff1a;开启编程世界的奇妙冒险 嘿&#xff0c;各位编程小白探险家&#xff01;欢迎来到 C# 的奇幻大陆&#xff01;今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类&#xff01;别害怕&#xff0c;跟着我&#xff0c;保准让你轻松搞…...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...

解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist

现象&#xff1a; android studio报错&#xff1a; [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决&#xff1a; 不要动CMakeLists.…...

MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用

文章目录 一、背景知识&#xff1a;什么是 B-Tree 和 BTree&#xff1f; B-Tree&#xff08;平衡多路查找树&#xff09; BTree&#xff08;B-Tree 的变种&#xff09; 二、结构对比&#xff1a;一张图看懂 三、为什么 MySQL InnoDB 选择 BTree&#xff1f; 1. 范围查询更快 2…...

人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent

安全大模型训练计划&#xff1a;基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标&#xff1a;为安全大模型创建高质量、去偏、符合伦理的训练数据集&#xff0c;涵盖安全相关任务&#xff08;如有害内容检测、隐私保护、道德推理等&#xff09;。 1.1 数据收集 描…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...

大模型真的像人一样“思考”和“理解”吗?​

Yann LeCun 新研究的核心探讨&#xff1a;大语言模型&#xff08;LLM&#xff09;的“理解”和“思考”方式与人类认知的根本差异。 核心问题&#xff1a;大模型真的像人一样“思考”和“理解”吗&#xff1f; 人类的思考方式&#xff1a; 你的大脑是个超级整理师。面对海量信…...