开发一个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 base
        sw 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 base
        lw 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 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
        sw      t6, 120(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

.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 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);
}

其中,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 交流~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】类和对象(下)

1、初始化列表 初始化列表&#xff1a;以一个冒号开始&#xff0c;接着是一个以逗号分隔的数据成员列表&#xff0c;每个"成员变量"后面跟一个放在括号中的初始值或表达式。 class Date { public:Date(int year, int month, int day): _year(year), _month(month), _…

OpenLayers实战进阶专栏目录,OpenLayers实战案例,OpenLayers6实战教程

前言 本篇作为OpenLayers实战进阶教程的目录&#xff0c;用于整理汇总专栏所有文章&#xff0c;方便查找。 OpenLayers是前端最流行的JS二维地图引擎之一。 反馈建议 OpenLayers系列-交流专区&#xff0c;建议和问题反馈 Openlayers实战进阶 Openlayers实战&#xff0c;O…

全志F1C200S嵌入式驱动开发(lcd屏幕驱动)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 lcd RGB屏幕作为比较经济、实用的显示工具,在实际场景中使用较多。它的信号来说,一般也比较简单,除了常规的数据信号,剩下来就是行同步、场同步、数据使能和时钟信号了。数据信…

Pytorch深度学习-----神经网络之池化层用法详解及其最大池化的使用

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…

Python批量将Excel内指定列的数据向上移动一行

本文介绍基于Python语言&#xff0c;针对一个文件夹下大量的Excel表格文件&#xff0c;对其中的每一个文件加以操作——将其中指定的若干列的数据部分都向上移动一行&#xff0c;并将所有操作完毕的Excel表格文件中的数据加以合并&#xff0c;生成一个新的Excel文件的方法。 首…

虚拟机(VMware)安装Linux(Ubuntu)安装教程

清华大学开源网站镜像站网址&#xff1a;清华大学开源软件镜像站 | Tsinghua Open Source Mirror 进入之后在搜索框中搜索“ubuntu” 直接点击箭头所指的蓝色字体“ubuntu-20.04.1-desktop-amd64.iso”即可下载

一起学算法(位运算篇)

1.位运算 1.二进制数值表示 在计算机中&#xff0c;我们可以用单纯的0和1来表示数字&#xff0c;一般不产生歧义&#xff0c;我们会在数字的右下角写上它的进制&#xff0c;例如&#xff1a;1010&#xff08;10&#xff09;其表示的是1010&#xff0c;1010&#xff08;2&#…

Windows下安装HBase

Windows下安装HBase 一、HBase简介二、HBase下载安装包三、环境准备3.1、 JDK的安装3.2、 Hadoop的安装 四、HBase安装4.1、压缩包解压为文件夹4.2、配置环境变量4.3、%HBASE_HOME%目录下新建临时文件夹4.4、修改配置文件 hbase-env.cmd4.4.1、配置JAVA环境4.4.2、set HBASE_MA…

【css】背景图片附着

属性&#xff1a;background-attachment 属性指定背景图像是应该滚动还是固定的&#xff08;不会随页面的其余部分一起滚动&#xff09;。 background-attachment: fixed&#xff1a;为固定&#xff1b; background-attachment: scroll为滚动 代码&#xff1a; <!DOCTYPE h…

【1.4】Java微服务:服务注册和调用(Eureka和Ribbon实现)

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f49e;当前专栏&#xff1a; 微服务 ✨特色专栏&#xff1a; 知识分享 &#x…

企业数据,大语言模型和矢量数据库

随着ChatGPT的推出&#xff0c;通用人工智能的时代缓缓拉开序幕。我们第一次看到市场在追求人工智能开发者&#xff0c;而不是以往的开发者寻找市场。每一个企业都有大量的数据&#xff0c;私有的用户数据&#xff0c;自己积累的行业数据&#xff0c;产品数据&#xff0c;生产线…

【Linux进程篇】进程概念(1)

【Linux进程篇】进程概念&#xff08;1&#xff09; 目录 【Linux进程篇】进程概念&#xff08;1&#xff09;进程基本概念描述进程-PCBtask_struct-PCB的一种task_ struct内容分类 组织进程查看进程通过系统调用获取进程标示符通过系统调用创建进程——fork初识 作者&#xff…

2023软件设计师中级备考经验分享(文中有资料链接分享)

先摊结论吧&#xff0c;软考中级设计师备考只是备考半个月&#xff08;期间还摆烂了几天&#xff09;&#xff0c;然而成绩如下&#xff1a; 我自己都没想到会这么好的成绩。。。 上午题&#xff1a;推荐把软考通APP里的历年真题刷3-4遍&#xff0c;直接刷真题&#xff0c;然后…

分享一些精选的开源框架与代码!

今天主要是收集并精选了一些自己所了解和学习过的优秀的嵌入式开源框架代码和项目&#xff0c;不太了解的就不推荐给大家了&#xff0c;因为开源的东西实在是太多了&#xff0c;鱼龙混杂&#xff0c;所以取其精华去其糟粕是迫在眉睫的大事~ 当然也不要总是沉浸在开源的东西之中…

光伏、储能一体化监控及运维解决方案

前言 今年以来&#xff0c;在政策利好推动下光伏、风力发电、电化学储能及抽水蓄能等新能源行业发展迅速&#xff0c;装机容量均大幅度增长&#xff0c;新能源发电已经成为新型电力系统重要的组成部分&#xff0c;同时这也导致新型电力系统比传统的电力系统更为复杂&#xff0…

基于多任务学习卷积神经网络的皮肤损伤联合分割与分类

文章目录 Joint segmentation and classification of skin lesions via a multi-task learning convolutional neural network摘要本文方法实验结果 Joint segmentation and classification of skin lesions via a multi-task learning convolutional neural network 摘要 在…

idea 里 controller service impl mapper xml 切换跳转快捷键

首先在controller层&#xff0c;对着接口点方法的方法上按着ctrl和鼠标左键&#xff0c;你会进入service层。 对着方法ctrlaltb不按鼠标&#xff0c;你会进入impl层。service层的方法上按ctrl和鼠标左键会回到controller&#xff0c;ctrlaltb不按鼠标也会进入到impl层,impl上的…

vue echart3个饼图

概览&#xff1a;根据UI设计需要做3个饼图且之间有关联&#xff0c;并且处理后端返回的数据。 参考链接&#xff1a; echart 官网的一个案例&#xff0c;3个饼图 实现思路&#xff1a; 根据案例&#xff0c;把数据处理成对应的。 参考代码&#xff1a; 1.处理后端数据&am…

谐音标注外语发音的学习方式,早该终结了!

语言学习的热潮席卷全国&#xff0c;在多数80、90后记忆里尤为深刻&#xff0c;部分对外语过敏的同学&#xff0c;就像溺水的鱼&#xff0c;使劲扑棱也无济于事&#xff0c;难受但是死不了&#xff0c;在懵懂的年纪就被“摧残”了整个青春。 记忆中遇到记不住读音的单词&#x…

Python模块psycopg2连接postgresql

目录 1. 基础语法 2. 基础用法 3. 多条SQL 4. 事务SQL 1. 基础语法 语法 psycopg2.connect(dsn #指定连接参数。可以使用参数形式或 DSN 形式指定。host #指定连接数据库的主机名。dbname #指定数据库名。user #指定连接数据库使用的用户名。…