概述
RTOS Once More
大三的时候,我也看过有关实时操作系统的论文与程序,并没有太在意这一知识点、没有去深刻的学习有关的知识,显然我是被论文中的一句话所坑了“实时性最好的还是裸机跑程序”,确实。想想我大学四年的开发中无论是学习还是比赛,都是“裸奔”的。由于STM32F103的主频够快,也感觉不到控制输出有延迟。现在当我去做FlightFight游戏时,却实现不了两个对象的分别控制,才想起RTOS。2020/4/19今天是第一天学习,总结一下今天的成果。今天是从文章“建立一个属于自己的AVR的RTOS”开始的。
知识点回顾
专门与堆栈和PC指针打交道的一类指令
rcall 相对调用子程序指令
icall 间接调用子程序指令
ret 子程序返回指令
reti 中断返回指令
ret与reti,都是将堆栈顶的两个字节弹出来送入程序计数器PC中,一般用来从程序或中断中推出。其中reti还可以在退出中断时,重新开启全局中断使能。这些都是学习汇编的时候提到的。
启发
有了相应的知识点基础,并加上RTOS是利用函数地址调用函数的。我们可以模拟一下操作系统调用函数的过程:将函数的地址存到私有堆栈中。当某任务就绪时,将该任务的函数入口地址,给SP,当使用ret 返回时SP的值恢复到了PC中,进而PC指向了某个函数。所以先建立私有堆栈试一试。
void fun1(void)
{
unsigned char i=0;
while(1)
{
PORTB=i++;
PORTC = 0X01<<(i%8);
}
}
unsigned char Stack[100];//建立一个100字节的人工堆栈
void RunFunInNewStack(void (*pfun)(),unsigned char *pStack)
{
*pStack --=(unsigned )pfun;//将函数的地址低位压入堆栈
*pStack --=(unsigned int)pfun>>8;//将函数的地址高位压入堆栈
SP = pStack;//将堆栈指针指向人工堆栈的栈顶
}
int main(void)
{
RunFunInNewStack(fun1,&Stack[99]);
}
该程序的运行过程是:函数RunFunInNewStack()将指向函数的指针的值保存到一个unsigned char的数组Stack中,作为人工堆栈。并且将栈顶的数组传递给堆栈指针SP。因此当用ret返回时,从SP中恢复到PC中的值,就变成了指向fun1()的地址,开始运行fun1()。
问题发现
其一:通常大家都会认为,在任务调度时,当然要将所有的通用寄存器都保存,并且还应该保存程序状态寄存器SREG。然后再根据相反的次序,将任务内容恢复。这是不对的。反复试验用到的寄存器为第一类第二类第三类寄存器。如果在中断函数中有调用其它函数,会在进入中断后,固定的将第一类寄存器和第三类寄存器入栈 ,在退出中断又将他们入栈。
其二:前后台系统,协作式内核系统 ,抢占式内核系统,有何不同?
举个例子:排队打饭。你正在打饭,身后是院长,院长身后是校长。
如果是前后台系统:不管是谁都得排队一个一个来。也就是我们的裸奔系统。
如果是协作式系统:等你打完饭,让校长先吃上饭,院长最后才吃上饭。
如果是抢占式系统:不论是谁在打饭还是排队,只要有更高级别的人在等着,都要第一时间让出来,让最高级别的校长第一个打饭。
实战1--------写一个只有延时服务的协作式内核
#define OS_TASKS 3
unsigned char Stack[200];
register unsigned char OSRdyTb1 asm("r2"); //运行任务就绪表
register unsigned char OSTaskRunningPrio asm("r3");
//正在运行的任务
struct TaskCtrBlock{ //任务控制块
unsigned int OSTaskStackTop;//保存任务的栈顶
unsigned int OSWaitTick;//任务延时时钟
}TCB[OS_TASKS+1];
//防止被编译器占用
register unsigned char tempR4 asm("r4");
register unsigned char tempR5 asm("r5");
register unsigned char tempR6 asm("r6");
register unsigned char tempR7 asm("r7");
register unsigned char tempR8 asm("r8");
register unsigned char tempR9 asm("r9");
register unsigned char tempR10 asm("r10");
register unsigned char tempR11 asm("r11");
register unsigned char tempR12 asm("r12");
register unsigned char tempR13 asm("r13");
register unsigned char tempR14 asm("r14");
register unsigned char tempR15 asm("r15");
register unsigned char tempR16 asm("r16");
register unsigned char tempR17 asm("r17");
void OSTaskCreate(void (*Task)(void),unsigned char *Stack,unsigned char TaskID)
{
unsigned char i;
*Stack --= (unsigned int)Task>>8;
*Stack --= (unsigned int)Task;
*Stack--= 0x00;
*Stack--= 0x00;
*Stack--= 0x80;
for(i=0;i<14;i++)
{
*Stack --=i;
}
TCB[TaskID].OSTaskStackTop=(unsigned int)Stack;
OSRdyTbl|=0x01<<TaskID;
}
void OSStartTask()
{
OSTaskRunningPrio = OS_TASKS;
SP = TCB[TaskID].OSTaskStackTop+17;
asm volatile("reti t");
}
void OSSched(void)
{
asm volatile("PUSH R0 t");
asm volatile("PUSH R1 t");
asm volatile("IN R1,SERGt");
asm volatile("PUSH R1t");
asm volatile("CLR R0 t");
asm volatile("PUSH R18 t");
asm volatile("PUSH R19 t");
asm volatile("PUSH R20 t");
asm volatile("PUSH R21 t");
asm volatile("PUSH R22 t");
asm volatile("PUSH R23 t");
asm volatile("PUSH R24 t");
asm volatile("PUSH R25 t");
asm volatile("PUSH R26 t");
asm volatile("PUSH R27 t");
asm volatile("PUSH R30 t");
asm volatile("PUSH R31 t");
asm volatile("PUSH R28 t");//R28R29用于建立堆栈的指针
asm volatile("PUSH R29t");//到此入栈成功
TCB[OSTaskRunningPrio].OSTaskStackTop = SP;//将正在运行的堆栈第保存。
unsigned char OSNextTaskID;
for(OSNextTaskID=0;OSNextTaskID<OS_TASKS&&!(OSRdyTb1&(0x01<<OSNextTaskID));OSNextTaskID++)
OSTaskRunningPrio = OSNextTaskID;
cli();//保护堆栈转换
SP = TCB[OSTaskRunningPrio].OSTaskStackTop;
sei();
//根据中断时的出栈次序
asm volatile("POP R29 t");
asm volatile("POP R28 t");
asm volatile("POP R31 t");
asm volatile("POP R30 t");
asm volatile("POP R27 t");
asm volatile("POP R26 t");
asm volatile("POP R25 t");
asm volatile("POP R24 t");
asm volatile("POP R23 t");
asm volatile("POP R22 t");
asm volatile("POP R21 t");
asm volatile("POP R29 t");
asm volatile("POP R20 t");
asm volatile("POP R19 t");
asm volatile("POP R18 t");
asm volatile("POP SERGt");
asm volatile("POP R1 t");
asm volatile("OUT SERG,R1t");
asm volatile("POP R1 t");
asm volatile("POP R0 t");
//中断时出栈完成
}
void OSTimeDly(unsigned int ticks)
{
if(ticks)//当延时有效时
{
OSRdyTbl&=~(0x01<<OSTaskRunningPrio);
TCB[OSTaskRunningPrio].OSWaitTick=ticks;
OSSched();//重新调度
}
}
void TCN0Init()//定时器0
{
TCCR0=0;
TCCR0|=(1<<CS02);//定时器256预分频
TIMSK|=(1<<TOIE0);//T0溢出中断允许
TCNTO=100;//置计数起始值
}
SIGNAL(SIG_OVERFLOW0)
{
unsigned char i;
for(i=0;i<OS_TASKS;i++)
{
if(TCB[i].OSWaitTick)
{
TCB[i].OSWaitTick--;
if(TCB[i].OSWaitTick==0)
{
OSRdyTb1|=(0x01<<i);
}
}
}
TCNT0 =100;
}
void Task0()
{
unsigned int j=0;
while(1)
{
PORTB=j++;
OSTimeDly(2);
}
}
void Task1()
{
unsigned int j=0;
while(1)
{
PORTC=j++;
OSTimeDly(4);
}
}
void Task2()
{
unsigned int j=0;
while(1)
{
PORTD=j++;
OSTimeDly(8);
}
}
void TaskScheduler()
{
while(1)
{
OSSched();反复调度
}
}
int main()
{
TCN0Init();
OSRdyTb1=0;
OSTaskRunningPrio=0;
OSTaskCreate(Task0,&Stack[49],0);
OSTaskCreate(Task1,&Stack[99],1);
OSTaskCreate(Task2,&Stack[149],2);
OSTaskCreate(TaskScheduler,&Stack[199],OS_TASKS);
OSStartTask();
}
上面的程序要表达的流程是:三个正在运行的主任务,都通过延时服务,主动放弃对CPU的控制权。在时间中断中对各个任务的延时进行计时,如果某个任务延时结束,将任务重新在就绪表中置位。最低级的系统任务TaskScheduler();在三个主任务在放弃对CPU的控制权后开始不断的进行调度。如果某个任务在就绪表中置位,通过调度,进入最高级别的任务中继续进行。下面由我整理的流程图会看起来明白一些。
下面是我本人的理解 ,CPU运行有运行环境(主要是寄存器),由于有四个任务因此有四个私有堆栈。当任务3完成后就不再调用,主要是任务0、任务1、任务2的反复调用执行。假如任务0在CPU中(相应寄存器中)运行,当任务0运行结束后,会将有关任务0的信息,存到任务0的私有堆栈当中,此时的信息指的是,当任务0运行时各个相应寄存器处于什么状态,存入堆栈的原因在于为下一次任务0执行做准备,比如其中一个值加加到了10,当任务再次调度时,接着10继续加加。任务0结束,任务1运行,CPU会重新分配寄存器,来用于运行任务1。任务1若要是执行结束,把寄存器中有关任务1的信息存到其私有堆栈当中。原理与任务0相同。再来个流程图加深一下印象:
上面的流程图是大体上的工作流程,下面再以任务0举例,来说明任务的调度与进栈出栈的关系,先来一张图重申一下问题。
来聊一下进栈出栈的问题,出栈即任务被调度,而且我们知道,先进栈的后出栈,后进栈的先出栈。有图说明问题:
上图为入栈过程。
上图为出栈过程。
小结
对于RTOS理解不到位的地方,请大家指出,共同进步。
最后
以上就是淡淡面包为你收集整理的写一个属于自己的RTOS(day-1)RTOS Once More的全部内容,希望文章能够帮你解决写一个属于自己的RTOS(day-1)RTOS Once More所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复