RTOS实战项目之实现多任务系统

news2024/9/23 15:29:21

文章目录

    • 一、RTOS引入
    • 二、任务的引入
      • 2.1 任务的定义
      • 2.2 理解C函数的内部机制
      • 2.3 ARM架构
      • 2.4 汇编指令
      • 2.5 怎么保存函数的现场
        • ①要保存什么
        • ②保存现场的几种场景
    • 三、FreeRTOS中怎么创建任务
    • 四、通过链表深入理解调度机制
      • 4.1 优先级与状态
      • 4.2 调度方法
    • 五、创建任务—伪造现场
      • 5.1 创建任务
        • 5.1.1 定义任务栈
        • 5.1.2 定义任务函数
        • 5.1.3 定义任务控制块
      • 5.2 启动任务
      • 5.3 切换任务
    • 六、多任务系统实战分析

一、RTOS引入

妈妈要一边给小孩喂饭,一边加班跟同事微信交流,怎么办?
在这里插入图片描述

用人类生活的示例来比喻单片机,妈妈要一边给小孩喂饭,一边加班跟同事微信交流,怎么办?

对于单线条的人,不能分心、不能同时做事,她只能这样做:

  • 给小孩喂一口饭
  • 瞄一眼电脑,有信息就去回复
  • 再回来给小孩喂一口饭
  • 如果小孩吃这口饭太慢,她回复同事的信息也就慢了,被同事催:你半天都不回我?
  • 如果回复同事的信息要写一大堆,小孩就着急得大哭起来。

这种做法,在软件开发上就是一般的单片机开发,没有用操作系统。

对于眼明手快的人,她可以一心多用,她这样做:

  • 左手拿勺子,给小孩喂饭
  • 右手敲键盘,回复同事
  • 两不耽误,小孩“以为”妈妈在专心喂饭,同事“以为”她在专心聊天
  • 但是脑子只有一个啊,虽然说“一心多用”,但是谁能同时思考两件事?
  • 只是她反应快,上一秒钟在考虑夹哪个菜给小孩,下一秒钟考虑给同事回复什么信息

这种做法,在软件开发上就是使用操作系统,在单片机里叫做使用RTOS。

程序简单示例:

在软件开发上就是使用操作系统,在单片机里叫做使用RTOS。RTOS的意思是:Real-time operating system,实时操作系统。

// 经典单片机程序
void main()
{
	while (1)
    {
        喂一口饭();
        回一个信息();
    }
}
------------------------------------------------------
// RTOS程序    
喂饭()
{
    while (1)
    {
        喂一口饭();
    }
}

回信息()
{
    while (1)
    {
        回一个信息();
    }
}

void main()
{
    create_task(喂饭);
    create_task(回信息);
    start_scheduler();
    while (1)
    {
        sleep();
    }
}

在这里插入图片描述

二、任务的引入

2.1 任务的定义

从这个角度想:函数被暂停时,我们怎么保存它、保存什么?怎么恢复它、恢复什么?

  • 任务是一个函数吗?
    • 函数保存在Flash上
    • Flash上的函数无需再次保存
    • 所以:任务不仅仅是函数
  • 任务时变量吗?
    • 单纯通过变量无法做事
    • 所以:任务不仅仅是变量
  • 任务时一个运行中的函数
    • 运行中:可以曾经运行,现在暂停了,但是未退出
    • 怎么描述一个运行中的函数
    • 假设在某一个瞬间时间停止,你怎么记录这个运行中的函数
  • 要理解任务的本质,需要理解ARM架构、汇编

2.2 理解C函数的内部机制

main函数中代码和反汇编程序:
在这里插入图片描述
结合反汇编程序,分析此段代码可以理解C函数的内部机制。

2.3 ARM架构

什么是程序:指令与数据的集合。
在这里插入图片描述
ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:

  • 对内存只有读、写指令
  • 对于数据的运算是在CPU内部实现
  • 使用RISC指令的CPU复杂度小一点,易于设计

比如对于a=a+b这样的算式,需要经过下面4个步骤才可以实现:
在这里插入图片描述
细看这几个步骤,有些疑问:

  • 读a,那么a的值读出来后保存在CPU里面哪里?
  • 读b,那么b的值读出来后保存在CPU里面哪里?
  • a+b的结果又保存在哪里?

