美文网首页
stm32超轻量操作系统之任务调度

stm32超轻量操作系统之任务调度

作者: 生若夏花_1ad0 | 来源:发表于2018-11-21 17:18 被阅读0次

这是简单STM32 OS的第一章stm32超轻量操作系统之抢占式内核

在这个最简程序中只有两个任务交替执行,任务一和任务二,两个任务分别控制两个LED灯的亮灭。只是完成了最简单的任务切换功能。

这一版的程序中没有加入像一般的OS中调用延时函数时会发生任务调度的功能,也没有优先级,没有时间片,只是两个任务不断交替执行。在第二章中会加入抢占式内核和延时功能。

STM32的任务调度可以有两种方式

1.通过systick_handler定时器调度

2.执行一个系统调用

cortexM3的寄存器只有16个,cortexM4除了这16个还有很多浮点运算和MPU单元,如果不用这些单元它和M4没有区别,我是用的cortexM4内核的STM32F407,因为没有用浮点运算和MPU保护单元因此OS也和M3内核兼容。

 图一.16个寄存器示意  

因此在任务调度的过程中,也是不断的保存现在任务的这16个寄存器,弹出下一个任务的16个寄存器。

任务调度的步骤总结为如下:

1.保存程序的上下文即当前任务的寄存器,保存存储寄存器的任务堆栈的地址。

2.根据下一个任务的任务堆栈地址依次弹出下一个任务的16个寄存器。

堆栈中的寄存器保存顺序如下,

XPSR

SP(代表MSP或PSP,在任务调度完成后,根据PSP的值定位了是哪个任务)

LR(存储函数的返回)

R12

R3

R2

R1

R0

R11

R10

R9

R8

R7

R6

R5

R4

具体寄存器的功能可以查看这位博主的文章 https://blog.csdn.net/sagitta_zl/article/details/51318507

接下来根据程序执行的顺序解释程序

图二.程序流程图

首先介绍几个定义的变量

1)TCB程序控制块,程序控制块是一个结构体,其中存储了每一个任务的堆栈的地址指针。

2)taskTCB[2],最多两个任务TCB。程序中定义了最大的同时执行的任务数量为2,也即只有两个任务互相交替,为了简化也没有加入IdleTask。

3)uint32_t stack[100],任务堆栈的大小为。也即是100*4个字节大小。当任务的嵌套层数很多,或很长有很多局部变量时要增大任务的堆栈。局部变量保存在了堆栈中。

4)TCB *currTCB,*nextTCB分别存储了当前任务和下一个任务的TCB,在任务切换的时候使用

1. OSInit()

OSInit中执行了对TCB的初始化,后面根据TCB初始化的值可以判定哪个TCB还处于空闲状态可以放入新的任务。很简单,栈顶指针初始化为了NULL,后面就通过判断是不是为NULL来判定这个TCB还能不能用。

void OSInit()

{

int i = 0;

for(i = 0;i<MAX_TASK_NUMBER;i++)

{

taskTCB[i].topStackPtr = NULL;

}

currTCB = &taskTCB[0];

nextTCB = &taskTCB[0];

}

2. 新建任务堆栈

新建任务堆栈是通过申请了一个静态的数组,也即是uint32_t stack[100]当作堆栈,如果采用动态分配的话要涉及到内存管理,现阶段简单的任务没有必要加上,一切从简。

3. 新建任务

根据任务的地址,任务的堆栈,就可以新建任务了

void OSCreateNewTask(void (*fun)(void),uint32_t *stackAddress)

