韦东山老师 RTOS 入门课程(二)理解任务的创建,切换过程

news2024/11/17 7:51:03

RTOS 的核心实现:保存,恢复现场

接下来开始尝试实现 RTOS。当然我们开发的时候其实不用这样做,现在尝试实现只是为了更好地理解原理。

RTOS 的核心就是刚才在研究的问题:保存和恢复现场。再追其本质,其实就是所有寄存器存入栈中了,以此保存。

从上面的代码中可以发现,临时变量都是在每个过程自己的栈中的,而每一个过程都有自己的栈,这个相当于自动保存了。只要不去破坏,就不会收到影响(比如数组越界,可能就会影响到其他函数的栈)。

1692447577446

那么我们首先要实现一个创建任务的函数。创建任务要求如下:

  1. 任务保存现场,每个任务有自己独立的一套栈;
  2. 执行任务;
  3. 恢复现场。

创建任务

我们写一个创建任务函数,传入一个函数指针(这个函数是待执行的任务),传入这个函数执行需要的参数,传入一个栈数组来存取栈。这是一种伪造现场,就是实际上栈的实现是我们自己写程序写了一个数组来存,而不是硬件实现的。

栈我们通过一个 1024 个字节的数组来伪造一个栈,可以存放 256 个寄存器数据(int 也是32位的,所以这里我们用 int 指针来表示栈指针)。

数组自己是从低地址往高地址增长的,栈指针则相反,所以拿到数组基址后要先+1024 跳转到

void create_task(task_function f, void *param, char *stack, int stack_len)
{
	int *top = stack + stack_len;//sp 指针,位于 stack[1024] 处,stack 是 char 类型数组
	
	/* 伪造现场 */
	top -= 16;//要存16个数据,如下,就是r0-r12,lr,返回地址,xPSR
	
	/* r4~r11 */
	top[0] = 0; /* r4 */
	top[1] = 0; /* r5 */
	top[2] = 0; /* r6 */
	top[3] = 0; /* r7 */
	top[4] = 0; /* r8 */
	top[5] = 0; /* r9 */
	top[6] = 0; /* r10 */
	top[7] = 0; /* r11 */
	
	/* r0~r3 */
	top[8]  = param; /* r0 */
	top[9]  = 0; /* r1 */
	top[10] = 0; /* r2 */
	top[11] = 0; /* r3 */
	
	/* r12,lr */
	top[12] = 0; /* r12 */
	top[13] = 0; /* lr */
	
	/* 返回地址 */
	top[14] = f; /* 任务入口 */
	
	/* PSR */
	top[15] = (1<<24); /* psr 使用thumb指令集 */	
	
	/* 记录栈的位置 */
	task_stacks[task_count++] = (int)top;//栈位置记录在这里,第 task_count 个数组元素的值就是指针指向的地址 int 值
}

psr 的值可以通过查表查看,比如规定是 thumb 还是 arm 的 isa 标志位。

传入参数要注意,栈参数 stack 一定要各不相同,这样才能保证各个任务栈互不干扰,

启动任务

现在每个任务都可以有一个栈了。

启动任务:把这个任务的栈中寄存器值,也就是上次停止运行时的状态恢复。

我们可以在定时器中断中切换任务,这就是时间片轮询的实现。

首先,这个任务想切换,得先创建好吧?没创建切换个锤。于是我们可以用一个变量 is_task_running 规定任务是否创建完成,定时器中断处理里如果发现没启动就先不做切换操作。

然后,如果任务都创建完成了,就在定时器中断处理中修改当前任务的 flag 值来不断切换应该运行的任务。

