热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入浅出RTOS:第一课——任务调度机制解析

在实时操作系统(RTOS)中,任务切换是核心功能之一,涉及从当前任务A平滑过渡到下一个任务B的过程。这一过程主要关注两个关键问题:一是确定下一个执行的任务(which),这取决于系统的调度策略;二是决定何时进行任务切换(when),通常由外部事件触发或定时器中断驱动,实现高效的资源管理和响应性。此外,任务切换还涉及到上下文保存与恢复等技术细节,确保系统稳定运行。

1 RTOS任务切换

任务切换: 就是从任务A切换到任务B,涉及以下几个问题:



  1. 选择切换到哪个任务?(which) ----> 调度策略问题

  2. 什么时候调度? (when) ----> 被动调度(如时间片)还是主动调度

  3. 调度时候要做什么? (how)----> 现场的保留与恢复

本篇主要关注现场的保留与恢复。

现场 = PC指针(包括寄存器组) + 独立栈

如下图所示,是RTOS多任务切换示意图。图中是内存中有3个任务块:taskA,taskB,taskC 以及CPU寄存器组。

PC指针指向哪个代码段,哪个任务就开始执行。

如:当PC指针指向taskA时,则taskA运行,当PC指针指向taskB时taskB任务开始运行。

但是为了保证task切换出去又能切换回来,则需要保留当前运行环境。

当前运行环境保留在SP指向的任务栈中。

所以:任务的切换指的是CPU指针的切换,需要考虑上下文的保留与恢复问题,上下文保留在任务栈中。

多任务切换图


2 ARM Cotrex-M3系列任务切换

ARM Cotrex-M3平台上,任务切换都是基于触发PendSV异常进入中断,然后在中断处理函数中实现任务切换。这部分许多文章都提及到了。详细情况可参考《UCOS-II在CORTEXT-M3(STM32)上的任务切换示意》一文。

使用PendSV异常的优点:



  1. 设置PendSV异常,通过进入中断自动保存一部分寄存器,可以加快上下文切换速度;

  2. 将PendSV中断优先级置为最低,可以优先执行优先级较高的中断处理。

但是,从学习的角度看,可以绕过PendSV异常,直接蛮干的切换任务。

如下一个150行左右的代码,构造了5个任务组成循环链表结构,通过主动释放CPU,依次轮询调度。

(裁剪了许多东西,如任务只保留ip与sp,保留现场只保留ip与sp,其中ip保存到堆栈里,sp保留到全局变量里,这样做是不完备的,最好保存整个寄存器组)。

