目录

实时操作系统的概念

软实时|硬实时

前后台系统

多任务

任务状态

可剥夺型内核与不可剥夺型内核

任务间通讯、同步与临界段

如何实现优先级切换?

OSTCBY和OSTCBX

OSTCBBitX和OSTCBBitY

OSRdyGrp和OSRdyTbl[ ]

查询映射表获取最高优先级任务

系统启动与任务切换(汇编)

临界段

调度切换

如何实现任务切换

SysTick_Handler的优先级

软件定时器

任务间通讯机制

1、信号量

2、消息队列

3、消息邮箱


实时操作系统的概念

软实时|硬实时

实时这个概念很原始,就是我让你做什么你就做什么,不要让我看到一点拖延。就像火箭点火,不管你中间经过了多么复杂的过程,我想要的就是立刻点上火,不断地降低中间的延时,从1分钟到1秒,甚至到1ms以内。

软实时和硬实时的概念区分,其实就是应用原来越复杂了。如果只做一两件事,那么硬实时无疑是高效准确的,但是事情多了之后如果仍然硬实时而缺乏调度,总是会丢失对低优先级事件的响应。

前后台系统

RTOS的上一个阶段,是前后台系统。
main函数是一个while(1)无限循环,中断触发的服务程序被称为前台,while(1)循环中调用相应的函数完成处理称为后台。
这种系统在复杂应用中的响应时间是难以确定的,往往程序修改了,循环的整个时序都要受到影响。而且应用越复杂,总体响应时间就越长。
但在逻辑简单的产品中反而有效,因为系统响应时间可以确定为一个常数,如微波炉、电饭锅等产品。

多任务

RTOS相比前后台系统的优点,最明显的就是RTOS可以有多个后台,即多任务。
每个任务都设置触发条件或检测条件,条件不允许时任务闲置,多任务运行时由内核调度。
这样就可以做到,设计应用程序时就可以把问题分割成多个任务;每个任务作为一个单独的模块,实现一个输入输出的功能。

多任务间,既有相互完全独立的,也有相互协作的。
比如一个车辆监控设备,要实现一个CAN和GPS数据监控功能与车身状态监控功能。
它就可以拆分出来如下几个任务:
1、数据上传任务,负责对系统监控的周期性数据进行周期性上传,以及实时反馈异常数据;
2、CAN数据采集任务,GPS采集任务;
3、数据处理任务,负责对采集到的数据进行加工处理,并传递到上传任务;
4、系统监控任务,负责监控车辆异常状态,并做警报和UI显示;

任务状态

每个任务都是一个while(1)无限循环。
每个任务都处于5种状态之中,即休眠态、就绪态、运行态、挂起态和被中断态。
休眠态,顾名思义就是任务休眠之后就不被内核调度了;
就绪意味着任务运行条件已经满足,但还有高优先级任务正在执行;
运行态即当前任务正在执行;
挂起态又叫等待事件态,任务执行时需要等待特定事件的发生时进入此态;
被中断态,发生中断时所有任务进入被中断态。

可剥夺型内核与不可剥夺型内核

内核调度:可剥夺型内核与时间片轮转
大多数RTOS的内核都采用可剥夺型内核,即最高优先级任务一旦就绪,总能立即取得CPU的控制权,被中断的任务进入就绪态;
不可剥夺型内核,则是任务被中断打断后,CPU仍返回原来被中断的任务,接着执行,直到该任务完成,任务完成后自己调用内核服务函数释放CPU控制权。
时间片轮转调度中,允许任务的优先级相同,内核根据任务确定好的时间(额度)运行一段时间,然后切换给另一个任务。
ucOSII不支持时间片轮转调度算法,freeRTOS是支持的

任务间通讯、同步与临界段

任务间的通讯本质上只有两种方式,全局变量和中断服务程序,信号量或消息队列都只是这种全程变量的不同表现形式。
那么为了保证“全程变量”的可靠性,就需要加一个保护(关闭中断),保证变量的修改不被打断,这个概念被称为“临界段”。
实时内核最重要的指标就是关闭中断用了多长时间(中断延迟),保存和回复CPU寄存器需要多长时间(中断响应时间和中断恢复时间)。
任务间除了通讯还有同步,任务间同步则依赖于时钟节拍(如同心脏脉搏),它是一种特定的周期性中断,它使得内核可以将任务延时划分成若干个时钟节拍,以及事件发生时的超时依据。时钟节拍频率越快,系统开销就越大。

如何实现优先级切换?

INT8U  const  OSUnMapTbl[256] = {0u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,7u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u  };

