⾃⼰实现⼀个RTOS《实时操作系统揭秘》(附源码)新年伊始,
将⾃⼰独⽴实现的⼀个⽐较⼩的RTOS源码贴上来,
顺便把原理都讲⼀讲,希望对在这块⼯作的朋友有些帮助或者启发
⼤家也给点改进的意见和建议。
本系列⽂章的标题叫做《实时操作系统揭秘》
第⼀篇前⾔
很多⼈对⽼美发明的操作系统顶礼膜拜,捧到了神的地步,
市⾯上也充斥着很多有关操作系统的劣质的译作
对其关键部分,⼤部都语焉不详,隔靴搔痒
让更多的⼈越看越糊涂
于是操作系统在⼈们⼼中更加⾼深了
其实,操作系统远没有这些⼈想象的那么神秘
任务切换,内存管理,⽂件系统,任务间通讯功能,引导程序等模块,
就形成了⼀个完整的操作系统内核
我们在这⾥就逐⼀剥⼀下操作系统的⽪,
把各个模块的原理,以结合代码的形式,抖给⼤家看看
让⼤家看清操作系统的⼀些秘密
对某些模块,系统功能有兴趣的同学,
也可以发邮件给我,
我们⼀起研究,共同学习...
(注:
1,这系列⽂章,
都是按照⽬前⼯作中,⼿头项⽬的进度
以及涉及到的知识点所写出来的,
是个类笔记的东西,是业余时间的⼀个作品
2,附注的源码,
是个⼈的⼀个⼩作品,
⾃⼰给他取了个不响亮的名字:BenOS
并且该系统在⾃⼰的Cortex-M3平台测试通过并且运⾏⾃⼰的应⽤没有问题
免费平台源码资源网3,BenOS是完全个⼈独⽴实现的
没有抄袭任何其他OS的源码
⽂档与源码:
第⼆篇任务切换,如何才能得到MM的青睐
操作系统最核⼼的功能就是任务切换
何为任务切换,为啥要任务切换
这个就简单说:
引⼊操作系统,就是为了让单个CPU可以运⾏多个任务
但是这些任务是并发运⾏的,
每个⼩猪吃⼀⼝奶,就让给另⼀个⼩猪吃⼀⼝
⼤家轮流来,宏观看起来就像所有的⼩猪都在同时吃奶⼀样
任务A正在运⾏,时间该任务B运⾏了,
这个时候,操作系统就需要做任务切换
于是,操作系统就保存任务A⽬前跑到了哪⼀步,运⾏时候,所有的参数是啥⼦
然后在任务B恢复上次运⾏到的位置,并且恢复上次停⽌运⾏时间点的所有参数
再打任务B⼀鞭⼦,任务B就像没停⽌过⼀样,开始欢快的跑了。
这些,就是任务切换所需的全部步骤
针对⽬前我们使⽤的CORTEX-M3平台⽽⾔
任务切换就是,
⾸先
1,计算当前就绪态任务列表中,优先级最⾼的任务,
再判断是否需要切换任务
2,保存当前任务运⾏相关的寄存器,⼤概⼗⼏个(⼊栈)
保存当前任务运⾏地址(PC指针,其实在1⾥⾯保存了)
保存当前的堆栈指针
3,递减各个任务等待的时间⽚计数器;
4,其他资源占⽤情况统计(死锁的解除)
5,然后将堆栈指针指向新任务的堆栈
6,恢复2中保存的所有寄存器和其他数据(出栈)
7,其他系统功能相关的东西
再给出例程代码:
PUSH {R0-R15} ;(2)
LDR    R4, =OSTCBCur          ; OSTCBCur->OSTCBStkPtr = SP;
LDR    R4, [R4]
STR    SP, [R4]                ;
LDR    R6, =OSTCBHighRdy  ; SP=OSTCBHighRdy->OSTCBStkPtr
LDR    SP, [R6]
POP {R0-R15}            ; (6)
BX      LR                      ; RET,此时堆栈恢复了,寄存器恢复了,PC指针也恢复了,                                        ; 状态也恢复了,旧任务就可以继续欢乐的执⾏了。
注:这⾥的堆栈指针都是另外赋值操作的,
绝对不能使⽤出栈⼊栈的⽅式保存恢复
因为出栈⼊栈操作会⾃动修改堆栈指针...
这⾥主要是(2),(6)的代码,并且没有考虑模式切换和堆栈指针切换,
其他步骤,⼀般实现都是在这切换之前进⾏。
任务切换在很多时候都会进⾏,常说的任务切换就是在时间定时中断时候进⾏,
芯⽚(或者由外部时钟)会产⽣⼀个定时中断,可以设置每秒多少次,
就是在这个中断处理函数中进⾏任务切换。
其他的任务切换,发⽣在,等待消息,任务延时,或者⼿动强制任务切换等等时候
这些不同的切换时机都有些⼩差别,不过本质上是⼀样的。
够简单吧?
其实实际上还有⼀些幕后⼯作并没有列出,
⽐如,内核要维护两个链表,其⼀是就绪态任务的表,其⼆是等待态任务的表
每个TICK中断的时候,等待态表中的每个等待TICKS计数器就减⼀,
当计数为0时,将其任务移动到就绪态表中来。
还是有其他⼀些繁琐的⼯作都要做,任务切换锁定检测,死锁检测,优先级反转的避免等;并且死锁和优先级反转问题在实时系统中是个很深刻的课题,有兴趣的不妨⾃⼰做⼀做。另外,指出⼀点:是依据时间⽚的操作系统,还是据优先级的抢占式操作系统,
还是混合多种切换算法的操作系统的根本区别就是其切换算法,
先进先出,后进先出,最短等待时间,最短平均等待时间,等等算法在这⾥都有⽤武之地!下⾯是⾃
⼰的⼀个实现:
/*
*时钟中断函数
*/
void SysTick_Handler()
{
INT32        index;
TCB                *pTCB;
INT8U        flagFirstTask=0;
INT_Stat  stat = 0;
stat = BenOS_INT_Save();
if (BenOSScheLock != 0)
{
BenOS_INT_Restore(stat);
return;
}
/*在时钟中断中,必须将所有时延都--*/
for (index = 0;index < TaskNUM;index++)
{
pTCB = BenOSTCBTable+index;
/*该任务在睡眠状态,⽽当前的调⽤是在时钟中断中*/
if (pTCB->TCBDelay > 0)
{
pTCB->TCBDelay--;
}
else
{
if (flagFirstTask==0)
{
BenOSNewTCB = pTCB;
flagFirstTask = 1;
}
}
}
if (BenOSNewTCB != BenOSCurTCB)
{
OSIntCtxSw();
}
BenOS_INT_Restore(stat);
}
/*
*在⾮中断中调度新的任务并且切换
*/
void TaskSche()
{
INT_Stat  stat = 0;
stat = BenOS_INT_Save();
if (BenOSScheLock != 0)
{
BenOS_INT_Restore(stat);
return;
}
if (BenOSNewTCB != BenOSCurTCB)
{
OSIntCtxSw();
}
BenOS_INT_Restore(stat);
}
__asm void OSCtxSw()
{
LDR    R4, =NVIC_INT_CTRL
LDR    R5, =NVIC_PENDSVSET
STR    R5, [R4]          ;激活PENDSVC中断,开始切换任务
BX      LR
NOP
}
__asm void PendSV_Handler()
{
MRS    R0, PSP                ; PSP is process stack pointer
SUB    R0, R0, #0x20          ; save remaining regs r4-11 on process stack        STM    R0, {R4-R11}
LDR    R4, =BenOSCurTCB        ; OSTCBCur->OSTCBStkPtr = SP;
LDR    R4, [R4]
STR    R0, [R4]                ; R0 is SP of process being switched out
LDR    R4, =BenOSCurTCB        ; BenOSCurTCB  = BenOSNewTCB;        LDR    R6, =BenOSNewTCB
LDR    R6, [R6]
LDR    R0, [R6]                ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;        LDM    R0, {R4-R11}            ; restore r4-11 from new process stack
ADD    R0, R0, #0x20
MSR    PSP, R0                ; load PSP with new process SP
ORR    LR, LR, #0x04          ; ensure exception return uses process stack
BX      LR                      ; exception return will restore remaining context
NOP
}
事实上,这⾥只是实现的⼀部分,在后⾯展⽰的源码中,会有其他的部分。
第三篇世界的起源任务如何启动
其实绝⼤部分操作系统的实现步骤都是这样的:
先压点合适的东西进堆栈----堆栈的初始化
(这步放在任务初始化的时候----切记关任务切换和定时中断)
只要恢复寄存器和其他数据之后
SP指针正确,状态寄存器没异常,
直接将PC指向新任务的第⼀条指令,不就⾏了嘛。
看下我们CORTEX-M3平台上的实现:(堆栈⽣长⽅向是--,跟X86相反)
/*把堆栈初始化模块⽤汇编写⼀遍,但愿性能会⾼点*/
__asm    STACK_TYPE *TaskStkInit (void (*task),STACK_TYPE *ptos)
{
PUSH    {R4-R6,LR}
MOV      R4,R0
MOV      R0,R1
MOV      R5,#0x1000000
STR      R5,[R0,#0]
SUBS    R0,R0,#4
STR      R4,[R0,#0]
MVN      R5,#1
SUBS    R0,R0,#4
STR      R5,[R0,#0]
MOV      R5,#0x2
SUBS    R0,R0,#4
STR      R5,[R0,#0]
MOV      R5,#0x3
SUBS    R0,R0,#4
STR      R5,[R0,#0]
MOV      R5,#0x4
SUBS    R0,R0,#4
STR      R5,[R0,#0]
ASRS    R5,R5,#1
SUBS    R0,R0,#4
STR      R5,[R0,#0]
SUBS    R0,R0,#4
STR      R1,[R0,#0]
MOV      R5,#0x5
SUBS    R0,R0,#4
STR      R5,[R0,#0]
SUBS    R6,R0,#4
MOV      R0,R6
STR      R5,[R6,#0]
MOV      R5,#0x7
SUBS    R0,R0,#4
STR      R5,[R0,#0]
MOV      R5,#0x8
SUBS    R0,R0,#4
STR      R5,[R0,#0]
MOV      R5,#0x8
SUBS    R0,R0,#4
STR      R5,[R0,#0]
MOV      R5,#0x10
SUBS    R0,R0,#4
STR      R5,[R0,#0]
MOV      R5,#0x11
SUBS    R0,R0,#4
STR      R5,[R0,#0]
MOV      R5,#0x12
SUBS    R6,R0,#4
MOV  R0,R6
STR      R5,[R6,#0]
POP      {R4-R6,PC}
}启动这个任务,就可以直接使⽤任务切换源码的后半部分(因为没有任务需要保存)
这样PC就指向了新任务的⼊⼝,
新任务可以开始运⾏啦!
世界就这样形成了...
实际启动是使⽤SVC中断启动
代码如下:
__asm void StartTask()
{
LDR    R4, =NVIC_SYSPRI2      ; set the PendSV exception priority
LDR    R5, =NVIC_PENDSV_PRI
STR    R5, [R4]
MOV    R4, #0                  ; set the PSP to 0 for initial context switch call
MSR    PSP, R4
LDR    R4, =SYS_TICKS  ;设置时钟节拍频率
LDR    R5, =NVIC_SYSTICK_LOAD
STR    R4, [R5]
MOV    R4, #0x07
LDR    R5, =NVIC_SYSTICK_CTRL
STR    R4, [R5]
SVC 0    ;呼叫SVC中断
NOP
}
/*SVC中断⼊⼝*/
__asm void SVC_Handler()
{
LDR R6,=BenOSCurTCB
LDR    R0, [R6]                ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;        LDR    R0, [R0]                ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。