void SysTick_Handler(int lr_bak, int old_sp)
{
	int stack;
	int pre_task;
	int new_task;
	
	SCB_Type * SCB = (SCB_Type *)SCB_BASE_ADDR;
		
	/* clear exception status */
	SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk;

	/* 如果还没有创建好任务, 直接返回 */
	if (!is_task_running())
	{
		return;  // 表示无需切换
	}
	
	/* 启动第1个任务或者切换任务 */
	if (cur_task == -1)
	{
		/* 启动第1个任务 */
		cur_task = 0;
		
		/* 从栈里恢复寄存器 */
		/* 写汇编 */
		stack = get_stack(cur_task);
		StartTask_asm(stack, lr_bak);
		
		return ; /* 绝对不会运行到这里 */
	}
	else
	{
		/* 切换任务 */
		// 取出下一个任务
		pre_task = cur_task;
		new_task = get_next_task();
		
		if (pre_task != new_task)
		{			
			/* 保存 pre_task: 在汇编里已经保存了 */
			/* 更新sp */
			set_task_stack(pre_task, old_sp);
			
			/* 切换 new_task */
			stack = get_stack(new_task);
			cur_task = new_task;
			StartTask_asm(stack, lr_bak);
		}
	}
	
}

具体的恢复栈实现,我们需要操作汇编。我们查询栈数组找到当前应该恢复的栈的 int 值,这个值作为 sp,栈指针。

StartTask_asm PROC
				
				; 从任务的栈里把R4~R11读出来写入寄存器
				; r0 : 保存有任务的栈				
				; r1 : 保存有LR(特殊值)
				LDMIA r0!, { r4 - r11 }	;从 r0 开始读取一系列数据,边读边自增
				
				; 更新SP
				MSR MSP, R0
				;MOV SP, R0,因为栈操作只能用 msr
				
				; 触发硬件中断返回: 它会把栈里其他值读出来写入寄存器(R0,R1,R2,R3,R12,PSR)
				BX R1
				
				ENDP
				
SysTick_Handler_asm PROC

				; 在这里保存R4~R11
				STMDB sp!, { r4 - r11 }
				STMDB sp!, { lr }

				MOV R0, LR ; LR是一个特殊值
				ADD R1, SP, #4
				BL SysTick_Handler  ; 这个C函数保证不破坏R4~R11
				
				LDMIA sp!, { r0 }
				
				LDMIA sp!, { r4 - r11 }
				
				BX R0
				
				ENDP

                 END

中断函数的返回地址是跳到一个处理函数,来处理剩余的硬件要处理的寄存器.

发生中断 -> 系统自动跳到 SysTick_Handler_asm (当发生中断自动跳转的时候,此时 lr 是一个特殊值,是硬件恢复现场的地址。中断程序执行完毕返回的时候应当跳转到这里,触发硬件的恢复工作)-> 存储 R4-R11,lr -> 把 lr 和 sp+4 作为参数,开始调用 SysTick_Handler 函数 -> 获取新任务的栈空间 -> 调用 StartTask_asm,获取其数组栈中保存的数据 -> 跳转到 Task a,开始执行 -> 中断恢复。

中断恢复:当跳转到 SysTick_Handler 函数时,此时 lr = 一个特殊值(如 0xFFFFFFF9),当程序跳转到这里的时候就会执行中断的返回,触发硬件的恢复工作。

graph TB
A[发生中断]-->B[硬件压栈,如 PC,LR,SP 等]
B-->C[SysTick_Handler_asm]
C-->D[SysTick_Handler]
D-->E{当前是否有任务执行}
E--是-->F[保存当前任务现场]
F-->G
E--否-->G[获取新任务栈]
G-->H[StartTask_asm]
H-->I[恢复 R4-R11]
I-->J[跳转到硬件中断恢复现场处]

main 创建的两个任务各自的栈是这样定义的:

static char stack_a[1024] __attribute__ ((aligned (4)));;
static char stack_b[1024] __attribute__ ((aligned (4)));;

意为数组元素以4字节为长度对齐。因为字长32,存储寄存器状态也是4字节。

切换任务

保存A现场;取出B栈;切换任务。

在启动任务的基础上加任务切换保存旧任务的代码。

内容见上。

