开发一个RISC-V上的操作系统(五)—— 协作式多任务

news2024/11/16 5:48:47

目录

往期文章传送门

一、什么是多任务

二、代码实现

三、测试


往期文章传送门

开发一个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.coloradmin.cn/o/810667.html

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

相关文章

NO4 实验四:生成Web工程

1、说明 使用 mvn archetype&#xff1a;generate 命令生成 Web 工程时&#xff0c;需要使用一个专门的 archetype。这个专门生成 Web 工程骨架的 archetype 可以参照官网看到它的用法&#xff1a; 2、操作 注意&#xff1a;如果在上一个工程的目录下执行 mvn archetype&…

LeetCode208.Implement-Trie-Prefix-Tree<实现 Trie (前缀树)>

题目&#xff1a; 思路&#xff1a; tire树&#xff0c;学过&#xff0c;模板题。一种数据结构与算法的结合吧。 代码是&#xff1a; //codeclass Trie { private:bool isEnd;Trie* next[26]; public:Trie() {isEnd false;memset(next, 0, sizeof(next));}void insert(strin…

第72篇:近年HVV、红队攻防比赛中常见外围打点漏洞的分析与总结

前言 大家好&#xff0c;我是ABC_123。前一段时间我花时间花精力总结了最近两三年中&#xff0c;在攻防比赛中比较常见的Web打点漏洞类型&#xff0c;捎带着把钓鱼邮件的主题类型也总结了一下。今天分享出来&#xff0c;相信无论是对于攻击方还是防守方&#xff0c;都能从中获…

【雕爷学编程】MicroPython动手做(14)——掌控板之OLED屏幕2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

Spring学习笔记之spring概述

文章目录 Spring介绍Spring8大模块Spring特点 Spring介绍 Spring是一个轻量级的控制反转和面向切面的容器框架 Spring最初的出现是为了解决EJB臃肿的设计&#xff0c;以及难以测试等问题。 Spring为了简化开发而生&#xff0c;让程序员只需关注核心业务的实现&#xff0c;尽…

从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

ExceptionHandler的作用 ExceptionHandler是Spring框架提供的一个注解&#xff0c;用于处理应用程序中的异常。当应用程序中发生异常时&#xff0c;ExceptionHandler将优先地拦截异常并处理它&#xff0c;然后将处理结果返回到前端。该注解可用于类级别和方法级别&#xff0c;…

【RabbitMQ】golang客户端教程1——HelloWorld

一、介绍 本教程假设RabbitMQ已安装并运行在本机上的标准端口&#xff08;5672&#xff09;。如果你使用不同的主机、端口或凭据&#xff0c;则需要调整连接设置。如果你未安装RabbitMQ&#xff0c;可以浏览我上一篇文章Linux系统服务器安装RabbitMQ RabbitMQ是一个消息代理&…

STL 关于vector的细节,vector模拟实现【C++】

文章目录 vector成员变量默认成员函数构造函数拷贝构造赋值运算符重载函数析构函数 迭代器beginend size和capacityresizereserve[ ]push_backpop_backinserteraseswap vector成员变量 _start指向容器的头&#xff0c;_finish指向容器当中有效数据的下一个位置&#xff0c;_end…

Python(五十一)获取列表中的多个元素——切片操作

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

【Rust教程 | 基础系列1 | Rust初相识】Rust简介与环境配置

教程目录 前言一&#xff0c;Rust简介1&#xff0c;Rust的历史2&#xff0c;Rust的特性3&#xff0c;为什么选择Rust4&#xff0c;Rust可以做什么 二&#xff0c; Rust环境配置1&#xff0c;windows11安装2&#xff0c;Linux安装 三&#xff0c;安装IDE 前言 Rust是一种系统编…

谈谈3D打印技术