{

int i = 0;

//进入临界区,关中断,也就是在临界区之内不发生中断

EnterCriticalRegion();

//寻找空闲的任务块

while(taskTCB[i].topStackPtr!=NULL)

{

i++;

}

//初始化任务的栈,栈存储的寄存器顺序不能错,顺序见图一

*stackAddress    = (uint32_t)0x01000000uL;  //xPSR的值,有个1代表是thumb模式

*(--stackAddress) = (uint32_t)fun;          //存储了要执行的任务的地址

*(--stackAddress) = (uint32_t)0xffffffffuL;  //R14(LR)因为程序是个无限的大循环,因此不返回

*(--stackAddress) = (uint32_t)0x12121212uL;  //R12

*(--stackAddress) = (uint32_t)0x03030303uL;  //R3

*(--stackAddress) = (uint32_t)0x02020202uL;  //R2

*(--stackAddress) = (uint32_t)0x01010101uL;  //R1

*(--stackAddress) = (uint32_t)0x00000000uL;  //R0

*(--stackAddress) = (uint32_t)0x11111111uL;  //R11

*(--stackAddress) = (uint32_t)0x10101010uL;  //R10

*(--stackAddress) = (uint32_t)0x09090909uL;  //R9

*(--stackAddress) = (uint32_t)0x08080808uL;  //R8

*(--stackAddress) = (uint32_t)0x07070707uL;  //R7

*(--stackAddress) = (uint32_t)0x06060606uL;  //R6

*(--stackAddress) = (uint32_t)0x05050505uL;  //R5

*(--stackAddress) = (uint32_t)0x04040404uL;  //R4

//TCB栈顶指针的初始化

taskTCB[i].topStackPtr = stackAddress;

//离开临界区,代表可以进行任务的切换

ExitCriticalRegion();

}

4. OSStart()

在这个部分中完成的任务比较重要,首先我们要知道PendSV中断的作用。前面提到了执行任务切换的两种方式,其中systick_handler就是通过调用PendSV来完成的任务切换。

图二.任务调度实例

个中事件的流水账记录如下:

1)  任务 A 呼叫 SVC 来请求任务切换(例如,等待某些工作完成)

2)  OS 接收到请求,做好上下文切换的准备,并且 pend 一个 PendSV 异常。

3)  当 CPU 退出 SVC 后,它立即进入 PendSV,从而执行上下文切换。

4)  当 PendSV 执行完毕后,将返回到任务 B,同时进入线程模式。

5)  发生了一个中断,并且中断服务程序开始执行

6)  在 ISR 执行过程中,发生 SysTick 异常,并且抢占了该 ISR。

7)  OS 执行必要的操作,然后 pend 起 PendSV 异常以作好上下文切换的准备。

8)  当 SysTick 退出后,回到先前被抢占的 ISR 中,ISR 继续执行

9)  ISR 执行完毕并退出后,PendSV 服务例程开始执行,并且在里面执行上下文切换

10) 当 PendSV 执行完毕后,回到任务 A,同时系统再次进入线程模式。

可以看到PendSV的优先级是最低的,这样才能够不影响其他中断的执行,影响了实时性。在Systick中可以把PendSV挂起,在ISR执行完成后再执行这个优先级最低的中断。

__ASM void OSStart()

{

PRESERVE8

//关中断

CPSID I

//设置PendSV的优先级为最低

LDR R0,=NVIC_SYSPRI14 //R0 = NVIC_SYSPRI14

LDR R1,=NVIC_PENDSV_PRI //R1 = NVIC_PENDSV_PRI

STRB R1,[R0] //R0 = *R1

//赋PSP=0,代表是第一次执行,作用见下文

LDR R4,=0x0 //R4 = 0

MSR PSP,R4 //PSP = R4

//LDR R4,=0x3

//MSR CONTROL,R4

//挂起PendSV中断,通过直接写寄存器的方式可以挂起

  LDR  R4, =NVIC_INT_CTRL             

  LDR  R5, =NVIC_PENDSVSET           

  STR  R5, [R4]                       

//开中断

CPSIE I

BX LR

nop //对齐

}

5. PendSV

在步骤4中程序的最后挂起了PendSV,因此一旦开启了中断,程序将会进入PendSV执行。在这一步中,需要理解到进入函数跳转即进入PendSV时,硬件自动会完成在PSP指向的地址中存储xPSR,LR,SP,R0-R3,R12这8个寄存器的工作。因此完成这个步骤之后PSP=PSP-0x20,之后再在PSP指向的地址中存储R4-R11 8个寄存器。之后的弹出过程与之相反,先弹出R4-R11寄存器,跳出PendSV后,硬件自动完成剩余的8个寄存器的弹出,PSP=PSP+0x20

__ASM void PendSV_Handler()

