EOS复习笔记by七号
使用教材《嵌入式操作系统》 廖勇 杨霞 主编
PPT截图来自 廖勇老师上课PPT
重点1:优先级位图法
重点2:上下文切换
重点3:中断
参考博客
https://blog.csdn.net/lynnbest/article/details/8852441
https://blog.csdn.net/yimu13/article/details/7259003
https://blog.csdn.net/sunrier/article/details/6555333
七号的见解:
要说优先级位图法,那我们先来理一理关于优先级位图法被用到的来龙去脉。
优先级位图法是在任务调度的时候用到的
所以一开始是创建任务,然后创建任务的代码里有一个TCB的初始化(在任务堆栈初始化过后),在TCB初始化函数里面要完成将刚刚创建的任务挂载到就绪队列里面,所以在 OS_TCBInit()函数里有如下代码段
ptcb->OSTCBY=(INT8U)(prio>>3);
ptcb->OSTCBBitY=OSMapTbl[ptcb->OSTCBY];
ptcb->OSTCBX=(INT8U)(prio&0x07);
ptcb->OSTCBBitX=OSMapTbl[ptcb->OSTCBX];
这四行代码就涉及到了这几个变量
OSTCBY:优先级换算成2进制的高三位
OSTCBX:优先级换算成2进制的低三位
OSTCBBitY:高三位的掩码,换算方法3位2进制数用8位掩码表示,表示几第几位就在第几位置1(例如4=第五位=0001 0000)
OSTCBBitX:低三位的掩码
例如35=00 100 011
所以
OSTCBY=100=4
OSTCBX=011=3
OSTCBBitY=0001 0000
OSTCBBitX=0000 1000
TCB初始化完成后,如果OS一运行就开始调度最高优先级的任务执行,于是就会用到OS_Sched()的函数来寻找最高优先级
为什么要找最高优先级呢?因为任务每次被创建都会被挂载到就绪队列里面,可能当前任务被创建的时候,已经有其他的任务处于就绪队列了,所以才需要调度函数找到最高优先级的任务来执行。
然后任务调度又分成两种情况:
1 主动调度=就是任务主动调用调度函数,根据调度算法调度下一个任务,然后这里就涉及到了任务切换的问题。如果被调度的任务和当前一样,就不切换,否则就切换。、
2 被动调度=常常由事件触发导致的,常见的情况有中断啊、超时啊什么的
因为对与RTOS来说调度是抢占式的,谁高谁上,所以调度的实质就是找到最高优先级任务来执行。
所以OS_Sched()函数在对就绪队列任务进行调度时要做如下的操作:
1 判断是否能调度
2 如果可以调度,找到最高优先级的任务
3 判断是否为当前任务,是则继续执行任务,不是的话就任务切换
于是OS_Sched()函数里有如下代码:
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3 //为cpu状态寄存器分配空间
OS_CPU_SR cpu_sr = 0;
#endif
OS_ENTER_CRITICAL(); //关中断
if (OSIntNesting == 0) { //中断标志为0,未在中断时可以调度
if (OSLockNesting == 0) { //任务调度上锁标志为0,表示没有任务使用了上锁即可以调度
OS_SchedNew(); //找到最高优先级的函数
if (OSPrioHighRdy != OSPrioCur) { //判断当前任务是否为最高优先级
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
OSTCBHighRdy->OSTCBCtxSwCtr++;
#endif
OSCtxSwCtr++;
OS_TASK_SW();
}
}
}
OS_EXIT_CRITICAL();
}
其中找到最高优先级的函数为OS_Sched里面调用的OS_SchedNew()函数,其代码如下
static void OS_SchedNew (void)
{
#if OS_LOWEST_PRIO <= 63
INT8U y;
y= OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3) +OSUnMapTbl[OSRdyTbl[y]]); //y*8+x
#else
INT8U y;
INT16U *ptbl;
if ((OSRdyGrp & 0xFF) != 0) {
y = OSUnMapTbl[OSRdyGrp & 0xFF];
} else {
y = OSUnMapTbl[(OSRdyGrp >> 8) & 0xFF] + 8;
}
ptbl = &OSRdyTbl[y];
if ((*ptbl & 0xFF) != 0) {
OSPrioHighRdy = (INT8U)((y << 4) +
OSUnMapTbl[(*ptbl & 0xFF)]);
} else {
OSPrioHighRdy = (INT8U)((y << 4) +
OSUnMapTbl[(*ptbl >> 8) & 0xFF] + 8);
}
#endif
}
然后终于在这里面用到了我们的优先级位图法!!
上述代码加灰部分就是当任务优先级在63以内的时候通过优先级位图法来得到最高优先级
这一段设计到的几个变量为OSRdyGrp、OSRdyTbl、OSUnMapTbl、OSPrioHighRdy
它们分别的含义和相关代码如下:
OSRdyGrp |= OSMapTbl[prio>>3]; (1)
OSRdyTbl[prio>>3] |=OSMapTbl[prio&0x07]; (2)
(1) 就绪队列里面几个优先级的高三位对应掩码进行或运算得到OSRdyGrp的值
(2) 相同高三位值的对应低三位值对应掩码进行或运算得到OSRdyTbl[]的值
(3) 例如:存在四个优先级分别为6,11,14,35的任务,其优先级的二级制表示分别为:00 000 110、00 001 011、00 001 110、00 100 011,所以OSRdyGrp=00010011 =0x13 OSRdyTbl[0]=0100 0000、OSRdyTbl[1]=01001000 OSRdyTbl[6]=00001000
y= OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
根据查表可以得到:y=0,x=6 所以最高优先级为6,符合我们待选的4个里面的最高优先级。(y= OSUnMapTbl[0x13]=0、x= OSUnMapTbl[OSRdyTbl[y]]= OSUnMapTbl[0x40]=6)
if (OSPrioHighRdy != OSPrioCur) { //判断当前任务是否为最高优先级
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
OSTCBHighRdy->OSTCBCtxSwCtr++;
#endif
OSCtxSwCtr++;
OS_TASK_SW();
}
If条件框就是判断当前任务是否为最高优先级,涉及到了新的变量OSPrioCur、OSTCBPrioTbl[]
然后为实现任务切换,OSTCBHighRdy必须指向优先级最高的那个任务控制块OS_TCB,这是通过将以OSPrioHighRdy为下标的OSTCBPrioTbl[]数组中的那个元素赋给OSTCBHighRdy来实现的
OSTCBCur为一个指向当前任务的任务控制块的指针变量,存放的是任务控制块结构体的地址,等于结构体第1个成员变量的地址。同时由于任务控制块的第1个成员变量是OS_STK *OSTCBStkPtr,所以任务堆栈指针变量的地址值和任务控制块的地址值相同。
所以在我们的优先级位图法里面我们会遇到下面这些变量
OSTCBY、OSTCBX、OSTCBBitY、OSTCBBitX、OSMapTbl
OSRdyGrp、OSRdyTbl、OSUnMapTbl、OSPrioHighRdy
最基本的的优先级位图才刚刚开始,通过上面我们大概可以感受到位图法是在为任务调度服务,所以接着上面的内容,我们继续探寻优先级位图法和任务调度的妙用。
y=ptcb->OSTCBY;
OSRdyTbl[y] &= ~ptcb->OSTCBBitX; //先将列置零
If (OSRdyTBL[y]==0){ //如果列都为零了,行也置零
OSRdyGrp &= ~ptcb->OSTCBBitY;
}
前面的部分已经介绍了两种调度的情况和调度时要发生的事情,讲了如果发生任务切换的话需要将最高优先级对应的TCB给OSTCBHightRdy,然后通过OS_TASK_SW()来执行任务的上下文切换。
前面已经讲了调度前准备和找到最高优先级的办法了(优先级位图法)
所以这一部分我们重点研究0S_TASK_SW()
在说这一部分的时候我们要明白几个基本的概念,什么是上下文切换,什么是任务切换、切换的时候我们要做一些什么操作
任务切换,cpu的执行权从一个任务转移到另一个任务上
上下文:就是指一个任务执行期间用到的寄存器里的值。
上下文切换:cpu被切换到另外一个任务时需要保存当前任务的运行状态并恢复另一个任务的运行状态,当前任务状态改变(就绪、挂起、删除),就绪队列最高优先级的任务变成当前任务。
所以任务切换要做的事情就是:得要进行上下文切换—保存当前任务的环境,恢复要执行任务的环境。
然后0S_TASK_SW()被定义为OSCtxSw() 由汇编代码实现
其代码如下:
OSCtxSw:
STMFD SP!, {LR} ;PC
STMFD SP!, {R0~R12,LR} ;R0~R12,LR
MRS R0, CPSR ;PUSH CPSR
STMFD SP!,{R0}
; OSTCBCur->OSTCBStkPtr=SP
LDR R0, =OSTCBCur
LDR R0, [R0]
STR SP, [R0]
; OSTaskHook();
BL OSTaskHook
; OSTCBCur =OSTCBHighRdy;
LDR R0, = OSTCBHighRdy
LDR R1, = OSTCBCur
LDR R0, [R0]
STR R0, [R1]
; OSPrioCur=OSPrioHighRdy
LDR R0,= OSPrioHighRdy
LDR R1,= OSPrioCur
LDR R0,[R0]
STR R0,[R1]
; SP=OSTCBHighRdy->OSTCBStkPtr;
LDR R0 ,= OSTCBHighRdy
LDR R0, [R0]
LDR SP, [R0]
;恢复新任务的上下文
LDMFD SP!, {R0}
MSR SPSR_cxsf, R0
LDMFD SR!, {R0-R12,LR,PC}^
第一段代码的作用是保存旧任务的任务现场
其中LR被用到了两次,它们有什么不同呢??
两次LR会因为引起任务切换的原因不同而不同
1) 主动引起的任务切换,例如将自己挂起而达到重调度的话,此时保存的都是相等,值得注意的是,前一个LR表示了PC的值,后面一个LR就是它自己。
2) 中断引起的任务切换,例如一个正在运行低优先级的任务被一个高优先级的任务中断,那么低优先级任务会被高优先级任务抢占。而这个时候,第一个LR的值是中断模式下的程序指针PC,而后一个LR保存的就是旧任务的LR。
值得注意的是,因为上文代码是在主动发起的任务切换情况下的,所以一开始的PC的值LR的值
补充:由中断引起的任务切换由代码 OSIntCtxSw 实现,它与OSCtxSw的不同就是OSCtxSw的第一段的旧任务现场保存,原因是因为中断会默认保存,不需要再在代码里面实现一次。
第二段 OSTCBCur->OSTCBStkPtr=SP。是将新的堆栈指针保存在旧任务的OSTCBStkPtr成员里,以便任务恢复执行旧任务现场。
第三段 是一个钩子函数,默认为空,根据需要程序员可以自己添加编写。
第四段 将当前任务的OSTCBCur指向就绪队列中的最高优先级的任务
第五段 将当前优先级(OSPrioCur)设置为就绪队列里面的最高优先级(OSPrioHighRdy)第六段 把就绪队列的最高优先级的任务的堆栈指针(OSTCBStkPtr)送入SP中,用于新任务的现场恢复
第七段 恢复抢占(高优先级)任务的现场
综上所述,任务切换的流程如下(2440处理器的情况)
1) 依次把PC,LR,R12,R11,….R0,CSPR等16个寄存器的值保存到旧任务的任务堆栈里面。保护现场。(从cpu里面搬到堆栈寄存器)
2) 保存好了过后,再把栈顶指针SP传给堆栈成员OSTCBStkPtr,可以看到保存了16个寄存器的值后的SP指向的是最后一个入栈的CPSR,所以最后PSTCBCur->OSTCBStkPtr的值就是SP,即放的指向CPSR的地址
3) 前面两步做好了旧任务的现场保存后,这一步要把高优先级的堆栈地址OSTCBHighRdy->OSTCBStkPtr传给处理器的R13(SP),值得一提的OSTCBStkPtr的内容指向的是堆栈寄存器里CPSR的地址,即SP现在指向了高优先级任务的CPSR
4) 将抢占任务的运行环境从R13(SP)指向的内存地址(第四步保存的)依次恢复到CPU的CPSR,R0,R1,R2,…,R12,LR,PC寄存器中,此时SP指向堆栈的顶部(现在已经空了)为了下一次保护环境使用。注意这个过程结束后SP,和抢占任务OSTCBStkPtr都指向了任务堆栈的栈顶。·
任务调度的上下文切换大概说了一些。我们继续后面的探索。
什么情况下会产生上下文切换呢?
1) OS_Sched()
2) OS_TimeDly
3) INT(中断
4) 时钟中断
5) Lock上锁
6) …
中断的类型:硬中断,自陷,异常
三种常见的处理中断的方式:
1 任务切换(开销大
2 系统调用(实时性不好,因为一个中断发生时会把其他中断屏蔽掉
3 前台任务(保存上下文,判断中断类型,到对应的地方执行中断服务,恢复现场 RTOS
中断机制:
2440上的中断:
2440发生中断时,PC将通过硬件机制跳转到相应异常在异常向量表中的位置开始执行。
然后异常向量表里的汇编指令全是跳转指令,意思是中断服务不会在
向量表进行,而是通过跳转到相应的中断服务程序起始地址开始执行。
我们跟随着PPT的思路来看中断:
首先是一个简单的加法中断
IRQ标示了这一段中断代码的入口
接着实现了一个简单的加法运算
然后开始在原地循环
我们的第一个问题来了:执行完中断操作后我们该回到哪?
我们回到LR-4的地方!为什么不是直接把LR的值给PC而是要减4呢?
因为操作系统存在流水线的原因,在这里我们认为有4条,所以我可以从下图看到
当前任务的指令位置在0x80 下一条指令地址时0x84 但是由于流水线的原因,pc的取值在0x88,所以发生中断的时候 LR的值为0x88,但是我们执行完中断服务程序后返回的地址应该是0x84 所以要LR-4后再赋值给PC
接着第二个问题:怎么区分不同的中断源呢?
看一段新的代码:
IRQ
LDR R0, =INTOFFSET
LDR R0,[R0]
LDR R1,HandleEINT0
LDR SP, [R1,R0 LSL #2]
如图所示的代码实现了对不同中断源的区分
涉及到了两个新的东西,一个是INTOFFSET,一个是HandleEINT0。
INTOFFSET是2440芯片一个用于中断管理等功能的寄存器,发生中断时,用来存放为中断源分配的一个整数,这个整数唯一对应一个中断源。
HandleEINT0代表的是一个内存地址,其内容是对应中断的中断服务函数入口
如图所示,每一种中断服务函数入口的地址为32位占据了4个字节
所以我们在计算每一种中断源对应的服务函数地址的时候会把INTOFFSET左移两位(乘以4)后再与起始地址HandleEINT0相加得到具体的中断服务函数入口地址。
后面就没写了qaq 有缘再补充吧。