📣RTOS原理与实现02:基本任务切换
2023-6-2
| 2024-7-6
0  |  阅读时长 0 分钟
type
status
password
date
slug
summary
category
URL
tags
icon

1. 任务定义与切换原理

1.1 任务是什么

1.1.1 任务的外观

任务的外观:一个永不返回的函数
notion image
💡
说明:使用void *类型形参,确保可以传入任意类型的参数

1.1.2 任务的内在

任务的内在:一个函数的执行
notion image
  1. 代码段和数据区由编译器在编译代码时自动分配与控制
  1. 堆的分配和使用由程序员控制
  1. C代码中一般不会显式使用栈,将由编译器完成;在汇编代码中,程序员可以设置栈的位置并使用
  1. C代码也不会显示使用寄存器,也是由编译器完成
个人:代码区 + 数据区 + 栈 + 堆可以理解为任务的实体 + 运行环境

1.2 任务切换原理

1.2.1 任务切换的本质

任务切换的本质:保存前一任务(prev)的当前运行状态,恢复后一任务(next)之前的运行状态,并切换到该任务运行
notion image

1.2.2 要保存哪些任务运行状态

notion image
  1. 代码区 & 数据区:由编译器自动分配,各个任务相互独立,并不冲突
  1. 堆:由程序员定义并使用,对堆操作的句柄保存在栈中。不使用
  1. 栈:内核硬件只支持两个栈空间(MSP & PSP),而不同任务的栈不能共用,所以需要给每个任务设置自己的栈空间
  1. 寄存器:编译器会在某些时刻将寄存器保存到栈中(e.g. 函数调用,异常处理),如果其他寄存器也会被破坏,则需要程序员自己保存
  1. 其他需要保存的状态数据

1.2.3 任务运行状态保存方案

解决方案:为每个任务分配独立的栈,用于保存该任务的所有状态数据
notion image
在进行任务切换时,
  1. 将前一(prev)任务的当前状态保存在该任务的栈中
  1. 找到下一(next)任务的栈,然后从该栈中恢复任务状态

1.3 设计实现

