首页 > 其他分享 >多任务调度器

多任务调度器

时间:2024-05-03 21:14:35浏览次数:16  
标签:r0 函数 sp 任务 lr 寄存器 任务调度

任务的状态

  • 运行态:正在占用CPU, 对于单核cpu, 任何时候只有一个正在运行的任务
  • 就绪态:被登记到就绪表的任务,等待占有CPU的任务释放CPU后,可能获得调度
  • 挂起态/等待态:不在上述状态的任务


抢占式调度

一旦就绪态中出现优先级更高的任务,会立即剥夺当前运行的任务,把CPU分配给这个优先级更高的任务;这样CPU总是执行处于就绪条件下优先级最高的任务;

在freeRTOS中,任务的优先级就是任务的唯一编号,所以不能两个任务是同一个优先级;


心跳定时器与延时

OSTimeDly函数通过心跳定时器的节拍来完成延时,功能就是先挂起当前任务,然后设置延时节拍,然后通过任务切换;

在指定的时钟节拍到来后,将当前任务恢复到就绪状态;

关于空闲任务


实现多任务的关键

  • 程序代码
  • 私有堆栈
  • 任务控制块

保存当前任务的SP指针不是C语言能干的事,这个是和CPU硬件体系相关的,所以只能借助于汇编;以ARM为例

ldr r4, =p_OSTCBCur 的作用是把p_OSTCBCur的地址加载到r4; 因为p_OSTCBCur不是汇编能直接处理的符号,加上=取了它的地址就可以给ldr进行操作了;

ldr r5,[r4] 则是对r4的内容作为地址,解引用,给到r5,此时r5就是p_OSTCBCur;

str sp, [r5] 则是把当前的sp指针复制到*p_OSTCBCur, 完成了sp保存到任务控制块中;

再谈函数

这里可以看到,任务是以函数的形式组织的,这和为什么sp指针很重要有关,这里我们来深入探讨这个问题;

首先我们知道,函数很重要,因为不管是多任务还是单任务,我们都离不开函数,它把代码按功能组织成一个又一个块;

有了函数,代码的编写者可以完成自己想要的功能的抽象,以及代码组织的更为高效;

我们可以通过跳转指令完成函数调用,并需要在函数执行结束后返回到原先的位置继续执行;

int funcb()
{
    return 0;    
}

void funca(int num)
{
    int k[10];
    k[0] = funcb();
    k[1] = num;
}
int main()
{
    int a = 5;
    funca(a);
}

在main()中,我们调用了funca, 在funca中,我们调用了funcb,看看在微观上(汇编的角度),需要做哪些事情才能完成;

在深入了解这个问题之前,我们需要学习一下arm架构中的几个重要概念;

在arm架构中,r0-r15寄存器是cpu的通用寄存器;

在函数调用的过程中,r0-r3用于传递函数参数,r4-r12 用于 存储临时数据和局部变量;

r13 是 sp, 栈指针寄存器,指向当前栈的顶部,arm架构中,栈是满减栈

r14是lr, 链接寄存器,用于存储函数返回地址,当函数调用子函数时,当前函数的返回地址会被存储在链接寄存器中,

r15是pc, 程序计数器,存储当前正在执行的指令的地址;

上述C代码对应的汇编代码如下:

.func main

main:
@函数入口

