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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了stm32超轻量操作系统之抢占式内核相关的知识,希望对你有一定的参考价值。

参考技术A 这一章比上一章内容多了不少。第一章完成了任务切换功能,这一章在任务切换功能上增加了以下几个功能。

1.改变特权级,加入SVC异常

2.增加优先级,使得内核可以抢占。

3.增加了滴答定时器中断功能,使得同优先级的任务以时间片方式调度。

4.增加延时函数。

接下来一项一项介绍。

1. 改变特权级

第一章中,所有的程序都是运行在特权级下。在中断中时切换到内核模式,在任务中切换到线程模式(Thread),但是权限都是特权级,意味着程序对内存中所有的数据都有修改的权限。此处加入两张图,通过修改寄存器,可以在退出中断后,在任务中运行即线程模式时权限是非特权级,这样在线程模式时就不能够更改内核模式下的数据了,程序的更具有健壮性。

更改特权级只能在特权级下更改,所以通过在PendSV最后加入红色字体部分来更改了特权级。

__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指针

//使程序运行在非特权级,设置CONTROL[0]位为1,这样内核级和用户级就分开了,即是某个线程出现了问题也不会影响内核

LDR R1,=0x3

MSR CONTROL,R1

ISB

CPSIE I

BX LR

nop



同时要注意在非特权级(用户级)下只有触发异常才可以进入重新进入特权级,因此额外加入了SVC异常来主动触发中断。

还记得我们上一章讲过的发起任务调度的两种方式吗?

1.产生异常。

2.systick定时器中断。

其中第一条就是SVC异常中断。SVC异常用于产生系统调用,它的优先级最高,一经调用立刻进入此异常,因此,通过调用SVC异常,OS就进入了内核态(handler模式),在此就可以进行任务切换了。这种方式发起任务调度就不必等到systick定时器中断产生再发生任务调度了。

在任务中调用SvcTaskSwitch()就可以进行一次任务切换,这是自己定义的函数。SVC具体细节可以在网上查看相关资料。

2. 增加优先级功能

优先级功能参考了FreeRTOS的设计,通过列表和列表项的方式实现。

简单来讲,为了实现这个功能,我们设置了不同的列表,如果我们设置优先级共有8个,则新建8个优先级列表priorityReadyList[8]存储处于就绪态的任务,新建一个delayedList,存储处于延时的任务,新建一个pendList,存储处于挂起态的任务。所有的任务必然存在于这三种列表中。

列表又由列表项组成,列表中的列表项以双向链表的形式组合在一起,每一个列表项又存储了一个任务的TCB,这样就可以找到相应的任务。

在任务切换时,首先找到一个目前优先级最高的任务,OSFindMaxPriorityTask()。如果找到的这个任务跟当前任务的优先级相同,那么就指向当前任务在列表中的下一个任务,来做切换,如果找到的任务优先级更高,那么直接切换高优先级的任务

3. 滴答定时器

滴答定时器的中断时间时1ms,也是时间片的时间。同优先级的任务每个运行1ms再切换。

systemTicks是任务运行的时间,系统初始化时赋值为0。在此我们不考虑溢出的问题,因为systemTicks是一个uint32_t的值,在此最大值代表约50天,因此在这个简易系统中不考虑溢出的情况。

其中需要注意的一点是判断任务的延时是否到达。其实很简单,如果有一个任务的延时时间到了,在延时列表中删除这个任务,在就绪列表中增加这个任务。我们怎么在延时列表中找到这个任务呢?因为列表中的列表项是按照readyTime升序排列的,因此通过我们前面在列表中特意设的最后列表的最后一项可以轻易的根据previous找到列表中的第一项。更新完任务后不要忘了更新下一项延时任务将要到达的时间。

在初始话列表的过程中我们给列表最后一项赋予了一个很大的值0x3ffffffff,因此可以任务永远不会到达这个时间。在列表中添加的项都会在它的前面。

4. 增加延时函数。

在第一章,我们时通过无意义的while(i--)来达到延时的目的,但这是阻塞运算,程序会卡在这个地方,满足不了实际的需要。OS中的延时函数会发生任务的调度,这样才不会浪费资源。

OSDelay的主要功能就是把当前任务挂起,在就绪列表中删除当前的任务,把任务加入到延时的列表中,更新其将要延时的时间,然后寻找下一个优先级最高的任务,进行切换。

最后设计了几个小实验,来验证程序是否正确运行。

设计了几个实验

1.两个小灯优先级相同,验证同优先级下的时间片调度。ok

void Task1()



int j = 0;

    while(1)

   

          j=20000000;

         HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);

         while(j!=0)

         j--;

         HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);

         j=20000000;

         while(j!=0)

         j--;

   



void Task2()



     int i = 0;

     while(1)

     

        i=10000000;

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);

        while(i!=0)

        i--;

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);

        i=10000000;

        while(i!=0)

        i--;

   



2.两个任务优先级相同,验证延时功能是否有效 ok

void Task1()



     while(1)

     

        OSDelay(2000);

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);

        OSDelay(2000);

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);

     



void Task2()



      while(1)

     

          OSDelay(1000);

          HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);

          OSDelay(1000);

          HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);

     



3.验证优先级不同,任务一优先级高,任务二优先级低,任务一一直运行,小灯暗灭切换,可以看到任务二不运行,小灯一直亮

void Task1()



      int i = 0;

      while(1)

     

           i = 20000000;

          while(i!=0)

          i--;

          //OSDelay(2000);

         HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);

         i = 20000000;

         while(i!=0)

         i--;

         //OSDelay(2000);

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);

     



void Task2()



      while(1)

     

          OSDelay(1000);

          HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);

          OSDelay(1000);

          HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);

     



4.任务一优先级高,任务二优先级低,任务一程序中有挂起3秒。可以看到在挂起的这段时间内任务二会切换,一但不挂起,任务二灯不会切换

void Task1()



     int i = 0;

     while(1)

      

          i = 100000000;

          while(i!=0)

          i--;

          //OSDelay(2000);

         HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);

         i = 100000000;

         while(i!=0)

         i--;

         OSDelay(5000);

         HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);

     



void Task2()



    while(1)

   

        OSDelay(1000);

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);

       OSDelay(1000);

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);

   



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

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

提取码:j5hw

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

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

抢占式内核中,线程在系统调用过程中被抢占,然后又被重新调度时,如何返回至被中断的系统调用的

参考技术A 首先,一般配置的linux,如果进程正在进行
系统调用
,那么此时进程就是正运行在内核态,而内核态的进程是不可被抢占的。
你说的这种情况,属于编译内核的时候开启了“内核可抢占”这个特性,这种情况下,即使进程正在内核态
执行系统
调用,也可以被其他进程抢占,这时,首先系统会在进程
结构体
中记录下这个进程当前是在内核态运行,然后,每个进程都有专属于自己的内核栈,系统会把当前的执行上下文信息都记录到这个进程的内核堆栈上。
以后这个进程恢复运行的时候,内核会从它的结构体中读到被打断前它处于内核态,进而从它的内核栈中读取当时的上下文信息并进行恢复,这样,进程就得以重新运行了。
有问题请追问

以上是关于stm32超轻量操作系统之抢占式内核的主要内容,如果未能解决你的问题,请参考以下文章

STM32F429第13章 任务调度—抢占式,时间片和合作式

nodejs中的fiber(纤程)库详解

STM32中断优先级

STM32优先级

STM32中的抢占优先级响应优先级概念

操作系统中抢占式和非抢占式内核的区别