gd32f303 设计中断优先级_和廖老师一起起飞的嵌入式系统设计
嵌入式系统设计要结课了,这课的氛围实属猛的一,学到了很多东西,非常感谢廖老师的教会我们来复习一波
国际班的朋友们,拿笔记复习的时候记得顺手点个赞!!!
![](/assets/blank.gif)
嵌入式系统的四个主要讲点
- 任务管理(调度)
- 同步,信号量,交流(这部分讲的比较少)
- 内存管理 基本没怎么讲
- 中断 时间管理(对于事件的处理方法)
task任务
- 内核的功能是什么?调度让不同的程序分时共享CPU从而达到近似的并行
- 内核如何调度?给每一个执行程序分配一个task
- task是什么?一个可以执行的软件,有代码 数据 计算资源 操作系统当中举的例子是一个计算机科学家要做蛋糕,食谱是代码,剩下的准备材料和做的过程就是task, 从代码的角度来说,task是一个无限循环的void函数,他会在内存当中不断地被调度,比如QQ一直在后台运行,直到你强制退出。 task是一个逻辑上的存在,并且是动态存在的,根据冯诺依曼体系,我们可以看出代码是存 储执行的,即静态的,这也是task和code的主要区别 A task is an executable software entity specialized to perform a special action which includes sequential code and relative data and computer resource A task is an infinite loop function. it looks just like any other C function containing a return type and an argument but it must return. The return type of a task must always be declared to be void
- task的特点是什么?同步 异步 动态性(不是重点)
如何描述一个task?
- 上述定义的描述方法
- 代码描述
![](/assets/blank.gif)
把内部的固定执行函数来看,task就是一个执行的函数,一个无限循环的void函数
- 为什么是无限循环? 因为一个任务要在系统当中不断地调度,而不是用一次就废了
- 为什么返回void,task是独立存在的,不需要为其他task做什么(即便需要也是共享或者通信)
下面我们通过代码进程块来了解一下task进程块在计算机当中的使用(调度)方法。
![](/assets/blank.gif)
![](/assets/blank.gif)
上面两段代码分别是两个task的代码。在循环外的代码主要目的是设置时钟节拍(晶体振荡器),剩下几个是寻找对应的引脚来控制指定引脚灯的开关(初始化)
A控制开灯,B控制关灯。
在μcOS操作系统当中维护着几个队列
- 就绪队列:等待执行的task,等待现在正在执行的task退出然后即可进入
- 延迟队列:延时的时间节拍不为0,暂时不能执行
为了防止CPU空转,在任务管理器当中设置一个空任务IDLE,简单讲就是占着茅坑不拉屎,等着别人去占用他。
有了这些基础知识,我们来看这两个任务如何工作的8
- 什么时候这些task被创建?操作系统当中有两种模式:核模式和运行模式(这个词我给搞忘了)只有在核模式下才可以创建任务。一般情况下,时间中断(也同样是个task,完成调度的任务)会打断运行进入核模式,创建任务
- 在哪被创建?在μcOS当中,为了保证时间复杂度的确定性,我们没有虚拟内存,都在内存当中被创建,放入就绪队列当中。
- 如何启动任务执行? 时间中断的task会在就绪队列当中选择优先级最高的task出队执行
在第一个时间片当中,A从就绪队列当中出队,执行,OSTimeDLy让他进入延迟队列当中,然后IDLE任务进入。
第一个时间片结束,时间中断任务进入,把所有延迟队列当中内容OSTimeDLy减1,选择就绪队列当中第二个任务执行。
二到九时间片都是IDLE空转,直到第10个时间片的时候,A的OSTimeDLy变为0,移入就绪队列当中再次执行。(我觉得讲清楚了)
所以灯是10个时间片一闪,再亮10个时间片的循环 一个时间片可以完成多个任务 上一个结束就会启动新的调度
task 和 process的区别是什么
![](/assets/blank.gif)
![](/assets/blank.gif)
我们还是通过上面的例子来说,上面两个例子两次的输出都是什么?
1 还是 2?
答案是task是1 process是2
task某种程度上可以理解为线程,他不像进程一样会产生阻塞。
task的控制核心->TCB描述任务
前面我们提到的task都是一个大的概念,这里我们通过细节的分析这个数据结构来掌握任务的管控情况。
除去额外的成员,TCB的组织基本如下(后面是占用字节数目的大小)
OS_STK *OSTCBStkPtr 4(堆栈指针) ptos
Os_tcb* OSTCBNext 4(双向链表的下一个)
Os_tcb* OSTCBPrev 4 (上一个)
Unsigned short OSTCBDly 2 (时延) 0
Unsigned char OSTCBStat 1 (状态) OS_STAT_RDY
Unsigned char OSTCBPrio 1 (优先级) (INT8U)prio
Unsigned char OSTCBX 1
Unsigned char OSTCBY 1
Unsigned char OSTCBBitX 1
Unsigned char OSTCBBitY 1
后面四个我们逐一来介绍
OSTCBNext 和 OSTCBPrev
这两个成员揭示了任务的存储方式即双向链表
为了方便,我们在boot初始化的时候就创建所有的TCB,64个(分别对应64个优先级)前两个是系统的优先级(用于处理紧急事件,比如掉电),后两个就是IDLE和系统监测(比如CPU温度)
所有的TCB都通过一个双向链表相连,称为TCBFreeList。如果TCB被某个对应优先级的task占用,就从Freelist到TCBList当中,代表正在使用的TCB。
显而易见,双向链表虽然能自适应的调整空间,但是查询时很慢的,所有系统当中还维护一个优先级队列(实质上是一个数组) 数组下标和优先级一一对应。
OSTCBStat
如果一个系统想要运行起来,就必须有一个完整的没有死状态的状态循环表,μcOS如下
![](/assets/blank.gif)
这里可以看到我们之前提到的timetick(延迟队列到ready队列)还有吧啦吧啦一丢就不多说了,重点在于标识状态
OSTimeTick就是我们之前对应的时间中断task,每一次发出信号
OSTCBX OSTCBY OSTCBBitX OSTCBBitY
ptcb->OSTCBY=prio>>3 向右移动三位
ptcb->OSTCBBitY=OSMapTBI[ptcb->OSTCBY] 对应表当中的映射
ptcb->OSTCBX=prio & 0x07 对应低三位的与运算
ptcb->OSTCBBitX = OSMapTbpptcb->OSTCBX]
![](/assets/blank.gif)
为了加速对最高优先级的查询过程,系统当中设计了一个数据结构标定任务是否被占用
这个方阵当中,如果被占用则为1,未占用则为0,(这里不是二维数组,而是一个数的不同位!!!)
PriorityReadyTable有8个数字,每个表示这一行当中的8个优先级是否有被占用
PriorityReadyGroup只有1个,8位,如果PriorityReadyTable的任意一项全部为0,则置0,否则为1.即检测该行是否有优先级被占用。
OSTCBX和OSTCBY分别对应着prio的高三位和低三位,也就分别对应着这个类似二维数组的两个索引。我们可以通过X和Y进行访问当前节点
OSTCBBitX OSTCBBitY的内容出现的目的是为了快速访问,也就是变成掩码的形式
![](/assets/blank.gif)
通过这两个掩码可以快速的访问自己的占用状态,并改变。
所以建立这个表有什么用?通过优先级位图法来快速查找最高优先级是多少
优先级位图法最重要的思想就是空间换时间。构造一个查找表如下
![](/assets/blank.gif)
priorty table对应的是不同的值的,256个分别代表着8位数的不同情况,来找到最大的1的位置。
当我们要寻找最高优先级的时候
- 以priortyReadyGroup为下标访问priortyDecisionTable的内容,迅速得到最高优先级所在的table行(高三位)
- 以priortytable[i]为下标访问priortyDecisionTable的内容得到最高优先级所在的位置(低三位)
- 通过得到的优先级索引,迅速访问优先级队列,调用对应的TCB进行执行
任务的创建
介绍完最重要的TCB,我们来了解一下任务是如何创建的
INT8U OSTaskCreate (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos,INT8U prio)
{OS_STK *psp;
#if OS_CRITICAL_METHOD == 3 OS_CPU_SR cpu_sr = 0;
#endifOS_ENTER_CRITICAL(); if (OSIntNesting > 0) {OS_EXIT_CRITICAL(); //如果发现是在嵌套当中则退出,不允许在嵌套当中进入临界区return (OS_ERR_TASK_CREATE_ISR);}
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) {//占用对应的TCB,标识优先级的占用OSTCBPrioTbl[prio] = (OS_TCB *)1; OS_EXIT_CRITICAL();
psp = (OS_STK *)OSTaskStkInit(task, pdata, ptos, 0); //堆栈初始化
err = OS_TCBInit(prio, psp, (OS_STK *)0, 0, 0, (void *)0, 0);//TCB初始化if (err == OS_NO_ERR) {OS_ENTER_CRITICAL();OSTaskCtr++; //记录task的数目 OS_EXIT_CRITICAL();if (OSRunning == TRUE) { OS_Sched();//创建后调度新的任务//如果创建任务直接去调度,效果如下}
//如果中间出现意外,则取消占用,把TCB的占用位置0else {OS_ENTER_CRITICAL();OSTCBPrioTbl[prio] = (OS_TCB *)0;OS_EXIT_CRITICAL();}return (err);}OS_EXIT_CRITICAL();return (OS_PRIO_EXIST);
}
Task的三个主要组成部分也就是TCB (由prio得到) stack code,也就是函数传入的三个参量
下面我们来重点介绍一下stack 和 TCB的初始化过程
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *p_arg, OS_STK *ptos, INT16U opt)
{OS_STK *stk; opt = opt; stk = ptos; *(stk) = (OS_STK)task; /* Entry Point task对应的PC应该指向的第一个位置*/ *(--stk) = (INT32U)0; /*LR */ *(--stk) = (INT32U)0; /*R12*/ *(--stk) = (INT32U)0; /*R11*/*(--stk) = (INT32U)0; /*R10*/*(--stk) = (INT32U)0; /*R9*/*(--stk) = (INT32U)0; /*R8*/*(--stk) = (INT32U)0; /*R7*/*(--stk) = (INT32U)0; /*R6*/*(--stk) = (INT32U)0; /*R5*/*(--stk) = (INT32U)0; /*R4*/ *(--stk) = (INT32U)0; /*R3*/*(--stk) = (INT32U)0; /*R2*/*(--stk) = (INT32U)0; /*R1*/*(--stk) = (INT32U)p_arg; /*R0存放传出的参量,即return*/ *(--stk) = (INT32U)0x 0x00000013L; /PSW cpsr/ return (stk);
}
看起来是不是很简单,堆栈的初始化实质上是模仿在保存现场的压栈过程。
内部存放的是所有寄存器的信息
传入的task (entry point)为对应的PC指针,指示代码的位置
LR指向调用该task的task的指令,用于还原。R12到R0是通用集存器
CPSR和PSW相同,表示状态,对所有的内容进行保存
模拟压栈的内容同样的顺序保证了我们第一次还原现场的时候顺序是相同的。
INT8U OS_TCBInit (INT8U prio, OS_STK *ptos, OS_STK *pbos, INT16U id, INT32U stk_size, void *pext, INT16U opt)
{
#if OS_CRITICAL_METHOD == 3 OS_CPU_SR cpu_sr;
#endif OS_TCB *ptcb;OS_ENTER_CRITICAL();ptcb = OSTCBFreeList; if (ptcb != (OS_TCB *)0) {OSTCBFreeList = ptcb->OSTCBNext; OS_EXIT_CRITICAL();ptcb->OSTCBStkPtr= ptos; ptcb->OSTCBPrio = (INT8U)prio; ptcb->OSTCBStat = OS_STAT_RDY; ptcb->OSTCBDly = 0; pext = pext; stk_size = stk_size;pbos = pbos;opt = opt;id = id;ptcb->OSTCBY = prio >> 3; ptcb->OSTCBBitY=OSMapTbl[ptcb->OSTCBY];ptcb->OSTCBX =prio & 0x07;ptcb->OSTCBBitX=OSMapTbl[ptcb->OSTCBX]; OS_ENTER_CRITICAL();OSTCBPrioTbl[prio] = ptcb;ptcb->OSTCBNext = OSTCBList; ptcb->OSTCBPrev = (OS_TCB *)0;if (OSTCBList != (OS_TCB *)0) {OSTCBList->OSTCBPrev = ptcb;}OSTCBList = ptcb;OSRdyGrp |= ptcb->OSTCBBitY; OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;OS_EXIT_CRITICAL();return (OS_NO_ERR);}OS_EXIT_CRITICAL();return (OS_NO_MORE_TCB);
}
既然我们已经介绍了保护的模拟,那么接下来就直接来介绍压栈的全过程8
中断的保护现场和还原现场
![](/assets/blank.gif)
首先从这张图说起,看看整个task的组织结构。
OSTCBCur指向当前执行的TCB,TCB的第一个单元为SP,指向我们的堆栈集存器,堆栈寄存器的第一个元素指向PC(code)
- 为什么SP是TCB的第一个元素?因为TCB的元素在上下文切换这种要求速度的场景转化要求不是很大,而SP是一定要改变的。SP从原来的栈底(没有任何元素到保存了所有的寄存器) 在实际实现的汇编当中,如果要寻找数据结构当中给出了第一个元素的任何元素都要对原指针进行移动,进行额外的操作,把常用的单元SP放在第一位可以快速的进行直接的写入
- 为什么PC是SP的第一个元素?在保护现场的过程当中,我们实质上还是要执行指令,这会导致PC出现偏差,在下一次还原的时候,不是本次保护的位置
现场保护的汇编代码
OSCtxSw:STMFD SP!, {LR} ;PCSTMFD SP!, {R0-R12, LR} ;R0-R12 LR MRS R0, CPSR ;Push CPSR STMFD SP!, {R0} ;----------------------------------------------------------------------------------; OSTCBCur->OSTCBStkPtr = SP;---------------------------------------------------------------------------------- LDR R0, =OSTCBCurLDR R0, [R0]STR SP, [R0]BL OSTaskSwHook
第一段代码代表了压栈的过程,
- PC为什么用LR保存?注意我们保存PC的时候,保存的是LR的值,这是因为在软中断当中LR和PC的值一定是相同的
- CPSR为什么不能直接保存?CPSR是一个特殊的寄存器,我们无法直接载入到内存当中,而是借用R0 简单来说就是CPSR不能被直接操作
- PC不应该保存PC+1的信息嘛,为什么只保存本身?由于指令是在主存当中,需要访问外总线,而CPU的操作在内总线当中从而出现了性能瓶颈,所以取址采用流水线的方式,一次取出多条指令。这我们在后面的中断响应当中介绍
;---------------------------------------------------------------------------------; OSTCBCur = OSTCBHighRdy;;----------------------------------------------------------------------------------LDR R0, =OSTCBHighRdyLDR R1, =OSTCBCurLDR R0, [R0]STR R0, [R1];---------------------------------------------------------------------------------; OSPrioCur = OSPrioHighRdy;;---------------------------------------------------------------------------------LDR R0, =OSPrioHighRdyLDR R1, =OSPrioCurLDRB R0, [R0]STRB R0, [R1] ;----------------------------------------------------------------------------------; OSTCBHighRdy->OSTCBStkPtr;;----------------------------------------------------------------------------------LDR R0, =OSTCBHighRdy LDR R0, [R0]LDR SP, [R0]
在保存现场过后,我们的R0和R1就解放了,我们用他们去访问不同地方的地址来进行数据上的交换(主要原因是在汇编这种机器语言当中,地址和内容一直是分开存在的,和高级语言不同) 都是先用LDR载入地址 再LDR R0, [R0] 来把地址当中值取出
同样注意,直接导出OSTCBHighRdy导出的就是SP,因为是第一个元素
;----------------------------------------------------------------------------------;Restore New task context;----------------------------------------------------------------------------------LDMFD SP!, {R0} ;POP CPSR MSR SPSR_cxsf, R0 LDMFD SP!, {R0-R12, LR, PC}^
最后我们当新的任务的上下文载入到寄存器当中,和入栈相反,CPSR第一个被导出,同样必须要借助R0的帮助
上下文切换可以认为是一种软中断,是时钟中断到来的时候做的,但是如果我们按下键盘?发射导弹?硬中断,我们应该如何处理?
软中断主要是由于时钟中断产生的(中断应该泛指上下文交换)
硬件中断
前面在创建task的时候,中断方式的选择我们当时并没有说
INT8U OSTaskCreate (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos,INT8U prio)
{OS_STK *psp;
#if OS_CRITICAL_METHOD == 3 OS_CPU_SR cpu_sr = 0;
#endif
。。。。。。。。
那么这个中断方式是个什么东西呢?
#if OS_CRITICAL_METHOD==1 #define OS_ENTER_CRITICAL() (Cli()) #define OS_EXIT_CRITICAL() (Sti()) #elseif OS_CRITICAL_METHOD == 2 #define OS_ENTER_CRITICAL() (PushAndCli()) #define OS_EXIT_CRITICAL() (Pop()) #elseif OS_CRITICAL_METHOD == 3 #define OS_ENTER_CRITICAL() (cpu_sr = OSCPUSaveSR())#define OS_EXIT_CRITICAL() (OSCPURestoreSR(cpu_sr))
#endif
三种方式的表达如下
方式1:关中断
Cli MRS R0,CPSR ORR R1,R0,#0x80 MSR CPSR_c,R1MOV PC,LR StiMRS R0,CPSRAND R1,R0,#0xffffff7f MSR CPSR_c,R1MOV PC,LR
直接在CPSR的基础上把中断INTA直接关掉,在关掉的时候直接开启。
这种方式不支持嵌套,因为如果嵌套两层,内部置1就会导致外部的混乱,在第一层就会被打开导致代码的错乱
方式2:堆栈保存
PushAndCliMRS R0,CPSR_c STMFD sp!,R0 ORR R1,R0,#0x80 MSR CPSR_c,R1 MOV PC,LRPopLDMFD sp!,R0MSR CPSR_c,R0
为了应对上面出现的问题,创建一个堆栈把所有的状态进行表达,然后把SP出栈。但这种方式是否会存在问题?在嵌套次数过多的时候SP保存的内容过多。
方式3 TCB保存:
OSCPUSaveSRMRS R0, CPSRORR R1, R0, #0x80 MSR CPSR_c, R1MOV PC, LR OSCPURestoreSRMSR CPSR_c, R0MOV PC, LR
把内容保存在每个堆栈当中进行保存,不用一个统一的堆栈进行保存。即每个TCB保存之前的状态(感觉这里说的好像有点小问题) 其实就是构造一个类似链表的东西
在了解了三种方法之后,我们紧接着来看对于硬件中断的处理方式。
![](/assets/blank.gif)
硬件中断的入口如下,系统首先要预留一些中断。比如0x00就是我的最爱,毕竟重启能解决50%的问题(狗头)。
注意,这里所有的地址都是和硬件相关,即硬件产生的中断会找到对应的位置,从而转到对应的中断服务程序。 比如电脑的USB接口就会和当中的硬件中断相对应
![](/assets/blank.gif)
事实上,中断向量和中断服务是完全分开的,相当于一个硬件和软件之间存在一个接口,中断对应的一个指令直接把PC转移到对应的中断服务程序,vector table和硬件中断存在一个对应关系 分别指向对应的引导程序当中
中断流程
- 中断判优 采用逻辑电路对多个中断进行优先级判定
- 根据中断信号产生中断向量
- 利用中断向量产生中断向量表
- 把PC转移到对应位置
- 中断返回在哪里?根据LR的记录,返回到中断前程序的PC+1
- 如何区分不同的中断?每一个中断都会产生中断向量,通过中断向量访问中断向量表
- 是每一个中断向量都有一个地址嘛? 这肯定是啊
如何响应中断
响应中断的IRQ程序如下
IRQ LDR r0,=INTOFFSET LDR r0,[r0] LDR r1,=HandleEINT0LDR pc,[r1,r0 lsl #2]
PC=(基地址+偏移量)当中的内容,把PC进行转移对应的位置
当然PC转移前,必要的工作是保护现场,硬件中断应该如何保护现场呢?
sub lr,lr,#4 stmfd sp!,{r0.r12,lr} mrs r0,spsrstmfd sp!,{r0} ldr r0,=INTOFFSET ;10: 0X28ldr r0,[r0]ldr r1,=HandlerEINT0 ;0x33FFFF20add r1,r1,r0,lsl #2 ldr r1,[r1] mov lr,pc mov pc,r1//在这后面的指令 只有返回的时候再进行执行 还原所有的现场内容ldmfd sp!,{r0}msr spsr_cxsf,r0ldmfd sp!,{r0.r12,lr}movs pc,lr
首先解决第一个问题,为什么是pc - 4?由于指令是在主存当中,需要访问外总线,而CPU的操作在内总线当中从而出现了性能瓶颈,所以取址采用流水线的方式如下
![](/assets/blank.gif)
四条取址线不断地继续执行,所以我们每次读出来都是3-4条指令,只有-4才能落到吓一跳指令地位置,当然如果一次load4条指令,就应该-8,这看情况而定。
保存了PC我们再来看后面的操作
- 保存现场,和上下文切换相同
- 引入上文的INT转入
- 回复现场(由于PC已经是下一条指令,则直接载入)
前提知识ARM处理器有IRQ和SVC两种模式,每个模式有自己的SP和LR,所以转化模式其实对应的是不同的寄存器。这里调度出现的问题是什么了,(我太菜了)貌似是解决正在执行的任务的问题 多了一个sp和pc寄存器
两种模式在有操作系统的模式下保证硬中断和软中断能同时保证执行(一套寄存器内容很多时候是不够用的)
我们提出新的IRQ的方法
IRQstmdb sp!,{r0-r2} ;sp_irq mov r0,sp add sp,sp,#12 sub r1,lr,#4 ;lr_irq mrs r2,spsr
- 把r0-r3三个寄存器内容存入sp当中,这三个寄存器得到解放
- r0存放这只有三个寄存器的sp的栈顶,然后把原栈顶复原
- 把pc的值保存到r1当中(和上文的流水线相同)
- 把cpsr写入r2当中
msr cpsr_cxsf,#SVCMODE|NOINT stmdb sp!,{r1} ;sp_svc stmdb sp!,{r3-r12,lr} ldmia r0!,{r3-r5} stmdb sp!,{r2-r5}
- 转化到SVC模式
- 把r1(存放的pc)压栈
- 压入各种内容
- 把r0当中指向堆栈的内容传入到r3-r5当中
- r2-r5压栈(r2存放的cpsr)
ldr r0,=OSIntNesting ldr r1,[r0]add r1,r1,#1 strb r1,[r0]teq r1,#1bne %F1 ldr r0,=OSTCBCur ldr r0,[r0]str sp,[r0] SVC存放之前的内容
- 这个OS int nesting主要考虑的是中断嵌套
- 然后利用r0把当前的sp转化到r0当中
msr psr_c,#IR QMODE|NOINTldr r0,= INTOFFSET ;10: 0x28ldr r0,[r0]ldr r1,=HandleEINT0 ;0x33FFFF20mov lr,pc //把当前的pc存入lr当中 ldr pc,[r1,r0,lsl#2] //转到当前的程序msr cpsr_c,#SVCMODE|NOINT bl OSIntExit //退出中断模式ldr r0,[sp],#4 //把cpsr弹出来msr spsr_cxsf,r0 ldmia sp!,{r0-r12,lr,pc}
- 转化到IRQ模式
- 访问中断向量表得到新的中断服务程序PC
- 转化为SVC模式,然后退出中断(中间执行中断服务程序)
void OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3OS_CPU_SR cpu_sr;
#endifif (OSRunning == TRUE) {OS_ENTER_CRITICAL();if (OSIntNesting > 0) { OSIntNesting--;} If ((OSIntNesting == 0) && (OSLockNesting == 0)) {
OSIntExitY= OSUnMapTbl[OSRdyGrp];OSPrioHighRdy=(INT8U)((OSIntExitY<<3)+ OSUnMapTbl[OSRdyTbl[OSIntExitY]]);if (OSPrioHighRdy != OSPrioCur) {OSTCBHighRdy= OSTCBPrioTbl[OSPrioHighRdy];OSCtxSwCtr++;/* Increment context switch counter */OSIntCtxSw(); /* Perform a context switch */} } OS_EXIT_CRITICAL();}
}
含冰冰:手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务调度策略(四)zhuanlan.zhihu.com
![](/assets/blank.gif)
中断的退出同样也是一个不可或缺的部分。中断退出会有很多种可能性。关键在于,在执行中断的过程当中,之前执行的任务很可能就不是最高优先级了,这里相当与解决了被中断打断的上下文切换问题。
- 如果是嵌套,则嵌套--,还原上一个的现场,不做什么操作
- 如果不是,则用优先级位图法找到最高优先级并替代当前执行TCB(调用上面的OSIntSwCtr) 顺便记录一下到底切换了多少次 当前任务被执行多少次 可能算是一个统计任务8
- 如果还是最高优先级,则什么都不做,返回执行原来恢复上下文的代码
解决了中断的问题过后,我们接着从一个更宏观的视角来看调度问题。
调度算法解决的核心是让任务保证实时性
时间管理
时钟控制相当于是一个实时系统的心跳,由晶体振荡器和倍频信号构成 time tick滴滴哒
把任务从就绪队列当中移除的做法如下,利用OSTimeDly函数来把任务从就绪队列当中移除
void OSTimeDly (INT16U ticks)
{ #if OS_CRITICAL_METHOD == 3OS_CPU_SR cpu_sr;#endif if (ticks > 0){ OS_ENTER_CRITICAL();if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) { OSRdyGrp &= ~OSTCBCur->OSTCBBitY; //判断ready group是否位0 也就是这一行除了我还有没有为0的项 }OSTCBCur->OSTCBDly = ticks; OS_EXIT_CRITICAL();OS_Sched(); //退出后继续进行调度}
}
中间那两行代码的确是不是很好理解,首先根据OSRdyTbl查询到对应的任务所在行,然后根据X找到除了X外的·所有位置信息,如果是0的话就意味着这一行的rdyGrp应该清零了。
以及对于时钟信号的代码 对于每一个进程都要进行讨论 减少timeDly的数值
void OSTimeTick (void)
{
#if OS_CRITICAL_METHOD == 3 OS_CPU_SR cpu_sr;
#endif OS_TCB *ptcb;if (OSRunning == TRUE) { ptcb = OSTCBList; //对于整个列表当中所有的64个process进行遍历while (ptcb->OSTCBPrio != OS_IDLE_PRIO) { //到最后一个就直接退出了OS_ENTER_CRITICAL();if (ptcb->OSTCBDly != 0) { if (--ptcb->OSTCBDly == 0) //这一步已经完成了--的操作了{ //如果dly变成0 转会就绪队列当中if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY){OSRdyGrp |= ptcb->OSTCBBitY; OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;} else { ptcb->OSTCBDly = 1;//如果保持在延迟队列当中 等待着下一个timetick再出来} }}ptcb = ptcb->OSTCBNext; OS_EXIT_CRITICAL();}}
}
任务调度算法
任务的分类:
- 周期任务(定期发生,比如雷达检测)
- 非周期任务(突然性任务,偶尔会发生,发射导弹)
- sporadic任务(介于两者之间,虽然不周期性,但是会偶然发生)比如在高速上时不时会出现超速报警,时常发生却没有周期。
首先我们先来介绍一些基本描述在系统当中任务的表达量
- Task number: n 任务个数
- Task: Ti ( i=1,2,…,n ) 每个任务的指代
- Period: Pi 每个任务的周期数即多长时间执行一次
- Executing time: ei 任务需要的和执行时间
- Relative deadline: Di 相对的截止时间 由于按照周期执行,表示某一次周期运行任务的截止时间相对上一次周期运行任务截止时间
- Absolute deadline: di 绝对的截止时间 每次都必须在一个给定的时间完成
- Phasing: Ii 初始相位这个参量在初始化的时候有点用,这里我们就不多考虑
然后我们来介绍RM算法
RM算法有严格的限制条件
- 所有任务是周期任务
- 任务相对截止时间等于任务周期
- 任务在每个周期内执行时间都是相等的
- 任务独立 不访问共享资源 (可以随时被抢占)
某种程度上来说,确定优先级的大小某种程度上就会决定算法的调度方式。
优先级的确定可以回答下面的几个问题(即如何完成分时共享)
- 哪个任务被执行
- 什么时候任务开始执行
- 任务执行多久
RM算法确定的方法是周期越长,优先级越低
在RM当中任务占用率没有达到100%,但是不能说没有全部占用,因为占用率是通过每个单独的任务来说的。
![](/assets/blank.gif)
我们可以看到这里的利用率比U要高,如果我们把2的T减小一点,达到全部占用(无idle也不是不可能的)
![](/assets/blank.gif)
对于RM算法,任务利用率的上限如下即为
关于伟大的RM算法,和廖老师一起来膜拜一下原文8,火星探测的故事
![](/assets/blank.gif)
RM算法遇到的问题
我们放宽之前的假设条件,互斥资源访问是什么样子的呢
问题:优先级反转
![](/assets/blank.gif)
在这个例子当中优先级排队为T1>T2>T3
- T3最先到达,并执行
- T1到达,打断
- 由于T1无法访问互斥资源还给T3
- T2到达,打断T3
- T3访问完共享资源,T1访问
这里的问题就是由于T1无法访问共享资源,导致T2优先级虽然低但是会先完成,会给算法带来不确定性。
解决方案:优先级继承
在T1访问贡献资源失败的时候,把T3优先级临时转化为T1,防止T2的抢夺
执行过程改变为
![](/assets/blank.gif)
如果有更高的优先级也想访问共享区,就不断地升高T3地优先级。但这会带来新的问题:死锁
死锁的来临:
![](/assets/blank.gif)
由于T2会继承T1的优先级,在他得到T1的优先级后,会试图抢占T1的资源S1,但是优先级相同抢不到,导致陷入纠缠的阶段。
在windows当中基本就按照先到先服务来做(由于实时性要求不是很高)
解决方案:优先级天花板
前面我们说过优先级继承在遇到新的更高的优先级抢占会接着继承。优先级天花板就干脆调到占用这个互斥资源的最高优先级.这样就没人敢抢我了。
![](/assets/blank.gif)
![](/assets/blank.gif)
放宽条件 存在非周期任务
RM算法会直接给非周期任务预留空间,等待被访问,如果没有提前进入下一个周期循环。
这显然不给力,所以产生新的的算法DF和EDF
但问题在于他在每个时刻不断计算哪个ddl是最近的,这导致计算消耗的资源过多而导致很少使用这种算法。
to be continue
互斥资源访问
1.二值信号量完成互斥访问
2. 多重不能完成互斥访问,但是可以完成优先级继承
3.计数的方法,生产者和消费者
任务的同步
通过二值信号来实现
- OSSemCreate(0)
- T1进入调用OSSempend() 判断发现无法执行 把就绪队列移到等待队列
- T2调用OSSempost()(告知T2执行完了,T1可以执行,把T1唤醒)
- T1进入调用OSSempend() 执行
信号量只能实现两个之间的同步
event可以实现同步
必须把三个任务执行完了才能实现下一个,保证任务都执行完了才能执行
事件的实现,集体的去调用AND和OR操作,和多个信号量其实相似
接着有关信号量
OS_EVENT *OSSemCreate (INT16U cnt)
{OS_EVENT *pevent;pevent = OSEventFreeList; //Get next free event control block(ECB)if (OSEventFreeList != (OS_EVENT *)0) { //See if pool of free ECB pool was emptyOSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;}if (pevent != (OS_EVENT *)0) { //Get an ECB and initialize itpevent->OSEventType = OS_EVENT_TYPE_SEM; //Event type is Semaphorepevent->OSEventCnt = cnt; //Set semaphore valuepevent->OSEventPtr = (void *)0; // Unlink from ECB free listOS_EventWaitListInit(pevent); //Initialize to 'nobody waiting' on Sem}return (pevent);
}
EventFreeList当中存储的是被阻塞的内容,在接受到对应的信号量之后,依次进行释放
操作系统是否支持信号量、消息队列,是否进行配置在OS_CFG.H进行修改,来确定信号量的选择
关于互斥信号量和二值信号量的区别:
互斥性信号量必须是同一个任务申请,同一个任务释放,其他任务释放无效,同一个任务可以递归申请
二进制信号量,一个任务申请成功后,可以由另一个任务释放
互斥量:互斥量表现互斥现象的数据结构,被当作二元信号灯,互斥本身是一个多任务的敏感的二元信号,用作同不多任务的行为,保护从中断来临界段代码和共享同步的资源 本质上是一把锁,提供队资源的独占访问
信号量:信号灯,在多线程环境下的一种设施,协调各个线程正确合理使用公共资源
- 二进制信号量 只允许信号量0 1 被一个线程获得
- 整型信号量 被多个线程同时获得,直到信号量值变为0]
Application specific
specialize and optimize the design for specific application
Ostimedelay 进行调度 最后一句话就是OSschdule
中断事件设置抢占点,高优先级进行占用
关于第一张的内容,我们还有很多没有说的,这里说一下embedded system
- smart sensors embedded in the environment
- pervasive computing is the trend towards increasingly ubiquitous, connected with each other
RTS: Any system where a timely response by the computer to external evenet is vital is a real time system
- hard RTS : something very bad will happen if not deliver the information before deadline
- soft RTS: if some deadline are missed, it is not a big problem
what are ERTS specifc
spacialize and optimiaze the design for specific application
- Reak-time
- memory
- power
- cost
- reliability (security safety) security protection from unauthorized access keep the system from dangerous
software architecture of ERTS
- polling loop
- foreground and background(interrupt)
- multi task
gd32f303 设计中断优先级_和廖老师一起起飞的嵌入式系统设计相关推荐
- gd32f303 设计中断优先级_煤矿液压支架CAN总线监控系统的设计
针对刨煤机组中液压支架多而分散.系统信息量大等特点,基于CAN协议总线设计了液压支架分布式监控系统,上位机监控端可通过CAN总线和液压支架控制器进行通信,实现多液压支架的分散安装和集中控制功能,介绍了 ...
- gd32f303 设计中断优先级_ALIENTEK 阿波罗 STM32F767 开发板资料连载第九章 外部中断实验...
1)实验平台:alientek 阿波罗 STM32F767 开发板2)摘自<STM32F7 开发指南(HAL 库版)>关注官方微信号公众号,获取更多资料:正点原子 第九章 外部中断实验 这 ...
- 廖的python教程_学廖老师的python教程想到的
929900276大佬: 看到了生成器一节,要生成杨辉三角 我就自己想了下,其中有个小分解动作,我就准备写个函数:也就是如果给定一个列表: 举例来说这个列表是,1,2,3,4,我想两两相加,得到3,5 ...
- HTML5汽车网页设计成品_学生DW汽车静态网页设计代做_web课程设计网页制作_宽屏大气汽车自驾游网站模板html源码...
HTML5汽车网页设计成品_学生DW汽车静态网页设计代做_web课程设计网页制作_宽屏大气汽车自驾游网站模板html源码 临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大?HTML ...
- HTML5汽车网页设计成品_学生DW汽车静态网页设计代做_web课程设计网页制作_宽屏大气汽车自驾游网站模板html源码
HTML5汽车网页设计成品_学生DW汽车静态网页设计代做_web课程设计网页制作_宽屏大气汽车自驾游网站模板html源码 临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大?HTML ...
- html网页制作期末大作业成品_新疆旅游网页设计作品_dreamweaver作业静态HTML网页设计模板_新疆旅游景点网页作业制作
html网页制作期末大作业成品_新疆旅游学生网页设计作品_dreamweaver作业静态HTML网页设计模板_新疆旅游景点网页作业制作 临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到 ...
- HTML5期末大作业:鲜花超市网站设计——鲜花超市(4页) HTML+CSS+JavaScript HTML5网页设计成品_学生DW静态网页设计代做_web课程设计网页制作
HTML5期末大作业:鲜花超市网站设计--鲜花超市(4页) HTML+CSS+JavaScript HTML5网页设计成品_学生DW静态网页设计代做_web课程设计网页制作 作品介绍 1.网页作品简介 ...
- python通用权限管理框架图_通用权限管理设计篇_设计模式
摘要: 本文讲的是通用权限管理设计篇_设计模式, 博客地址:http://www.blogjava.net/amigoxie/ 一.引言 因为做过的一些系统的权限管理的功能虽然在逐步完 ...
- b端 ux 设计思维_借助系统思维从视觉设计过渡到UX
b端 ux 设计思维 "How can I switch to UX?" This is a common question from visual designers becau ...
最新文章
- mysql 不让读的锁_MySQL实战45讲阅读笔记-锁
- Linux添加新硬盘-挂载硬盘,设置开机自动挂载 解决/home 空间不足问题
- Android中Dialog对话框
- python进程socket通信_python3 进程间通信之socket.socketpair()
- 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_04 IO字节流_2_一切皆为字节...
- printf输出格式总结
- 十大关系数据库SQL注入工具一览
- 我对“结构化思维”的理解 - 直播分享
- M - Maratona Brasileira de Popcorn(SDUT 2019 Autumn Team Contest 6th)
- Gom传奇引擎的微端连不上的原因是什么?附:微端配置教程
- 使用Python写俄罗斯方块,以游戏的方式学习编程
- 2018年7月18日日报
- 游戏夜读 | 写游戏用什么语言?
- 第十一届蓝桥杯既约分数 Java
- 【网络进阶】网络问题排查实例集锦(实战经验分享)
- Android设置状态栏字体深色,Android实现修改状态栏背景、字体和图标颜色的方法...
- 华为云桌面客户端_华为云CloudIDE的前世今生
- Android Studio代理
- 测试电脑GPU性能代码
- 闪讯客户端 linux,建议增加浙江的变态闪讯的认证客户端,和锐捷一样变态
热门文章
- 基于JavaGUI的校园卡自助服务系统
- 用mdx-deck和Now打造酷炫的在线幻灯片
- 用计算机弹出黎明的黑暗,STEAM打开黎明杀机启动游戏后弹出计算机丢失msvcp140period;dllperiod; | 手游网游页游攻略大全...
- 算法可爱小问题-探讨
- ADB命令大全(adb命令获取签名证书信息)
- 江湖版《为徐敬业讨武曌檄》
- 将activity设置成窗口模式
- 读《人人都爱经济学》摘记
- php 声明struct_彻底搞懂PHP 变量结构体
- 网上开店,网店系统的安全更重要