mov r0, #5
str r0, [sp, #0]

@调用函数funca
bl funca

@函数返回
mov r0, #0
bx lr

.endfunc

.func funca
funca:
@prologue
sub sp, sp, #40 @分配40字节的栈空间给k
@function body
bl funcb
str r1, [sp, #0] @将funcb的返回值存入k[0]
str r0, [sp, #4] @将参数num 存入k[1]
@epilogue
add sp, sp, #40 
@函数返回
bx lr
.endfunc

.func funcb
funcb:
mov r1, #0
bx lr
.endfunc

.endfunc

 从上面的汇编可以看出,为什么sp很重要,对一个函数而言,sp存放着这个函数的局部变量;

对一个任务而言,sp还要存放pc和寄存器组,当发生切换的时候,会从任务堆栈中把这些环境恢复出来;


关于函数调用约定

关于函数的调用有一些约定,以指导编译器的相关设计:

  • 参数的传递方式:参数是通过寄存器,栈还是其他方式传递
  • 寄存器的使用:哪些寄存器用于传递参数,保存临时数据等
  • 返回值的传递:返回值是通过寄存器,栈还是其他方式传递
  • 栈的管理,栈是如何分配,释放和维护的,栈帧的布局等

这些调用约定可以保证,不同模块之间的调用能够正确执行;

在ARM架构中,常见的函数调用约定有以下几种:

  1. ARM标准(AAPCS), 这是ARM架构的标准调用约定,全程ARM Architeture Procedure Call Stantard
  2. ARM 嵌入式ABI, 针对嵌入式的特定调用约定,如ARM EABI

AAPCS主要包括以下几个方面:

  • 寄存器用途规定,见上面的描述
  • 参数传递方式:优先使用寄存器传递,如果参数个数或者size超过了寄存器的容量,剩余的参数会压入栈中;参数的传递顺序是r0, r1,r2, r3, 然后是栈传递
  • 返回值的传递方式:返回值通常放在r0寄存器,如果返回值是结构体或者浮点数,使用额外的寄存器
  • 栈的管理:函数调用时,会在栈上创建一个栈帧(Stack Frame),用于保存局部变量,函数参数和返回地址信息,栈帧的布局和管理由编译器负责(这里是指如果是用C或者高级语言编程时,这些会由编译器帮用户完成,但如果是自己写汇编,则还是需要自己手动完成)
  • 异常处理

 如何切换任务

 

任务切换的核心就是这里的OS_TASK_SW()

首先是pc入栈,stmfd sp!, {pc} , 同时sp!的意思是sp的值更新;

这里需要注意

虽然注释是PC入栈,但是实际上入栈的是lr, 这是因为当前调用了OSSched()函数,我们希望当任务切换回来后,从OSSChed()返回后的地址开始执行

后面紧跟着是stmfd sp!, {r0-r12, lr} , r0-r12, lr入栈以及cpsr入栈;

stmfd 是连续压栈,stmfd sp!, {r0-r12, lr} 是 lr 先入栈,然后依次是r12, r11, ... r0

ldmfd是连续出栈,ldmfd sp!, {r0-r12, lr} 是r0先出栈,最后是lr出栈

综上看,从上往下代表从高地址到低地址,那么现在栈的布局就是

  • PC --- 栈底
  • LR
  • R12
  • R11
  • ...
  • R0
  • CPSR <--- SP 栈顶

SP始终是栈顶,往下增长,PC则是在栈底;

完成这些后,通过SaveSPToCurTcb 把当前的sp给到p_OSTCBCur,完成任务栈的保存;

 注意这里的入栈顺序,当出栈的时候,必须按照同样顺序出栈才能完成运行环境的重建;

拿到要运行任务的栈顶指针,执行POP_ALL, 把任务栈中恢复任务的运行环境

 根据前面入栈的顺序,出栈就是

sp 指向 CPSR, 通过ldmfd sp!, {r4} 把这个cpsr给到r4; sp+=4

然后给把任务栈的R0到R12给到CPU寄存器R0到R12, lr给LR, PC给PC;

关于抢占式调度

 

标签:r0,函数,sp,任务,lr,寄存器,任务调度
From: https://www.cnblogs.com/Arnold-Zhang/p/18170639

相关文章

  • 在Linux中,如何使用cron和at命令进行任务调度?
    在Linux中,cron和at命令是两个用于任务调度的工具。它们允许用户安排在特定时间或日期执行脚本或命令。1.使用cron进行任务调度cron是一个基于时间的作业调度器,它在后台运行并定期检查crontab中的作业,然后执行它们。编辑crontab文件:查看当前用户的crontab文件:crontab-l......
  • c# 实现Quartz任务调度
    使用Quartz.NET,你可以很容易地安排任务在应用程序启动时运行,或者每天、每周、每月的特定时间运行,甚至可以基于更复杂的调度规则。官网:http://www.quartz-scheduler.net/实现任务类创建一个实现了IJob接口的类(MailJobTest),该接口包含一个Execute方法,该方法将在作业运行时......
  • 分布式任务调度平台XXL-JOB:调度日志打印时区问题
    系列文章目录文章目录系列文章目录前言前言前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。Quartz作为开源作业调度中的佼佼者,是作业调度的首选。但是集群环境中Q......
  • FreeRTOS临界段代码保护和任务调度器的挂起与恢复学习
    FreeRTOS临界段代码保护和任务调度器的挂起与恢复学习临界段代码保护所谓临界段代码保护就是指必须完成运行,不能被打断的代码段。比如需要严格按照时序除初始化的外设:IIC、SPI,再或者因为系统自身需求和用户需求。FreeRTOS在进入临界段代码的时候需要关闭中断,当处理完临界段代......
  • 浅谈分布式任务调度系统Celery的设计与实现
    Celery是一个简单、灵活且可靠的分布式任务队列,它支持任务的异步执行、进度监控、重试机制等功能。Celery的核心组件包括:Broker:消息中间件,如RabbitMQ。用于任务的发布和订阅。Worker:任务执行者,运行在各个Worker节点上。Client:任务提交者,运行在应用程序中。使用步骤:在......
  • 08_任务调度
    任务调度开启任务调度器vTaskStartScheduler()xPortStartScheduler()启动第一个任务prvStartFirstTask()vPortSVCHandler()出栈/压栈汇编指令详解任务切换PendSV中断是如何触发的?查找最高优先级任务前导置零指令获取最高优先级任务的任务控制块PendS......
  • Java面试题:用Java并发工具类,实现一个线程安全的单例模式;使用Java并发工具包和并发框架
    面试题一:设计一个Java并发工具类,实现一个线程安全的单例模式,并说明其工作原理。题目描述:请设计一个Java并发工具类,实现一个线程安全的单例模式。要求使用Java内存模型、原子操作、以及Java并发工具包中的相关工具。考察重点:对Java内存模型的理解。对Java并发工具包的了......
  • Python任务调度
    在实际的软件开发过程中,经常会遇到需要定时执行某些任务的情况,例如定时备份数据、定时发送邮件等。Python提供了多种方式来实现任务调度,本文将介绍几种常见的任务调度方法。一、使用sched模块Python标准库中的sched模块提供了一个简单的任务调度器,可以用来在指定的时间执......
  • SpringBoot应用调用Linkis进行任务调度执行SQl;进行数据质量分析
    基于Linkis的Rest-API调用任务官网示例:“https://linkis.apache.org/zh-CN/docs/1.3.2/api/linkis-task-operator”集合Springboot集成准备工作:SpringBoot-web应用:封装好支持cookie的restClient就行封装RestTemplateimportorg.apache.http.client.HttpClient;importo......
  • 任务调度工具
    定时工具importcn.hutool.cron.CronUtil;importcn.hutool.cron.task.Task;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Component;importorg.springframework.util.StringUtils......