开发历程:开发一个RTOS(1)内核

本文介绍了基于ARM7架构的Cortex-M3内核自制实时操作系统(RTOS)的设计与实现。主要内容涵盖RTOS的启动流程、内核初始化过程、任务调度机制、临界区控制以及进程管理等关键环节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

声明

       这里在内核方面主要借鉴了网上某位网友的一篇文章《自己实现一个实时操作系统》和《嵌入式实时操作系统ucOS-2》,并做了适当的改进和简化,可能有些地方和上面提到的资料有些地方雷同,毕竟是个相对简单的rtos,无法摆脱这个框架,不过目的也很单纯,怀着纯粹的兴趣开发,或者说移植,为了能帮助更多有相同兴趣的人理解学习,本人当时也是怀着学习的心情看了资料搞出这个rtos的,也希望在今后的开发中留下一个纪念,所以在这里和大家分享下。

 

 

       好了,开始吧,先把我的实现框架和系统构成介绍下,作为一个操作系统,基本组成是不变的,内核,内存管理,文件系统,进城通信,网络,就是这基本的五大块构成的,本人在目前的公司的产品板子上利用业余时间开发,主要实现了除网络模块以外的其余部门,由于板子上没有安网络芯片,而且网络更加偏向于驱动,这里就不放到rtos里了,那么,就从内核开始吧。

 

       平台

内核,首先要介绍下平台,也就是cpu架构,我们公司的产品是使用的是arm7架构的cortex-M3内核,与《自己实现一个实时操作系统》作者使用的相同的芯片,目前st这款芯片在国内的低端处理器市场卖的很好,主要优势就是价格和速度,由于是arm7架构,那么不支持mmu,非常适合ucOS-2ucLinux的移植,后面会介绍下cortex芯片在内核开发中的具体使用。

 

启动

内核在操作系统中的启动流程,大致就是初始化接口,寄存器,变量,然后创建一个空闲任务,把控制权交给这个任务,然后开启进程中断,开始任务调度。

 

初始化

主要包括驱动用到的接口,串口这一类的初始化,当然,这些也可以在驱动中自己进行操作,开辟任务堆栈,这个比较重要,每个任务都需要自己的堆栈,堆栈主要用于在进程调度中存放压栈的指针和寄存器。在创建任务的时候,先初始化堆栈,代码如下

 

PUSH   {R4-R6,LR}

        MOV    R4,R0

        MOV    R3,R1

        MOV    R5,#0x21000000

        STR    R5,[R3]          //XPSR

        SUB    R3,R3,#4

        STR    R4,[R3]          //PC

        SUB    R3,R3,#4

        STR    R4,[R3]

        ;STR    R5,[R3]          //LR

        SUB    R3,R3,#4

        MOV    R4,#11

        STR    R4,[R3]          //R12

        SUB    R3,R3,#4

        MOV    R4,#12

        STR    R4,[R3]          //R3

        SUB    R3,R3,#4

        MOV    R4,#13

        STR    R4,[R3]          //R2

        SUB    R3,R3,#4

        ;MOV    R4,#14

        ASRS   R5,R5,#1

        STR    R5,[R3]          //R1

        SUB    R3,R3,#4

        ;MOV    R4,#15

        STR    R1,[R3]          //R0

       

        SUB    R3,R3,#4

        MOV    R4,#6

        STR    R4,[R3]

       

        SUB    R3,R3,#4

        MOV    R4,#7

        STR    R4,[R3]

       

        SUB    R3,R3,#4

        MOV    R4,#8

        STR    R4,[R3]

       

        SUB    R3,R3,#4

        MOV    R4,#9

        STR    R4,[R3]

       

        SUB    R3,R3,#4

        MOV    R4,#10

        STR    R4,[R3]

       

        SUB    R3,R3,#4

        MOV    R4,#11

        STR    R4,[R3]

       

        SUB    R3,R3,#4

        MOV    R4,#12

        STR    R4,[R3]

       

        SUB    R3,R3,#4

        MOV    R4,#13

        STR    R4,[R3]

       

        MOV    R0,R3

        POP    {R4-R6,PC}

        BX     LR

这是基于arm7的寄存器代码,主要是往每个任务的初始化堆栈里放点东西,其中需要注意的是,在压xpsr这个寄存器的时候,必须用#0x21000000,否则在开始调度的时候会有问题。

进程调度

当任务A正在运行时,时间片轮询到了,这时产生一个异常,这时,cpu就需要把任务A的进程信息都保存起来,放到任务A指定的堆栈中,如果时间片轮询到了,告诉A继续运行,那cpu将刚刚放到堆栈中的进程信息取出来,这样任务A就继续跑起来了,这里压栈和出栈根据不同的cpu架构,方法和规则有所不同,但其实现的本质是一样的。这里进程调度的核心代码如下:

__OS_Sched  

MRS     R0, PSP                

        SUB     R0, R0, #0x20        

        STM     R0, {R4-R11}                   

 

        LDR     R4, =TOSCurTCB   

        LDR     R4, [R4]

        STR     R0, [R4]             //将当前任务的寄存器压栈

 

        LDR     R4, =TOSCurTCB      

        LDR     R6, =TOSNewTCB

        LDR     R6, [R6]

        STR     R6, [R4]

 

        LDR     R0, [R6]               

        LDM     R0, {R4-R11}         

        ADD     R0, R0, #0x20

        MSR     PSP, R0            