ucOS-ii内核最核心的就是上面的映射表 - OSUnMapTbl[256] ,该数组表示0~255中每个数的二进制值中从右边开始第一个出现1的位。

那么,ucOSII是如何实现任务调度的呢?又是如何用到这个映射表的?
首先,ucOSII的任务的最大个数固定为64(或128),同时任务的TCB表使用全局变量。
其次,ucOSII按照优先级进行调度,数值越低优先级越高。

//内核部分变量的定义:
//1、TCB成员变量
INT8U       OSTCBPrio;
INT8U       OSTCBX;
INT8U       OSTCBY;
OS_PRIO     OSTCBBitX;
OS_PRIO     OSTCBBitY;//2、全局变量
OS_PRIO     OSRdyGrp;
OS_PRIO     OSRdyTbl[OS_RDY_TBL_SIZE];
INT8U       OSPrioHighRdy;

其中,OSTCBPrio表示系统优先级,其最大数值为255,但ucOSII中只用了6个bit,即最大值为63(111 111)。

其它数值与OSTCBPrio有如下转换关系

OSTCBY和OSTCBX

64个任务可以分成8组,每组8个。
将优先级值得高三位转成OSTCBY(表示所在组),低三位转成OSTCBX(表示组内第几个)。
则就有3个关系式:
1:OSTCBPrio = OSTCBY << 3 + OSTCBX;
2:OSTCBY  = OSTCBPrio  >> 3; 
3:OSTCBX = OSTCBPrio  & 0x07;

根据关系式可知,64个任务分成8组后,同一组任务的OSTCBY相同,任务OSTCBPrio数值相差为8的倍数的OSTCBX相同。

OSTCBBitX和OSTCBBitY

4:OSTCBBitX = 1 << OSTCBX; 
5:OSTCBBitY = 1 << OSTCBY;

这样就得到了一个属于每个任务都有的位标志量,标志着变更就绪状态时该置哪一位。
1)优先级数值最大为63时,OSTCBPrio用6个bit,OSTCBY和OSTCBX用3个bit,3个bit最大为7,所以OSTCBBitY和OSTCBBitX需要8个bit,即1个字节;
1)优先级数值最大为127时,OSTCBPrio用7个bit,OSTCBY和OSTCBX用4个bit,4个bit最大为15,所以OSTCBBitY和OSTCBBitX需要16个bit,即2个字节;

OSRdyGrp和OSRdyTbl[ ]

OSRdyGrp和OSRdyTbl[8]是系统调度中的就绪组和就绪表,每当有任务就绪或等待时,就只需要将对应位置1或清0。
6:任务就绪时将对应位置1
OSRdyGrp |=  ptcb->OSTCBBitY;
OSRdyTbl[y] |=  ptcb->OSTCBBitX;
7:任务等待时将对应位清0
OSRdyTbl[y]  &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;

查询映射表获取最高优先级任务

8:每当有就绪状态变更时,自动计算出最高优先级
INT8U y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);

由以上公式可以分析:
1)y值-OSUnMapTbl[OSRdyGrp]即最高优先级任务对应的OSTCBY值;
2)OSRdyTbl[y]即是所在组的8个任务的就绪状态,OSUnMapTbl[OSRdyTbl[y]])则是其中最高优先级任务的OSTCBX值;
由于OSUnMapTbl[256]表示0~255中二进制数值从右边开始第一个出现1的位,,那么无论是“就绪组OSRdyGrp”,还是“OSRdyTbl[y]”,只要代入(uin8_t的数值)进去就能得出最高优先级任务所在的Y或X值。

公式1~5,在任务初始化时就已经生效,即任务初始化时,先设置优先级,然后转化成另外四个变量常值。
公式6~8,在任务就绪状态变更时生效,且位操作效率高,特别是巧妙地通过一张静态表去查询,中间产生的这种执行时间,是可以根据芯片的指令周期去计算的,即确定为一个常数

系统启动与任务切换(汇编)

临界段

#if OS_CRITICAL_METHOD == 3u
#define  OS_ENTER_CRITICAL()  {cpu_sr = OS_CPU_SR_Save();} //进入临界段-保存CPU寄存器
#define  OS_EXIT_CRITICAL()   {OS_CPU_SR_Restore(cpu_sr);} //退出临界段-恢复CPU寄存器
#endifOS_CPU_SR_SaveMRS     R0, PRIMASK     ;将状态寄存器的值送到R0通用寄存器,相当于读取该值CPSID   I               ;关闭IRQ中断(在PRIMASK=1的情况下),I代表IRQ(F对应FIQ)BX      LR              ;跳转到LR寄存器中的地址开始执行OS_CPU_SR_RestoreMSR     PRIMASK, R0     ;将R0通用寄存器该值送回状态寄存器,读取之后什么也没做就返回 BX      LR              ;跳转到LR寄存器中的地址开始执行