补充内容

  1. 如果需要加数组的不同状态(锁定等),则可以定义一个状态数组,我们访问数组来判断当前这个任务能不能被切换。
  2. 同步互斥:为了进行原子操作,可以暂时关闭中断避免多个任务同时操作一个变量造成错误。

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

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

相关文章

【Linux】 文件类型和访问权限

执行 ls -l &#xff08;或者&#xff1a;ll &#xff09; 指令查看文件的具体属性。 示例&#xff1a; drwxr-xr-x. 2 root root 33 10月 7 11:27 test2 看图说话&#xff1a; 下面是示例分解图 第一列的字符表示文件或目录的类型和权限。 第一个字符表示文件类型 例如&…

局域网内网管理软件有哪些功能?(局域网内网管理软件有哪些)

局域网&#xff08;LAN&#xff09;是指在一个小范围内&#xff08;如一个办公室、一个楼层或者一个大楼&#xff09;的计算机网络。随着互联网和科技的快速发展&#xff0c;局域网在企业、学校和个人生活中的应用越来越广泛。局域网内网管理软件是一种专门用于监控和控制局域网…

「天锐绿盾」——数据防泄露(智能透明加密保护)企业加密软件

天锐绿盾是一款专业的企业级加密软件&#xff0c;提供专业版、行业增强版和旗舰版&#xff0c;分别针对不同的用户需求。 PC访问地址&#xff1a; https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 天锐绿盾数据防泄密模块&#xff0c;采取系统底…

【手绘 | 日漫风】从临摹开始控笔,线条,再到人体

博主&#xff1a;_LJaXi 专栏&#xff1a; Unity | 横版游戏开发 手绘入门 控笔 排线起稿方式九宫格起稿五官起稿专业起稿 握笔姿势三角握持姿势拇指指握姿势 勾线建议注意对于人体 控笔 排线 在绘画过程中&#xff0c;可以使用铅笔控制笔触的方向、压力和角度&#xff0c;以获…

tf卡损坏怎么修复恢复?

TF卡是一种极其微小的数据储存卡&#xff0c;常见于手机、行车记录仪、微型相机中。因为TF卡不具备读写保护功能&#xff0c;所以一旦发生损坏就会非常麻烦。今天小编就给大家介绍一下&#xff0c;TF卡突然损坏什么原因&#xff0c;TF卡损坏怎么办一招帮你修复。 一、TF卡突然损…

upload-labs靶场通关

文章目录 Pass-01 前端检测&#xff08;JS检测&#xff09;1.1 原理分析1.2 具体问题具体分析1.3 实验 Pass-02 后端检测&#xff08;MIME检测&#xff09;2.1 原理分析2.2 具体问题具体分析2.3 实验 Pass-03 后端检测&#xff08;黑名单绕过&#xff0c;特殊后缀名&#xff09…

DataX和dataX-web 集群部署及使用

&#x1f4d1; DataX和dataX-web 集群部署及使用 一 . 安装前准备 DataX 是一个异构数据源离线同步工具&#xff0c;致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。 DataX 采用 框架 插件 的模式…

【Proteus仿真】【STM32单片机】智能饮水机

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用OLED液晶、按键、语音播放模块、DS18B20温度传感器、加热器和水泵等。 主要功能&#xff1a; 系统运行后&#xff0c;OLED显示温度、温度阈值&a…

【Linux】 ls命令使用

ls&#xff08;英文全拼&#xff1a; list directory contents&#xff09;命令用于显示指定工作目录下之内容&#xff08;列出目前工作目录所含的文件及子目录)。 ls命令 -Linux手册页 著者 由Richard M.Stallman和David MacKenzie撰写。 语法 ls [-alrtAFR] [name...] ls命…

众佰诚:抖音开网店新手怎么做才能做起来

抖音作为国内最热门的短视频平台&#xff0c;其商业价值也日渐凸显。许多商家和个体经营者开始在抖音上开设网店&#xff0c;以此为新的销售渠道。那么&#xff0c;对于新手来说&#xff0c;如何才能在抖音上成功运营网店呢? 首先&#xff0c;明确经营定位。每个电商平台都有其…