ORR     LR, LR, #0x04       //新任务的寄存器出栈,并进入新任务运行

        BX      LR 

 

临界区控制

在操作系统运行过程中,会有很多临界区,例如,有些任务在进行过程中如果进入中断就会使任务无法正常运行,这时就需要关闭中断,这样就避免代码和中断进入临界区,通常微处理器一般都有关中断/开中断指令,这里arm7的操作指令如下

关闭中断,并保存当前中断状态

MRS     R0, PRIMASK               

        CPSID   I

        BX      LR

       打开中断,

MSR     PRIMASK, R0

               BX      LR

 

       创建第一个任务,并开始运行

       将第一个任务初始化后,打开处理器的systick中断,这就是操作系统进行调度和运作的发动机,然后将第一个任务的寄存器出栈,将堆栈指针指向psp(任务堆栈),这样,第一个任务就跑起来了,加载任务的代码如下:

LDR     R4, =TOSCurTCB             //当前任务的结构体指针

        LDR     R0, [R4]                           

        LDR     R0, [R0]

        LDM     R0,{R4-R11}

        ADD     R0,R0,#0x20

        MSR     PSP, R0                             //将堆栈指针指向psp

     

        ORR     LR, LR, #0x04  

        BX      LR                                     //返回,直接进入psp的堆栈模式运行。

      

       优先级选择

       这是任务调度的核心问题,对于一个rtos,实时性永远是其最最关心的问题,这里不详细列举各个rtos的实时性处理,简单的介绍下本操作系统的实时性的简单实现框架,在systick的中断函数中实现其实时性选择调度

       SysTick()

       {

              //关中断

              TCB        *pTCB;

             unsigned char index = 0;

 

TOSNewTCB = NULL;

/*判断任务调度是否关闭*/

             if(TosParam.OSScheLock == TOS_TaskSchedLock)

             {

                  TOS_INT_Enable();

                  return;

             }

             /*遍历所有进程任务*/

             for(index = 0;index < MAX_TASK_NUM;index ++)

             {

                pTCB = TOSTCBTable+index;

                if(NULL == pTCB->pStackTop)

                     continue;

                /*判断时间片是否到*/

                if(pTCB->TCBDelay)

                {

                     pTCB->TCBDelay --;

                     continue;

                }

      

                /*判断任务是否挂起*/

                if (pTCB->TaskStat == TOS_Task_Pend)   

                {

                     continue;

                }

      

      

                /*判断优先级*/

                if (TOSCurTCB->CurPriority < pTCB->CurPriority)

                {

                     TOSNewTCB = pTCB;

                     continue;

                }

      

                if (TOSCurTCB->CurPriority > pTCB->CurPriority)

                {

                     if (TOSNewTCB == NULL)

                     {

                           TOSNewTCB = pTCB; 

                           Continue

                     }

                }

      

                if (TOSCurTCB->CurPriority == pTCB->CurPriority)

                {

                     if(TOSCurTCB == pTCB)

                     {

                           if(TOSNewTCB == NULL)

                                TOSNewTCB = pTCB;

                           continue;

                     }else if(pTCB < TOSCurTCB){

                           if(TOSNewTCB == NULL)

                         TOSNewTCB = pTCB;

                           continue;

                     }else if(pTCB > TOSCurTCB){

                           TOSNewTCB = pTCB;

                           break;

                     }

                }

             }

 

             if(TOSCurTCB != TOSNewTCB)

                  TOSCtxSw();                      //开始调度

              //开中断

       }

1.       首先判断进程调度是否关闭

2.       查询进程任务表,选择非空的任务进程

3.       进程时间片没有到或者进程被挂起,继续查找下一个

4.       找到最高优先级,调入新进程

5.       如果优先级相等,则调入新进程。

6.       如果新进程不等于当前进程,进行任务调度

这样的调度方法思路很单一,就是找优先级最高的,优先级低的任务根本没有机会轮询,不过对于一个实时性要求非常高的场合,这个方法还是实用的。

进程管理

       这里进程的结构体是

typedef struct taskControlBlock

{

                     /*堆栈指针*/

                     STACK_TYPE       *pStackTop;         

                     /*优先级*/

                     PRIORITY_TYPE   CurPriority;

               /*进程状态*/

                     unsigned char        TaskStat;

                     /*时间片*/

                     unsigned long  TCBDelay;

               /*ID*/

               unsigned char   TCB_ID;

                     /*通信管道*/

               ECB             *TCB_ECB;

               unsigned char   TCB_MSGNUM;

} TCB, TASK_TYPE;

       给每个进程分配相应的堆栈,时间片,id,进程就可以跑起来了,本操作系统主要采用静态分配进程表,在一个进程数组中对进程进行管理,这样好处是代码实现方便,但无法支持动态增加进程,并且进程数有限定,不过对于一个本例,已经足够。当需要加入一个新任务时,只需要挑选一个空的进程空间就可以。

 

这样,基本的内核体系就实现了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值