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

一、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
  • 39
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值