#include
#include
//================ debug uart cOnfig=====================//
#define ITM_PORT8(n) (*(volatile unsigned char *)(0xe0000000 + 4*(n)))
#define ITM_PORT16(n) (*(volatile unsigned short *)(0xe0000000 + 4*(n)))
#define ITM_PORT32(n) (*(volatile unsigned long *)(0xe0000000 + 4*(n)))
#define DEMCR (*(volatile unsigned long *)(0xE000EDFC))
#define TRCENA 0X01000000
int fputc(int ch, FILE *f)
{
if(DEMCR & TRCENA) {
while(ITM_PORT32(0) == 0);
ITM_PORT8(0) = ch;
}
return ch;
}
//========================================================//
#define STACK_SIZE 1024
#define MAX_TASK_NUM 5
struct Thread {
unsigned long ip;
unsigned int *sp;
};
typedef struct PCB {
struct Thread thread;
int pid;
volatile long state; /* -1 idle, 0 runnable */
unsigned int stack[STACK_SIZE];
struct PCB *next;
} tPCB;
tPCB task[MAX_TASK_NUM];
tPCB *current_task = NULL;
tPCB *next_task = NULL;
void tTaskRunFirst(void);
void tTaskSwitch(void);
void tTask_schedule(void);
void my_process(void)
{
int i = 0;
while (1) {
i++;
if (i % 100 == 0) {
printf("this is process %d - \r\n", current_task->pid);
tTask_schedule();
printf("this is process %d + \r\n", current_task->pid);
}
}
}
void StackInit (tPCB * task, void (*entry)(void), unsigned int ** stack)
{
(*stack)--;
**stack = (unsigned long)entry; // the entry
}
void tTaskInit(int task_num)
{
int i = 0;
task[i].pid = i;
task[i].state = 0;
task[i].thread.ip = (unsigned long)my_process;
task[i].thread.sp = &task[i].stack[STACK_SIZE - 1];
task[i].next = &task[i];
StackInit(&task[i], my_process, &(task[i].thread.sp));
for (i = 1; i task[i].pid = i;
task[i].state = 0;
task[i].thread.ip = (unsigned long)my_process;
task[i].thread.sp = &task[i].stack[STACK_SIZE - 1];
task[i].next = task[i - 1].next;
task[i - 1].next = &task[i];
StackInit(&task[i], my_process, &(task[i].thread.sp));
}
}
int main(void)
{
printf("Init tasks\r\n");
tTaskInit(MAX_TASK_NUM);
/* run the first task */
current_task = &task[0];
tTaskRunFirst();
}
void tTask_schedule(void)
{
if (current_task == NULL ||
current_task->next == NULL) {
return;
}
printf("enter task schedule ->\r\n");
next_task = current_task->next;
if (next_task->state == 0) {
/* switch to next process */
tTaskSwitch();
}
}
__asm void tTaskRunFirst(void)
{
IMPORT current_task
/* R0 = current_task->thread.ip */
LDR R0, =current_task
LDR R0, [R0]
LDR R0, [R0, #4]

/* POP */
LDMIA R0!,{R14};

/* refresh current_task->thread.ip */
LDR R1, =current_task
LDR R1, [R1]
STR R0, [R1, #4]
BX R14
}
__asm void tTaskSwitch(void)
{
IMPORT current_task
IMPORT next_task
/* R0 = current_task->thread.ip */
LDR R0, =current_task
LDR R0, [R0]
LDR R0, [R0, #4]
/* PUSH */
STMDB R0!, {R14};
/* refresh current_task->thread.ip */
LDR R1, =current_task
LDR R1, [R1]
STR R0, [R1, #4]

/* current_task = next_task; */
LDR R0, =current_task
LDR R1, =next_task
LDR R1, [R1]
STR R1, [R0]
/* R0 = next_task->thread.ip */
LDR R0, =next_task
LDR R0, [R0]
LDR R0, [R0, #4]

/* POP */
LDMIA R0!,{R14};

/* refresh next_task->thread.ip */
LDR R1, =next_task
LDR R1, [R1]
STR R0, [R1, #4]
BX R14
}

运行环境:MDK5 Sim环境

运行结果:

多任务运行串口输出

上述代码能正常运行,表示我们对任务切换的理解方大体不差(这么做当然还有其它问题,比如怎么写成时间片轮询的模式?需要考虑更多问题)。


3 参考:



  1. freertos任务切换xPortPendSVHandler

  2. FreeRTOS任务切换

  3. GCC内联汇编基础

  4. UCOS-II在CORTEXT-M3(STM32)上的任务切换示意



推荐阅读
  • 在处理大规模并发请求时,传统的多线程或多进程模型往往无法有效解决性能瓶颈问题。尽管它们在处理小规模任务时能提升效率,但在高并发场景下,系统资源的过度消耗和上下文切换的开销会显著降低整体性能。相比之下,Python 的 `asyncio` 模块通过协程提供了一种轻量级且高效的并发解决方案。本文将深入解析 `asyncio` 模块的原理及其在实际应用中的优化技巧,帮助开发者更好地利用协程技术提升程序性能。 ... [详细]
  • 在 Linux 系统中,`/proc` 目录实现了一种特殊的文件系统,称为 proc 文件系统。与传统的文件系统不同,proc 文件系统主要用于提供内核和进程信息的动态视图,通过文件和目录的形式呈现。这些信息包括系统状态、进程细节以及各种内核参数,为系统管理员和开发者提供了强大的诊断和调试工具。此外,proc 文件系统还支持实时读取和修改某些内核参数,增强了系统的灵活性和可配置性。 ... [详细]
  • PyQt5 QTextEdit:深入解析Python中多功能GUI库的应用与实现
    本文详细探讨了 PyQt5 中 QTextEdit 组件在 Python 多功能 GUI 库中的应用与实现。PyQt5 是 Qt 框架的 Python 绑定,提供了超过 620 个类和 6000 个函数及方法,广泛应用于跨平台应用程序开发。QTextEdit 作为其中的重要组件,支持丰富的文本编辑功能,如富文本格式、文本高亮和自定义样式等。PyQt5 的流行性不仅在于其强大的功能,还在于其易用性和灵活性,使其成为开发复杂用户界面的理想选择。 ... [详细]
  • 在Unity中进行3D建模的全面指南,详细介绍了市场上三种主要的3D建模工具:Blender 3D、Maya和3ds Max。每种工具的特点、优势及其在Unity开发中的应用将被深入探讨,帮助开发者选择最适合自己的建模软件。 ... [详细]
  • 结语 | 《探索二进制世界:软件安全与逆向分析》读书笔记:深入理解二进制代码的逆向工程方法
    结语 | 《探索二进制世界:软件安全与逆向分析》读书笔记:深入理解二进制代码的逆向工程方法 ... [详细]
  • 本文详细介绍了使用响应文件在静默模式下安装和配置Oracle 11g的方法。硬件要求包括:内存至少1GB,具体可通过命令`grep -i memtotal /proc/meminfo`进行检查。此外,还提供了详细的步骤和注意事项,确保安装过程顺利进行。 ... [详细]
  • 本文深入探讨了 MXOTDLL.dll 在 C# 环境中的应用与优化策略。针对近期公司从某生物技术供应商采购的指纹识别设备,该设备提供的 DLL 文件是用 C 语言编写的。为了更好地集成到现有的 C# 系统中,我们对原生的 C 语言 DLL 进行了封装,并利用 C# 的互操作性功能实现了高效调用。此外,文章还详细分析了在实际应用中可能遇到的性能瓶颈,并提出了一系列优化措施,以确保系统的稳定性和高效运行。 ... [详细]
  • 本文探讨了在Android应用中实现动态滚动文本显示控件的优化方法。通过详细分析焦点管理机制,特别是通过设置返回值为`true`来确保焦点不会被其他控件抢占,从而提升滚动文本的流畅性和用户体验。具体实现中,对`MarqueeText.java`进行了代码层面的优化,增强了控件的稳定性和兼容性。 ... [详细]
  • 在Linux环境下编译安装Heartbeat时,常遇到依赖库缺失的问题。为确保顺利安装,建议预先通过yum安装必要的开发库,如glib2-devel、libtool-ltdl-devel、net-snmp-devel、bzip2-devel和ncurses-devel等。这些库是编译过程中不可或缺的组件,能够有效避免编译错误,确保Heartbeat的稳定运行。 ... [详细]
  • 本文详细介绍了如何在Linux系统中搭建51单片机的开发与编程环境,重点讲解了使用Makefile进行项目管理的方法。首先,文章指导读者安装SDCC(Small Device C Compiler),这是一个专为小型设备设计的C语言编译器,适合用于51单片机的开发。随后,通过具体的实例演示了如何配置Makefile文件,以实现代码的自动化编译与链接过程,从而提高开发效率。此外,还提供了常见问题的解决方案及优化建议,帮助开发者快速上手并解决实际开发中可能遇到的技术难题。 ... [详细]
  • 在 Android 开发中,通过合理利用系统通知服务,可以显著提升应用的用户交互体验。针对 Android 8.0 及以上版本,开发者需首先创建并注册通知渠道。本文将详细介绍如何在应用中实现这一功能,包括初始化通知管理器、创建通知渠道以及发送通知的具体步骤,帮助开发者更好地理解和应用这些技术细节。 ... [详细]
  • Liferay Portal 中 AutoEscape 构造函数的应用与实例代码解析 ... [详细]
  • 在Spring框架中,基于Schema的异常通知与环绕通知的实现方法具有重要的实践价值。首先,对于异常通知,需要创建一个实现ThrowsAdvice接口的通知类。尽管ThrowsAdvice接口本身不包含任何方法,但开发者需自定义方法来处理异常情况。此外,环绕通知则通过实现MethodInterceptor接口来实现,允许在方法调用前后执行特定逻辑,从而增强功能或进行必要的控制。这两种通知机制的结合使用,能够有效提升应用程序的健壮性和灵活性。 ... [详细]
  • 如何构建和部署C# Windows服务应用程序
    本文介绍了如何从零开始构建和部署C# Windows服务应用程序。通过详细步骤和代码示例,帮助读者掌握创建、配置和部署Windows服务的关键技术点,适合初学者和有经验的开发人员参考。 ... [详细]
  • 探讨 jBPM 数据库表结构设计的精要与实践
    探讨 jBPM 数据库表结构设计的精要与实践 ... [详细]
author-avatar
我是80初
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有