调度切换

void SysTick_Handler(void)
{OSIntEnter();//进中断OSTimeTick();//节拍+1OSIntExit();//退中断HAL_IncTick();
}
void  OSIntExit (void)
{                          OS_CPU_SR  cpu_sr = 0u;if (OSRunning == OS_TRUE) {OS_ENTER_CRITICAL();if (OSIntNesting > 0u) {                          OSIntNesting--;//退一次中断-1}if (OSIntNesting == 0u) {                         if (OSLockNesting == 0u) {//检查:存在中断和调度锁时不执行切换                    OS_SchedNew();//上述公式8,根据索引表计算出最高优先级任务OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];//获取最高优先级任务对应的TCB指针if (OSPrioHighRdy != OSPrioCur) {          OSCtxSwCtr++;//如果当前任务不是最高优先级任务,则执行切换,并统计上下文切换次数                          OSIntCtxSw();}}}OS_EXIT_CRITICAL();}
}OSIntCtxSwLDR     R0, =NVIC_INT_CTRL    ;直接对寄存器赋值,写入中断控制寄存器地址                     LDR     R1, =NVIC_PENDSVSET   ;NVIC_PENDSVSET-第28位置1,意即悬起PendSV中断 STR     R1, [R0]              ;将R1的值复制到以R0的值作为地址的内存中去BX      LR                    ;跳转到R14寄存器所指向的地址

如何实现任务切换

ucoSII的实现方法是PendSV(可悬起系统调用),它有一个非常重要的特质:它可以缓期执行,即等待其它高优先级任务或中断完成之后才会执行

在如下OS_CPU_A.ASM(OS的汇编代码的OS启动部分),可以看到,在OS启动之后直接将PENDSV的优先级设置为0XFF即最低。

这样就保证了:

请求任务切换之后(写1悬起PendSV异常),处理器直到所有其他异常或中断完成之后,才会进入PendSV中断处理函数。

(这也是为什么移植ucOSII时,要在stm32f4xx_it.c文件中,注释掉PendSV_Handler函数的原因,因为这个函数被定义到了这个汇编文件中)

(也是在PendSV_Handler这个函数中,真正的实现了寄存器入栈和出栈(保存与恢复)的操作,从而实现任务切换)

SysTick_Handler的优先级

与PendSV异常相关联的,就是SysTick_Handler的优先级设置,能注意到这个问题才证明你认真思考了

SysTick_Handler的优先级一般两种做法,要么和PendSV一样设置为最低,要么最高。

最低则能保证优先处理中断产生的消息或数据,但是中断过于频繁时会阻塞RTOS的运行调度。

最高则能保证RTOS的实时切换与调度,但毕竟是1秒中断一次,可能会影响高频率下的其他中断处理。

一般的做法就是将二者都设置为最低,参考stm32cubxMX。

;********************************************************************************************************
;                                NVIC内核寄存器变量定义,用于悬起PendSV异常
;********************************************************************************************************NVIC_INT_CTRL   EQU     0xE000ED04                              ; 中断控制和状态寄存器地址
NVIC_SYSPRI14   EQU     0xE000ED22                              ; 设置PendSV优先级的寄存器地址
NVIC_PENDSV_PRI EQU           0xFF                              ; 255,即将PendSV设置为最低优先级
NVIC_PENDSVSET  EQU     0x10000000                              ; 写1则悬起PendSV中断;********************************************************************************************************
;                                         启动多任务系统
;********************************************************************************************************OSStartHighRdyLDR     R0, =NVIC_SYSPRI14                                  ; Set the PendSV exception priorityLDR     R1, =NVIC_PENDSV_PRISTRB    R1, [R0]MOVS    R0, #0                                              ; Set the PSP to 0 for initial context switch callMSR     PSP, R0LDR     R0, =OS_CPU_ExceptStkBase                           ; Initialize the MSP to the OS_CPU_ExceptStkBaseLDR     R1, [R0]MSR     MSP, R1    LDR     R0, =OSRunning                                      ; OSRunning = TRUEMOVS    R1, #1STRB    R1, [R0]LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)LDR     R1, =NVIC_PENDSVSETSTR     R1, [R0]CPSIE   I                                                   ; Enable interrupts at processor levelOSStartHangB       OSStartHang                                         ; Should never get here;********************************************************************************************************
;                                       PendSV_Handler中断处理函数
; OS先请求上下文切换 --即悬起PendSV异常
; 异常悬起后(缓期执行即等待其他中断结束后才能进入),执行切换
;********************************************************************************************************PendSV_HandlerCPSID   I                                                   ; Prevent interruption during context switchMRS     R0, PSP                                             ; PSP is process stack pointerCBZ     R0, OS_CPU_PendSVHandler_nosave                     ; Skip register save the first timeSUBS    R0, R0, #0x20                                       ; Save remaining regs r4-11 on process stackSTM     R0, {R4-R11}LDR     R1, =OSTCBCur                                       ; OSTCBCur->OSTCBStkPtr = SP;LDR     R1, [R1]STR     R0, [R1]                                            ; R0 is SP of process being switched out; At this point, entire context of process has been saved
OS_CPU_PendSVHandler_nosavePUSH    {R14}                                               ; Save LR exc_return valueLDR     R0, =OSTaskSwHook                                   ; OSTaskSwHook();BLX     R0POP     {R14}LDR     R0, =OSPrioCur                                      ; OSPrioCur = OSPrioHighRdy;LDR     R1, =OSPrioHighRdyLDRB    R2, [R1]STRB    R2, [R0]LDR     R0, =OSTCBCur                                       ; OSTCBCur  = OSTCBHighRdy;LDR     R1, =OSTCBHighRdyLDR     R2, [R1]STR     R2, [R0]LDR     R0, [R2]                                            ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;LDM     R0, {R4-R11}                                        ; Restore r4-11 from new process stackADDS    R0, R0, #0x20MSR     PSP, R0                                             ; Load PSP with new process SPORR     LR, LR, #0xF4                                       ; Ensure exception return uses process stackCPSIE   IBX      LR                                                  ; Exception return will restore remaining contextEND

软件定时器

软件定时器的意义:

适合对实时性要求不高的功能,比如控制led、蜂鸣器,任务检查或tick等等(要求严格的肯定是要上定时器的)。

软件定时器的如何触发的,参考OS_CPU_C.C文件中的OSTimeTickHook钩子函数:在硬件中断中调用ucOSII系统节拍处理函数,在这个函数有1个钩子函数,用于检测并推送定时器的信号量。

软件定时器的实现算法:

用到了一个时间轮的概念,它将定时器按照到期值的余数进行分组(组数OS_TMR_CFG_WHEEL_SIZE由用户定义,工程难度不大的用默认值就好)(形成一个链表),tick触发时先求余,求余相等的再去分组中检查tick值是否相等,这样大大缩短了处理时间。

任务间通讯机制

1、信号量

此信号量非彼信号量。

ucOS的信号量,本质上是一个单字节类型的用于计数的全局变量。

使用逻辑:任务阻塞性等待计数值,计数值大于1时OS会执行切换,执行或处理多少次由计数值的大小变化决定。

而其它地方,比如freeRTOS,一般是用来做资源管理的(当然ucOS也可以这样用),“必需”要设置信号量的上限值,即某任务使用共享资源时+1,另外一个任务访问时若>=上限值则不能访问,任务使用完毕时-1,才能去访问。

2、消息队列

消息队列的实现机制和方法在不同OS之间都大同小异。

本质上都是一个指针数组,存放的是指向一块内存区域的消息指针,一个队列结构。post时,将指针写入数组的尾部(也可以扩展实现插到队头),pend读取时取出队头的指针消息。

消息队列适用于任务间内部消息传递,以及数据安全性要求较低的外部消息传递。

比如,1个发送任务的发送消息队列,由内部推送;再比如,中断接收外部的JSON消息,可以用消息队列进行传递。

但是,当数据传递安全性较高时,(建议)可以使用信号量+数据队列的形式处理数据。比如中断源收到数据-先缓存起来,然后信号量通知线程去读取数据,防止出现粘包、断包。

3、消息邮箱

邮箱本质上是数组长度为1的消息队列。传递的是一个指针。

(邮箱+链表或队列)非常适合用来传递内部数据处理任务,比如一个CAN总线命令交互处理任务,一条命令执行一个功能;

那么,当收到一条命令时,将命令序列化为一个结构体指针,根据优先级或到期时间将指针压入链表,检查任务空闲状态,若空闲则提取第一条命令推送到处理任务进行执行。

ucOSII知识整理相关推荐

  1. python常用变量名_python基础知识整理

    Python Python开发 Python语言 python基础知识整理 序言:本文简单介绍python基础知识的一些重要知识点,用于总结复习,每个知识点的具体用法会在后面的博客中一一补充程序: 一 ...

  2. Spring AOP 知识整理

    为什么80%的码农都做不了架构师?>>>    AOP知识整理 面向切面编程(AOP)通过提供另外一种思考程序结构的途经来弥补面向对象编程(OOP)的不足.在OOP中模块化的关键单元 ...

  3. Linux系统基础知识整理

    一.说明 本篇文章,我将结合自己的实践以及简介,来对linux系统做一个直观清晰的介绍,使得哪些刚接触Linux的小伙伴可以快速入门,也方便自己以后进行复习查阅. 二.基本知识整理 1.Linux文件 ...

  4. 计算机二级c语基础知识,计算机二级C语基础知识整理.doc

    计算机二级C语基础知识整理 1.1 算法 算法:是一组有穷指令集,是解题方案的准确而完整的描述.通俗地说,算法就是计算机解题的过程.算法不等于程序,也不等于计算方法,程序的编制不可能优于算法的设计. ...

  5. js事件(Event)知识整理

    鼠标事件 鼠标移动到目标元素上的那一刻,首先触发mouseover  之后如果光标继续在元素上移动,则不断触发mousemove  如果按下鼠标上的设备(左键,右键,滚轮--),则触发mousedow ...

  6. Spring学习篇:IoC知识整理(一)

    现在正通过spring的官方文档学习spring,将自己学习时的点点滴滴记录下来. Ioc知识整理(一): IoC (Inversion of Control) 控制反转. 1.bean的别名 我们每 ...

  7. 使用Aspose.Cells的基础知识整理

    使用Aspose.Cells的基础知识整理 转自 http://www.cnblogs.com/kenblove/archive/2009/01/07/1371104.html 这两天用Aspose. ...

  8. 前端基础知识整理汇总(中)

    前端基础知识整理汇总(中) Call, bind, apply实现 // call Function.prototype.myCall = function (context) {context = ...

  9. 前端基础知识整理汇总(上)

    前端基础知识整理汇总(上) HTML页面的生命周期 HTML页面的生命周期有以下三个重要事件: 1.DOMContentLoaded -- 浏览器已经完全加载了 HTML,DOM 树已经构建完毕,但是 ...

最新文章

  1. 在线作图|2分钟在线绘制RDA图
  2. jQuery学习笔记——jQuery选择器详解种类与方法
  3. Autodesk布道GIS新理念
  4. 商淘多b2b2c商城系统怎么在个人电脑上安装_b2b2c商城系统免费模板怎么用?
  5. PHP 单一入口程序
  6. git删除远程分支文件,不改变本地文件
  7. 08 在C#程序中使用注释测试分析 1214
  8. 安卓开发之如何利用Intent对象,实现Activity和另一个Activity之间的跳转
  9. 旋转校正原理_【干货】全站仪水准器的检校原理及方法,值得学习!
  10. 橙子减肥法:好吃快速成为瘦美人 - 健康程序员,至尚生活!
  11. Grasshopper: Architectural Prototyping Grasshopper建筑原型 Lynda课程中文字幕
  12. 存储过程与函数的区别
  13. c语言基础课程包括啥,推荐收藏!C语言入门基础知识大全
  14. 好架构师都是写代码写出来的
  15. 复旦教授疯狂打车800次,发现大数据杀熟的秘密!附完整调研报告
  16. 基于HAL库的STM32F704的电阻式触摸屏的学习
  17. 《搜索和推荐中的深度匹配》——1.5 近期进展
  18. 多功能纺织品易燃性测试仪市场现状及未来发展趋势
  19. 数据分析真题日刷 | 网易2018校园招聘数据分析工程师笔试卷
  20. 筋斗云教程(一): 运行筋斗云应用

热门文章

  1. Java 获取当前时间并且格式化
  2. Android学习路线的归纳总结,绝对干货!
  3. 神经网络入门最好的一篇文章
  4. OpenFlow Flow-Mod消息学习
  5. java基础面试重点,HR的话扎心了
  6. 安装ansys的mpi
  7. java json float_关于浮点数的json解析
  8. Java多线程优化都不会,怎么拿Offer?
  9. 玩转jpmml之tpot+sklearn2pmml自动化机器学习集成模型部署
  10. 直播邀请函 | 第12届亚洲知识产权营商论坛:共建创新价值 开拓崭新领域