1.3.1 类型定义

  1. 栈类型:Cortex-M中对栈操作的单位为4B,所以使用uint32_t类型
    1. 任务类型:每个任务的栈地址,均保存在任务结构体中,即TCB中

      1.3.2 任务定义与初始化

      2. 任务切换的实现

      2.1 设计目标

      notion image
      💡
      说明:在本节实现中,任务切换由任务主动调用tTaskSched函数实现

      2.2 任务切换原理

      2.2.1 如何启动初始任务

      1. 上电启动后,系统进入特权级线程模式(线程模式 + MSP),而任务一般运行在用户级线程模式(线程模式 + PSP)
      1. 设置任务初始栈:也就是设置初始任务的内核寄存器值、其他状态数据等。这些初始栈代表了程序运行的初始状态。
      1. 触发任务切换,使用任务初始栈内容填充任务运行状态。
        1. notion image
      💡
      说明2:在设置任务初始栈时,会将任务函数入口地址(task entry)填入PC寄存器对应的位置,这样在执行任务切换后,CPU即可运行任务函数

      2.2.2 如何实现任务切换

      1. 将当前任务运行状态保存到当前任务栈中。说明:此处的任务运行状态保存分为2部分,
        1. 硬件自动保存部分(进入pendSV异常时硬件自动保存),硬件保存的数据也是保存在系统当前使用的栈中,也就是当前任务的栈中。
          1. 💡
            M3处理器会自动保存xPSR、PC、LR、R12、R0~R3寄存器
        2. 程序员自行保存部分。需要通过软件保存R4~R11寄存器
      1. 找到下一任务,并用下一任务的任务栈恢复该任务运行环境。与任务状态的保存类似,任务状态的恢复也分为
        1. 硬件自动恢复部分
        2. 程序员自行恢复部分
      任务切换原理图
      任务切换原理图
      💡
      在任务切换函数中,只需要完成程序员自行恢复部分即可

      2.3 设计实现

      2.3.1 设置任务初始栈

      💡
      说明1:任务初始化栈,是任务首次运行时的环境。由于是任务首次运行,软硬件均为设置过该任务的栈,所以此时同时初始化了由硬件保存的部分和由程序员保存的部分
      notion image
      💡
      说明2:任务初始化栈设置注意事项
      1. 硬件自动保存状态部分必须与硬件操作顺序一致
      1. 程序员自行保存状态部分要遵循高编号寄存器对应高地址的规则(STRM & LDRM指令要求的规则)

      2.3.2 启动初始任务

      💡
      说明:触发pendSV异常后,系统进入pendSV ISR运行,由于此处将PSP寄存器置为0,在进行任务切换时据此可识别出此时为启动初始任务,无需保存前一任务的运行状态(因为此时还没有这个"前一任务")

      2.3.3 申请任务调度

      💡
      说明:此处调度实现非常简单,就是在2个任务之间相互切换

      2.3.4 任务切换

      说明1:任务切换流程图如下,
      notion image

      3. 双任务时间片运行原理

      3.1 设计目标

      notion image
      💡
      说明:在上节实现中,任务切换由任务主动调用tTaskSched函数实现;在本节实现中,将在SysTick ISR中调用tTaskSched函数进行任务切换,进而实现基于时间片的任务调度

      3.2 时间片切换原理

      3.2.1 时间片实现

      notion image
      💡
      核心:利用SysTick的周期性中断,在该中断的ISR中选择下一任务,并触发pendSV异常进行任务切换

      3.2.2 SytTick定时器简介

      SysTick定时器是Cortex-M3内核内置的24位递减定时器,当递减到0时,将从RELEAD寄存器中自动重载定时初值到CURRENT寄存器,如此反复。
      notion image

      3.3 设计实现

      3.3.1 SysTick设置

      3.3.2 SysTick_Handler

      3.3.3 任务函数

      💡
      说明:由于在SysTick_Handler中进行任务切换,所以在任务函数中无需再调用tTaskSched函数

      4. 双任务延时原理与空闲任务

      4.1 设计目标

      notion image
      💡
      说明:delay函数的延时通过在循环中递减计数值实现,属于忙等操作,在延时过程中持续占用CPU,因此降低了CPU使用率。本节设计的任务延时接口的要点就是在延时过程中释放CPU

      4.2 任务延时原理

      4.2.1 任务延时

      • 目标:在任务延时过程中,暂停当前任务运行,释放CPU控制权给其他任务
      • 步骤:
          1. 启动任务计时器时,通过调用任务切换函数释放CPU
          1. 结束任务计时器时,也会调用任务切换函数,恢复之前暂停的任务(当然,此处还涉及多任务优先级的调度问题)
          notion image

      4.2.2 软件计时器

      notion image
      • 说明1:由于任务数量不限,而硬件计时器资源有限,所以任务计时器一般使用软件计时器实现
      • 说明2:软件计时器是基于硬件计时器工作的,从本质上说,软件计时器是一个计数值,每当硬件SysTick触发时,在SysTick ISR中维护软件计时器计数值
      • 说明3:由于软件计时器基于硬件计时器工作,所以软件定时时间必须是硬件定时器周期的倍数。相当于硬件计时器提供了最小的计时分辨率,如果需要更高精度的延时则只能使用硬件定时器

      4.2.3 延时精度问题

      notion image
      💡
      说明:由于使用软件计时器,加之延时处理需要时间,所以延时精度有限,使用时需要注意场景。

      4.2.4 空闲任务

      如果所有任务都进入延时(或者当前没有任务可运行),那么RTOS应该运行什么 ?(毕竟CPU不能闲着呀~)。所以我们添加空闲任务,在空闲任务中添加低功耗运行指令或者进行CPU统计任务

      4.3 设计实现

      notion image

      4.3.1 增设软件计时器

      说明:在任务初始化函数tTaskInit函数中需要对应地将delayTick字段初始化为0

      4.3.2 SysTick_Handler

      💡
      说明:在SysTick ISR中,首先遍历所有任务的延时计数值,如果非零则递减,说明完成一个SysTick的延时;然后调用tTaskSched函数触发任务切换,这是原先基于时间片调度就需要完成的操作

      4.3.3 tTaskDelay函数

      说明1:tTaskDelay函数的参数为延时时间对应的SysTick个数
      说明2:任务调用tTaskDelay函数设置延时后,需要释放CPU控制权,该动作通过调用tTaskSched函数实现

      4.3.4 空闲任务的实现

      4.3.5 任务调度函数

    2. RTOS
    3. RTOS原理与实现01:芯片内核简介RTOS原理与实现03:内核核心实现
      Loading...
      目录