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

这是简单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超轻量操作系统之信号量与互斥量

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容