云服务器可以做什么?分享阿里云服务器的十种玩法

阿里云服务器可以干什么&#xff1f;服务器的用途有很多&#xff0c;常见的有网站、小程序、视频服务器、手机APP等&#xff0c;例如微信基于腾讯云服务器&#xff0c;淘宝基于阿里云服务器&#xff0c;阿里云百科aliyunbaike.com来说下阿里云服务器十大用途&#xff1a; 目录…

理解C++四种强制类型转换

理解C强制类型转换 文章目录 理解C强制类型转换理解C强制转换运算符1 static_cast1.1. static_cast用于内置数据类型之间的转换1.2 用于指针之间的转换1.3 用于基类与派生类之间的转换 2. const_cast2.1示例12.2 示例2——this指针 3.reinterpret_cast3.1 示例1 4.dynamic_cast…

slamplay:用C++实现的SLAM工具集

0. 项目简介 slamplay 是一个功能强大的工具集合&#xff0c;可用于开始使用 C 来玩和试验 SLAM。这是一项正在进行的工作。它在单个 cmake 框架中安装并提供一些最重要的功能 后端框架&#xff08;g2o、gtsam、ceres、se-sync 等&#xff09;、 前端工具&#xff08;opencv、…

C++那些让我们偷懒的函数

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析&#xff08;3&#xff09; 前言 让我们康康c为我们提供的懒人函数吧&#xff0c;后续会…

tcpdump(一)基础理论知识

一 抓包分析技术初探 说明&#xff1a; 本篇章跟tcp/ip的知识没有关系,只是讲解tcpdump工具背景补充&#xff1a; 抓包是做报文分析的第一步敬畏心&#xff1a; 隔行如隔山,不要想当然 ① 背景 ② 抓包技术名词 1、捋顺这些技术的来龙去脉甚至八卦;2、这样我们在后续课程…

Stable Signature - 为开源生成式AI 创建的图像 添加水印的新方法

文章目录 关于 Stable Signature 关于 Stable Signature 一种为开源生成式AI创建的图像添加水印的新方法 Stable Signature: A new method for watermarking images created by open source generative AI https://ai.meta.com/blog/stable-signature-watermarking-generativ…

Redis三种模式(主从复制,哨兵,集群)

Redis三种模式&#xff08;主从复制&#xff0c;哨兵&#xff0c;集群&#xff09; 一、主从复制1.1、主从复制概述1.2、 Redis主从复制流程1.3、 Redis主从复制作用1.4 、部署Redis 主从复制 二、Redis 哨兵模式2.1、哨兵模式的原理2.2、哨兵模式的作用2.3、哨兵的结构组成2.4…

【Spring知识点介绍 | 第二篇】什么是AOP

前言&#xff1a; 在本文中&#xff0c;我们将介绍AOP的基本概念、原理和核心组件。我们将探讨AOP的作用、优势以及如何在实际应用中使用AOP来提升软件开发的效率和质量。无论是新手还是资深开发人员&#xff0c;都将受益于对AOP的理解和应用。 希望本文能够帮助读者对AOP有一…

MineDojo - “我的世界” 基础上构建的 具身智能体仿真组件

关于 MineDojo Building Open-Ended Embodied Agents with Internet-Scale Knowledge github : https://github.com/MineDojo/MineDojo论文&#xff1a;MineDojo: Building Open-Ended Embodied Agents with Internet-Scale Knowledge https://arxiv.org/abs/2206.08853 Min…

Linux安装 spark 教程详解

目录 一 准备安装包 二 安装 scala 三 修改配置文件 1&#xff09;修改 workers 文件 2&#xff09;修改 spark-env.sh文件 四 进入 spark 交互式平台 一 准备安装包 可以自行去 spark 官网下载想要的版本 这里准备了 spark3.1.2的网盘资源 链接: https://pan.baidu.com…