我们需要深入ARM处理器的内部。简单概括如下,我们先忽略各种CPU模式(系统模式、用户模式等等)。
在这里插入图片描述
CPU运行时,先去Flash上取得指令,再执行指令:

  • 把内存a的值读入CPU寄存器R0
  • 把内存b的值读入CPU寄存器R1
  • 把R0、R1累加,存入R0
  • 把R0的值写入内存a

2.4 汇编指令

只需要记住5条汇编指令:

  • 读内存:Load,LDR
  • 写内存:Store,STR
  • 加法:ADD
  • 入栈:PUSH,实质上就是写内存STR
  • 出栈:POP,实质上就是读内存LDR

①要读内存:读内存哪个地址?读到的数据保存在哪里?读多少字节?

  • LDR R0, [R1, #0x00]
    • 源地址:R1+0x00,注意:不是读R1,是把R1的值当做内存的地址
    • 目的:R0,CPU的寄存器
    • 长度:4字节,LDR指令就是读4字节,LDRH是读2字节,LDRB是读1字节

②要写内存:写内存哪个地址?从哪里得到数据?写多少字节?

  • STR R0, [R1, #0x00]
    • 目的地址:R1+0x00,注意:不是写R1,是把R1的值当做内存的地址
    • 源:R0,CPU的寄存器
    • 长度:4字节,STR指令就是读4字节,STRH是读2字节,STRB是读1字节

③入栈:把CPU的寄存器的值,写到内存上

  • PUSH {R3, LR}

    • 源:CPU的寄存器R3、LR的值
    • 目的:内存,内存哪里?使用CPU的SP寄存器指定内存地址
    • 长度:大括号里所有寄存器的数据长度,每个寄存器4字节
    • 注意:低编号的寄存器,保存在内存的低地址处
    • 执行结果如下
      在这里插入图片描述

④出栈:把内存中的数值,写到CPU的寄存器

  • POP {R3, PC}
    • 源:内存,内存哪里?使用CPU的SP寄存器指定内存地址
    • 目的:CPU的寄存器R3、PC的值
    • 长度:大括号里所有寄存器的数据长度,每个寄存器4字节
    • 注意:内存的低地址处的数据,写到CPU低编号的寄存器
    • 执行结果如下
      在这里插入图片描述

⑤其他知识:

  • CPU内部有R0、R1、……、R15共16个寄存器
  • 某些寄存器有特殊作用
    • R13,别名SP,栈寄存器,保存着栈的地址
    • R14,别名LR,返回地址,保存着函数的返回地址
    • R15,别名PC,程序计数器,也就是当期程序运行到哪了

程序运行分析:
在这里插入图片描述

2.5 怎么保存函数的现场

在这里插入图片描述

①要保存什么
  • 程序运行到了哪里?

    PC寄存器的值,R2的值:我辛辛苦苦从内存里读到的值放在R2里,函数继续运行时,R2的值不要被破坏了。

  • 只需要保存R2吗?

    答:切换任务的话,所有的寄存器都要保存。

  • 保存在哪里?

    答:内存的栈里。

②保存现场的几种场景
  • 函数调用

  • 中断处理

  • 任务切换

三、FreeRTOS中怎么创建任务

详细请看博客:FreeRTOS——任务通知(基于百问网FreeRTOS教学视频)-CSDN博客

四、通过链表深入理解调度机制

  • 可抢占:高优先级的任务先运行

  • 时间片轮转:同优先级的任务轮流执行

  • 空闲任务礼让:如果有同是优先级0的其他就绪任务,空闲任务主动放弃一次运行机会

4.1 优先级与状态

  • 优先级不同
    • 高优先级的任务,优先执行,可以抢占低优先级的任务
    • 高优先级的任务不停止,低优先级的任务永远无法执行
    • 同等优先级的任务,轮流执行:时间片轮转
  • 状态
    • 运行态:running
    • 就绪态:ready
    • 阻塞:blocked,等待某件事(时间、事件)
    • 暂停:suspend,休息去了
  • 怎么管理?
    • 怎么取出要运行的任务?
      • 找到最高优先级的运行态、就绪态任务,运行它
      • 如果大家平级,轮流执行:排队,链表前面的先运行,运行1个tick后乖乖地去链表尾部排队

4.2 调度方法

  • 谁进行调度?

    • TICK中断!

在这里插入图片描述

中断处理流程

在这里插入图片描述

五、创建任务—伪造现场

FreeRTOS中任务的操作:创建任务、启动任务 、切换任务

5.1 创建任务

5.1.1 定义任务栈
#define TASK_COUNT 32
static int task_stacks[TASK_COUNT];

在这里插入图片描述

在多任务系统中,每个任务都是独立的,互不干扰的,所以要为每个任务都分配独立的栈空间,这个栈空 间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于RAM中。

5.1.2 定义任务函数

为任务函数伪造现场,如图所示:
在这里插入图片描述
任务是一个独立的函数,函数主体无限循环且不能返回。

示例代码:

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

void task_a(void *param)
{
	char c = (char)param;
	while (1)
	{
		putchar(c);
	}
}

void task_c(void *param)
{
	int i;
	int sum = 0;
	
	for (i = 0; i <= 100; i++)
		sum += i;
	
	while (1)
	{
		put_s_hex("sum = ", sum);
	}
}


int mymain()
{
	create_task(task_a, 'a', stack_a, 1024);
	create_task(task_a, 'b', stack_b, 1024);
	create_task(task_c, 0, stack_c, 1024);
	start_task();
	return 0;
}

5.1.3 定义任务控制块

在多任务系统中,任务的执行是由系统调度的。系统为了顺利 的调度任务,为每个任务都额外定义了一个任务控制块,这个任务控制块就相当于任务的身份证,里面存有任务的 所有信息,比如任务的栈指针,任务名称,任务的形参等。
在这里插入图片描述

有了这个任务控制块之后,以后系统对任务的全部操作都 可以通过这个任务控制块来实现。
在这里插入图片描述

5.2 启动任务

方法:从栈里恢复到寄存器(读内存),设置标识位,让启动任务时修改标识位的值.
在这里插入图片描述

设置标识位代码示例:

static int task_running = 0;
int cur_task = -1;

void start_task(void)
{
	task_running = 1;
	while (1);
}

int is_task_running(void)
{
	return task_running;
}

在中断中启动任务代码示例:

* 如果还没有创建好任务, 直接返回 */
	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 ; /* 绝对不会运行到这里 */
/* 其余代码省略 */

启动任务执行过程解释:

在寄存器中读取内存值,读取到的值写入其它寄存器需要触发中断返回(BX R1)。
在这里插入图片描述

5.3 切换任务

任务切换就是在就绪列表中寻找优先级最高的就绪任务,然后去执行该任务。

在中断函数中的示例代码:

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

任务切换的反汇编代码示例:
在这里插入图片描述

FreeRTOS 中的任务切换机制是系统实时性的保证,也是实现多任务并行执行的基础。在进入和退出任务切换的关键代码段时,FreeRTOS 管理着临界区和中断优先级,以确保任务切换过程不受干扰,并且尽量减少关中断的时间,以降低对系统实时性的影响。在Cortex-M3这种具有独特硬件特性的平台上,FreeRTOS 利用这些硬件能力(如 PendSV 异常)来实现更高效、更低延迟的任务切换操作。

六、多任务系统实战分析

优点:多任务系统可以并发执行或者并行处理,使得在嵌入式开发中能够提升CPU利用率,多任务系统可以增强系统的响应性,通过抢占式或者轮询方式确保每个任务可以获得执行的机会。能够显著提升计算机系统的整体性能和效率。

在项目中的运用如:

  1. 智能家居对用户输入、设备状态监控、无线通信中的多个任务并行处理;
  2. 在自动化生产线中,FreeRTOS 可以管理传感器数据采集、马达控制、安全监控等多个任务,提高系统的响应速度和可靠性。

参考资料来源:

git clone https://gitee.com/jiang-jiawei123/doc_and_source_for_livestream.git

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

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

相关文章

Python青少年简明教程:赋值语句

Python青少年简明教程&#xff1a;赋值语句 变量赋值是指将一个值分配给变量的过程。Python 支持多种形式的赋值&#xff08;assignment&#xff09;&#xff0c;包括基本赋值、多重赋值、链式赋值和解包赋值等。 为了深入理解Python赋值语句机制&#xff0c;先了解一下id()函数…

[Meachines] [Easy] Legacy nmap 漏洞扫描脚本深度发现+MS08-067

信息收集 IP AddressOpening Ports10.10.10.4TCP:135,139,445 $ nmap -p- 10.10.10.4 --min-rate 1000 -sC -sV -Pn PORT STATE SERVICE VERSION 135/tcp open msrpc Microsoft Windows RPC 139/tcp open netbios-ssn Microsoft Windows n…

战略合作篇白皮书:深度革新,赋能企业跃迁

01背景 企业数字化转型已经成为当今商业环境中不可避免的趋势&#xff0c;主要有以下几个原因&#xff1a; 技术发展&#xff1a;随着信息技术的迅猛发展和普及&#xff0c;企业面临着数字化转型的迫切需求。云计算、大数据、人工智能等技术正在改变商业模式和运营方式&#xf…

【C++第十四章】进阶模板

【C第十四章】进阶模板 非类型模板参数&#x1f9d0; 我们创建一个类&#xff0c;可以用模板开一个大小的为N的数组&#xff0c;这样优于用宏来定义N&#xff0c;因为可以在创建对象时可以根据需求更改数组大小。我们称在模板定义中使用的不依赖于模板类型的参数为非类型模板参…

当前A股平均市盈率

再写一篇【不务正业】的 2023-08-23A股平均市盈率 来自乐咕乐股网 当前A股市盈率是否为低点&#xff1f; 不言而喻 ‌当前A股市场的市盈率确实处于相对低位。‌ 根据东方财富Choice最新数据显示数据&#xff0c;截至2024年8月23日&#xff0c;全A市盈率为13.06倍&#xff0c;…

(贪心) LeetCode 45. 跳跃游戏 II

原题链接 一. 题目描述 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 < j < nums[i] i j < n …

《加油吧少年》热播 编剧蔡璧鸿:创作需要对幽默保持高度关注

近日&#xff0c;校园剧《加油吧少年》正在热播中&#xff0c;该剧以学渣视角&#xff0c;讲述他在高中校园与学霸&#xff0c;女神&#xff0c;死党一起学习&#xff0c;成长和努力拼搏的故事&#xff0c;《加油吧少年》自播出后&#xff0c;便以轻松幽默&#xff0c;诙谐搞笑…

2024口碑最好的四大游泳耳机大揭秘,游泳教练全方位测评分析!

游泳&#xff0c;作为一种全身性的锻炼方式&#xff0c;越来越受到人们的青睐。在水下&#xff0c;人们渴望能够聆听到美妙的音乐&#xff0c;让游泳变得更加有趣和放松。游泳耳机的出现&#xff0c;正是为了满足这一需求。它们不仅能够提供防水、防汗的功能&#xff0c;还能在…

软件测试——自动化测试selenium常用函数

目录 元素的定位cssSelectorxpathxpath语法&#xff1a; 元素定位函数 操作测试对象窗口切换窗口窗口设置大小窗口切换屏幕截图关闭窗口 等待强制等待隐式等待显示等待 浏览器导航弹窗警告弹窗确认弹窗提示弹窗 文件上传浏览器参数设置 元素的定位 web⾃动化测试的操作核⼼是能…

RFID光触发标签在多行业的应用与效益差异

在当今数字化和智能化的浪潮下&#xff0c;RFID技术已成为众多行业优化运营、提升竞争力的关键手段。RFID光触发标签作为这一技术的创新成果&#xff0c;以其独特的性能特点&#xff0c;正逐渐在各个领域发挥着重要作用。 一、RFID光触发标签的特点与参数 &#xff08;一&…

优惠券秒杀项目

一、添加优惠券的同时&#xff0c;将优惠券信息&#xff0c;以及用户列表放到redis中 Override Transactional public void addSeckillVoucher(Voucher voucher) {// 保存优惠券save(voucher);// 保存秒杀信息SeckillVoucher seckillVoucher new SeckillVoucher();seckillVou…

linux dig域名DNS 查询与iptables域名ip访问流量限制;PTR 反向解析从 IP 地址到域名的映射

一、域名 dns查询 在 Linux 系统中&#xff0c;你可以使用多种工具和技术来进行 DNS 查询和 IP 限制。以下是一些常用的方法和工具&#xff1a; DNS 查询 dig 命令&#xff1a; dig 是一个强大的命令行工具&#xff0c;用于查询 DNS 信息。 dig example.com你可以指定查询类型…

【TCP】核心机制:滑动窗口、流量控制和拥塞控制

文章目录 滑动窗口窗口滑动滑动窗口丢包 流量控制拥塞控制窗口大小变化过程 滑动窗口 有一类算法题&#xff0c;就是通过滑动窗口的思想来解决的&#xff0c;算法中的“滑动窗口”借鉴自 TCP 的滑动窗口 TCP 是要保证可靠传输的>代价&#xff0c;降低了传输的效率&#xf…

【机器学习】3. 欧式距离,曼哈顿距离,Minkowski距离,加权欧式距离

Euclidean - L2 norm L2范数 D ( A , B ) ( a 1 − b 1 ) 2 ( a 2 − b 2 ) 2 . . . D(A,B) \sqrt{(a_1-b_1)^2(a_2-b_2)^2 ...} D(A,B)(a1​−b1​)2(a2​−b2​)2... ​ Manhattan D ( A , B ) ∣ a 1 − b 1 ∣ ∣ a 2 − b 2 ∣ . . . D(A,B) \sqrt{|a_1-b_1||a_…

全网最简单的Java设计模式【九】策略模式-实战中最常用的设计模式之一

策略模式是一种行为设计模式&#xff0c;它允许你定义一系列的算法&#xff0c;把它们一个个封装起来&#xff0c;并且使它们可以相互替换。该模式让算法的变化独立于使用算法的客户。在实际开发中&#xff0c;策略模式可以帮助我们减少大量的 if-else 或 switch 条件判断语句&…

产品的需求分析

一、需求 1.需求概念 用户的预期与现实存在在差异 用户所期望的明确的解决方案 2.需求的常用形式 提问题 提目的 提方案 3.需求的来源 外部需求&#xff1a;用户、市场、其他竞品 内部需求&#xff1a;业务部门、产品经理 4.需求测试方法 定性方式&#xff1a;逻辑…

【生日视频制作】江边夜景游轮观光船霓虹灯AE模板修改文字软件生成器教程特效素材【AE模板】

游轮观光船生日视频制作教程AE模板修改文字特效软件生成器素材 怎么如何做的【生日视频制作】江边夜景游轮观光船霓虹灯AE模板修改文字软件生成器教程特效素材【AE模板】 生日视频制作步骤&#xff1a; 安装AE软件下载AE模板把AE模板导入AE软件修改图片或文字渲染出视频

鸿蒙(API 12 Beta3版)【使用Image完成图片接收器】图片开发指导依赖JS对象

图像接收类&#xff0c;用于获取组件surface id&#xff0c;接收最新的图片和读取下一张图片&#xff0c;以及释放ImageReceiver实例。 开发步骤 添加依赖 在进行应用开发之前&#xff0c;开发者需要打开native工程的src/main/cpp/CMakeLists.txt&#xff0c;在target_link_…

【Qt】Qt系统 | Qt事件| 鼠标事件

文章目录 鼠标事件鼠标点击事件鼠标释放事件鼠标双击事件鼠标移动事件 滚轮事件 在 Qt 中&#xff0c;鼠标事件是用 QMouseEvent 实现的。当在窗口中按下鼠标或者移动鼠标时&#xff0c;都会产生鼠标事件 鼠标事件 鼠标点击事件 鼠标按下时通过 虚函数 mousePressEvent() 来…

系统编程—进程

一、进程的概念 1.程序与进程的区别 程序:编译后产生的&#xff0c;格式为ELF的&#xff0c;存储于硬盘的文件。可以通过 readelf -s [文件名] 查看文件信息 …