{

extern PendSVFirst;

extern currTCB;

extern nextTCB;

PRESERVE8

CPSID I

//判断PSP是否为0,如果是则代表是第一次执行程序,那么就没有了保存当前寄存器这个过程,直接跳转到弹出寄存器

MRS R0,PSP

CBZ R0,PendSVPopData

STMDB R0!,{R4-R11} //在R0中依次保存R4-R11寄存器 完成后R0=R0-0x20

LDR R1,=currTCB

LDR R1,[R1] //R1=currTCB->StackTopPtr

STR R0,[R1] //currTCB->StackTopPtr=R0,保存当前任务上下文的最后一步,也即当前任务的TCB保存了其任务堆栈栈顶的指针,完成了保存。

nop

//因为没有BX跳转指令,执行完这个函数后接着会执行下面的PendSVPopData()

}

__ASM void PendSVPopData()

{

extern PendSVFirst;

extern currTCB;

extern nextTCB;

PRESERVE8

LDR R0,=currTCB //R0=currTCB 

LDR R1,=nextTCB //R1=nextTCB

LDR R1,[R1] //R1=*R1

STR R1,[R0] //*R0=R1 currTCB=nextTCB完成了指向新的任务TCB的工作

LDR R0,[R1] //R0=*R1 R0保存了TCB堆栈栈顶的指针

LDMIA R0!,{R4-R11} //依次弹出R4-R11,完成后R0=R0+0x20

MSR PSP,R0 //PSP=R0

ORR LR,LR,#0x04 //LR=LR|0x04,表示函数返回后使用PSP指针

CPSIE I

BX LR

nop

}

6. OSSwitch()

OSSwitch函数只是简单的更新了nextTCB,之后完成了触发PendSV,即设置PendSV相应寄存器的位为1

void OSSwitch()

{

EnterCriticalRegion();

if(currTCB==&taskTCB[0])

nextTCB = &taskTCB[1];

else

nextTCB = &taskTCB[0];

OSTaskSchedule();

ExitCriticalRegion();

}

7. EnterCtiticalRegion和ExitCriticalRegion很简单,内容就是开关中断

__ASM void EnterCriticalRegion()

{

PRESERVE8

CPSID I  //关中断

BX    LR //LR跳回

}

__ASM void ExitCriticalRegion()

{

PRESERVE8

CPSIE I

BX LR

}

至此就完了最简单的任务切换功能,下一章将加入抢占式内核、时间片和延时进行调度的功能。

程序链接如下,实验用的是STM32F407的开发板

链接:https://pan.baidu.com/s/1my2HPG6shXB7QiwbR47Dnw

提取码:j5hw

stm32超轻量操作系统之抢占式内核

stm32超轻量操作系统之信号量与互斥量

相关文章

  • stm32超轻量操作系统之任务调度

    这是简单STM32 OS的第一章stm32超轻量操作系统之抢占式内核 在这个最简程序中只有两个任务交替执行,任务一...

  • stm32超轻量操作系统之信号量与互斥量

    互斥量就是二元信号量,因此在一章里面介绍。 这一章比较简单,废话不多说,直接进入正题 信号量可以控制任务的执行顺序...

  • stm32超轻量操作系统之抢占式内核

    这一章比上一章内容多了不少。第一章完成了任务切换功能,这一章在任务切换功能上增加了以下几个功能。 1.改变特权级,...

  • 《计算机组成与体系结构》——7操作系统支持

    操作系统最重要的功能是进程或任务的调度和存储管理!操作系统最重要的功能是进程或任务的调度和存储管理!操作系统最重要...

  • os任务调度实现原理

    为什么要做任务调度-why 操作系统中最为显著的特性就是任务调度,任务调度主要来自于以下几种需求: 程序并发(mu...

  • Python线程

    多任务 概念:操作系统可以同时运行多个任务 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现...

  • 操作系统知识点

    进程调度全局队列调度:操作系统维护一个全局的任务等待队列,当系统中有一个空闲的CPU时,操作系统就会从全局任务队列...

  • Java定时任务调度工具详解

    本篇内容:什么是定时任务调度?Java定时任务调度工具详解之 Timer篇Java定时任务调度工具详解之 Quar...

  • 多线程与多进程 线程安全

    多核多线程多进程 任务调度 操作系统一般都会采用时间片轮转的抢占式调度 进程 操作系统是计算机的管理者,它负责任务...

  • Go知识点

    1、进程和线程的区别答:计算机由CPU和操作系统组成,CPU执行计算任务,操作系统执行资源调度任务。在操作系统上会...

网友评论

      本文标题:stm32超轻量操作系统之任务调度

      本文链接:https://www.haomeiwen.com/subject/zbicqqtx.html