目录 1.什么是3D打印 2.3D打印与传统打印技术的不同之处 3. 3D打印带来的技术变革 1.什么是3D打印 3D打印技术&#xff0c;也称为增材制造&#xff08;Additive Manufacturing&#xff09;&#xff0c;是一种将数字模型转化为实体物体的制造方法。它通过逐层添加材料的方式&a…

一文了解MySQL中的多版本并发控制

在开始之前&#xff0c;先抛出一个问题&#xff1a;我们都知道&#xff0c;目前&#xff08;MySQL 5.6以上&#xff09;数据库已普遍使用InnoDB存储引擎&#xff0c;InnoDB相对于MyISAM存储引擎其中一个好处就是在数据库级别锁和表级别锁的基础上支持了行锁&#xff0c;还有就是…

windows环境安装elasticsearch+kibana并完成JAVA客户端查询

下载elasticsearch和kibana安装包 原文连接&#xff1a;https://juejin.cn/post/7261262567304298554 elasticsearch官网下载比较慢&#xff0c;有时还打不开&#xff0c;可以通过https://elasticsearch.cn/download/下载&#xff0c;先找到对应的版本&#xff0c;最好使用迅…

24考研数据结构-第二章:线性表

目录 第二章&#xff1a;线性表2.1线性表的定义&#xff08;逻辑结构&#xff09;2.2 线性表的基本操作&#xff08;运算&#xff09;2.3 线性表的物理/存储结构&#xff08;确定了才确定数据结构&#xff09;2.3.1 顺序表的定义2.3.1.1 静态分配2.3.1.2 动态分配2.3.1.3 mallo…

MacOS Monterey VM Install ESXi to 7 U2

一、MacOS Monterey ISO 准备 1.1 下载macOS Monterey 下载&#x1f517;链接 一定是 ISO 格式的&#xff0c;其他格式不适用&#xff1a; https://www.mediafire.com/file/4fcx0aeoehmbnmp/macOSMontereybyTechrechard.com.iso/file 1.2 将 Monterey ISO 文件上传到数据…

更简单的读取和存储对象 (Bean)

怎样才能比之前更简单的 读取和存储对象 (Bean) 呢? 答: 就两个字"使用注解", 接下来就说说如何利用注解来更简单的操作 Bean 目录 一. 前置工作 (配置扫描路径) 二. 使用注解存储 Bean 2.1 注解介绍 2.1.1 类注解存储 Bean 的默认命名规则 2.2 Controller (控…

手把手移植 simpleFOC (四):pwm 六相 篇

文章目录 系列文章目录前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 今天移植的内容&#xff0c;为定时器生在pwm&#xff0c;能按矢量数据控制电机到相应的位置 一、定时器的配置 通读了simpleFoc的代码&#xff0c;准备让定时器1生成的pwm波…

【玩转python系列】【小白必看】使用Python爬虫技术获取代理IP并保存到文件中

文章目录 前言导入依赖库打开文件准备写入数据循环爬取多个页面完整代码运行效果结束语 前言 这篇文章介绍了如何使用 Python 爬虫技术获取代理IP并保存到文件中。通过使用第三方库 requests 发送HTTP请求&#xff0c;并使用 lxml 库解析HTML&#xff0c;我们可以从多个网页上…

要单片机和RTOS有必要学习嵌入式linux吗?

学习嵌入式 Linux 是否有必要&#xff0c;取决于你的项目需求和职业发展目标。以下是一些考虑因素&#xff1a; 项目需求&#xff1a;如果你的项目需要处理复杂的网络、文件系统、多任务管理等功能&#xff0c;嵌入式 Linux 可能是更适合的选择。Linux 提供了丰富的开源软件包和…

生成对抗网络DCGAN实践笔记

在AI内容生成领域&#xff0c;有三种常见的AI模型技术&#xff1a;GAN、VAE、Diffusion。其中&#xff0c;Diffusion是较新的技术&#xff0c;相关资料较为稀缺。VAE通常更多用于压缩任务&#xff0c;而GAN由于其问世较早&#xff0c;相关的开源项目和科普文章也更加全面&#…