Small RTOS OSStart 函数解析 以及 内存堆栈 变化 (一)

Small RTOS 是在 Keil C51 上面 编译的。
看如下代码:

#include "config.h"

void main(void)
{
	TMOD = (TMOD & 0XF0) | 0X01;
	TL0 = 0x0;
	TH0 = 0x0;
	TR0 = 1;
	ET0 = 1;
    OSStart();
}

Keil C51 编译器 在 程序进入main函数之前,会做如下的工作,
在这里插入图片描述

进入 main函数之后 ,
进入 OSStart() 函数:
代码如下:

void OSStart(void)        
{
    uint8 idata *cp;
    uint8 i;
	  uint8 tmp ;
    
    cp = STACK;
    
    OSTsakStackBotton[0] = STACK;

    OSTsakStackBotton[OS_MAX_TASKS + 1] = (uint8 idata *)(IDATA_RAM_SIZE % 256);
    
    /* 初始化优先级最高的任务堆栈,使返回地址为任务开始地址 */
	  /*SP  PCL 然后 SP+1  PCH*/
		tmp = cp ;
    *cp++ = ((uint16)(TaskFuction[0])) % 256; //低8位字节
		tmp = cp ;
    *cp = ((uint16)(TaskFuction[0])) / 256;  //高8位字节
	


	/* 初始化优先级最低的任务堆栈 */
    cp = (uint8 idata *)(IDATA_RAM_SIZE - 1) ;
		tmp = cp ;
    *cp-- = 0;
		tmp = cp ;
	
    *cp-- =  ((uint16)(OSIdle)) / 256;
		
		tmp = cp ;
		
		
    OSTsakStackBotton[OS_MAX_TASKS] = cp;
		
    *cp-- =  ((uint16)(OSIdle)) % 256;
    
		tmp = cp ;

		/* 初始化其它优先级的任务堆栈 */
    for(i = OS_MAX_TASKS - 1; i > 0; i--)
    {
        *cp-- = 0;
				tmp = cp ;
        *cp-- =  ((uint16)(TaskFuction[i])) / 256;
				tmp = cp ;
        OSTsakStackBotton[i] = cp;
        *cp-- =  ((uint16)(TaskFuction[i])) % 256;
			  tmp = cp ;
    }
    /* 允许中断 */
    Os_Enter_Sum = 1;
    OS_EXIT_CRITICAL();
    /* 函数返回优先级最高的任务 */
}

其中的
cp = STACK;
STACK 是经过C51 初始化之后,sp堆栈指针指向的RAM地址。
这样 cp指针 也指向堆栈指针指向的RAM地址了。

Keil C51 在编译的时候,会将全局变量,局部变量等都分配到RAM的某一个地址上去。
之后,RAM剩余的地址,才会指定给sp指针,作为栈使用。

/* 初始化优先级最高的任务堆栈,使返回地址为任务开始地址 */
  /*SP  PCL 然后 SP+1  PCH*/
	tmp = cp ;
*cp++ = ((uint16)(TaskFuction[0])) % 256; //低8位字节
	tmp = cp ;
*cp = ((uint16)(TaskFuction[0])) / 256;  //高8位字节

这一部分代码的作用是 将 void TaskA(void) 函数的地址,放入堆栈中,
首先将地址的低8位字节放入sp指向的地址,
然后sp+1 ,
将地址ide高8位字节放入sp指向的地址。

这样做的目的是 OSStart 返回之后,会执行 RET 汇编指令。
RET指令的作用如下:
在这里插入图片描述

这样OSStart 会首先跳转到 TaskA 函数中去。

查看一下 :test.m51
OSTsakStackBotton 的地址:
D:0010H PUBLIC OSTsakStackBotton
I:0021H PUBLIC STACK

在这里插入图片描述

#define IDATA_RAM_SIZE 0x100
cp = (uint8 idata *)(IDATA_RAM_SIZE - 1) ;
cp指针指向了RAM的0xFF地址

函数指向完毕 跳转到TaskA 去执行 :

void TaskA(void)
{
    while (1)
    {
        OSWait(K_TMO,5);
    } 
}

进入 OSWait(K_TMO,5);
uint8 data OSTaskID = 0;
OSWaitTick[OSTaskID] = ticks; //ticks = 5

    case K_TMO:                                 /* 等待超时,即延时一段时间 */
        OS_ENTER_CRITICAL();
        while (OSWaitTick[OSTaskID] != 0)       /* 判断超时时间是否到   */
        {
            OSClearSignal(OSTaskID);            /* 任务进入等待状态     */
            OSSched();                          /* 运行下一个任务       */
        }
        OS_EXIT_CRITICAL();
        return TMO_EVENT;

uint8 OSTaskRuning = 0xff; // 初始值
uint8 const OSMapTbl[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00};

OSTaskRuning &= ~OSMapTbl[TaskId]; //将对应的bit位清零
变为:0xfe了

堆栈

复位之后:
在这里插入图片描述

运行到main函数起始位置的时候,(看PC指针)
在这里插入图片描述

OSStart 函数 最后一步,将要退出函数的时候:此时 SP堆栈指针设置的是指向22H地址
在这里插入图片描述

仔细看上图其中:
OSTsakStackBotton
地址0010H :

TaskA
地址 0548H

TaskB
地址 0552H

TaskC
地址 055CH

OSIdle
地址 0003H

0010H 位置值是 21 F7 FA FD
0x21 对应的是 TaskA 05 48H 存放的地址
0xF7 对应的是 TaskB 地址 0552H
0xFA对应的是 TaskC 地址 055CH
0xFD对应的是 OSIdle 地址 0003H
SP 等于22H的原因是:
在这里插入图片描述
子程序返回的时候
SP 指向22H:0x05
这样PCH = 05,然后 SP = SP-1 = 21H
SP 指向21H:0x48
PCL= 0x48,然后 SP = SP-1 = 20H

这样OSStart 返回之后,PC指向地址0548H这个程序段对应的函数,SP=20H

单步执行以下,程序切换到TaskA 中,
在这里插入图片描述

TaskA中,运行之后,进入OSWait(K_TMO,5);
OSSched();
进入 OSCtxSw
C:0530H PUBLIC OSCTXSW
在这里插入图片描述

TaskA – 21: TaskA 起始堆栈
TaskB – F7: TaskB起始堆栈,并且在起始堆栈中设置taskB的函数指针位置。并且重新设置SP 堆栈指针的指向地址。
TaskC – FA:
Idle – FD:
在这里插入图片描述
OSCtxSw 跳转到 LoadCtx 之前:
在这里插入图片描述

TaskA – 21: TaskA 起始堆栈
TaskB – 28: TaskB起始堆栈,并且在起始堆栈中设置taskB的函数指针位置。并且重新设置SP 堆栈指针的指向地址。
TaskC – FA:
Idle – FD:

在这里插入图片描述

单步执行下一步:
进入 TaskB
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

因为每个任务 都是通过 OSWait(K_TMO,5); 进入休息状态,等待时间到了,才能够继续运行。
如果时间不够, 此时 进入了 OSIdle 任务中。等待中断时间到达。
os_core.c 的
OSTickISR---->OSTimeTick---->OSIntSendSignal-----> OSTaskRuning |= OSMapTbl[TaskId];
此时,如果 定时时间到达,对应的 OSTaskRuning 任务位 变为 1。

OSTickISR 在结束的时候,会调用 OSIntExit(); 函数。
OSIntExit 中会根据 优先级的顺序,查看 OSTaskRuning 那个位 变为1 了,
如果那个位变为1了,OSNextTaskID 变为对应的任务。

os_core.c 最后 的OSTickISR函数

需要打断点的地方

OSTickISR -----> os_cpu_c.c 最后几行
OSTimeTick(); //一个断点
OSIntExit(); //一个断点

OSIdle ------> os_cpu_c.c
OSStart ------> os_cpu_c.c
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

变量的状态变化和依赖关系

OSTaskRuning 根据任务的状态 和中断 变化
OSNextTaskID 根据OSTaskRuning 的变化而变化
OSTaskID 根据OSNextTaskID 的变化而变化。

假设有3个任务,TaskA,TaskB,TaskC,优先级 TaskA 大于 TaskB 大于 TaskC

刚开始 OSTaskRuning = 0xFF,所有的任务,都等待执行,
任务A 首先 执行,然后任务A 休眠5s
任务切换,任务B 得到执行,然后任务B 休眠10s
任务切换,任务C 得到执行,然后任务C 休眠15s
这个时候,所有任务都没有事情作,都处于休眠状态,所以进入OSIdle()任务中。

经过5s之后,中断程序,激活任务A,这个时候,OSTaskRuning 发生变化。
OSNextTaskID 发生变化。OSTaskID 根据OSNextTaskID 变化而变化。
任务A,进行运行模式。

OSTsakStackBotton[ ] 保存的是,任务A,B,C 被切换到其他任务之前的那个断点时候的,堆栈的地址。
任务的堆栈,是该任务被中断的那个断点上的 程序PC 断点。
具体参看《Small RTOS OSStart 函数解析 以及 内存堆栈 变化 (二)》

程序:


uint8 idata * data OSTsakStackBotton[OS_MAX_TASKS + 2];/* 任务堆栈底部位置            */

extern uint8 data OSTaskID,OSNextTaskID;


//OSNextTaskID 是要切换去的任务
//OSTaskID 是当前要保存的任务


//0---TaskA --  21: TaskA 起始堆栈
//1---TaskB --  F7:  TaskB起始堆栈,并且在起始堆栈中设置taskB的函数指针位置。并且重新设置SP 堆栈指针的指向地址。
//2---TaskC --  FA: 
//3---Idle   --   FD:




//0---TaskA --  21: TaskA 起始堆栈
//1---TaskB --  28:  TaskB起始堆栈,并且在起始堆栈中设置taskB的函数指针位置。并且重新设置SP 堆栈指针的指向地址。
//2---TaskC --  FA: 
//3---Idle   --   FD:

//OSTaskID = 0 
//OSNextTaskID = 1 

void C_OSCtxSw(void)
{
    uint8 idata * cp1,idata *cp2 ;

    uint8 i,temp ,SaveSP ;

    SaveSP = SP ;


    /*当前堆栈指针位置0x27*/
    /*SP = 0x27*/
    /*cp1 = 0x28*/
    
    cp1 = (uint8 idata *)SP + 1 ;
    

    /*TaskC的地址FA*/
    temp = (uint8) OSTsakStackBotton[OSNextTaskID +1 ];

    /*TaskB的地址F7*/
    cp2 = OSTsakStackBotton[OSTaskID+1];
    

    if(OSNextTaskID > OSTaskID){

        /*预加载任务 堆栈内容,复制到当前堆栈中*/
        /*cp2 是预加载任务的起始地址*/
        /*temp是预加载任务的结束地址*/
        /*cp1是当前堆栈指向的RAM的相邻地址*/
        while(cp2 != (uint8 idata *) temp){

            
            *cp1++ = *cp2++ ;
            
        }


        //重新设置堆栈指针 指向位置
        SP = (uint8)cp1 -1 ;


        /*TaskB的地址F7*/
        /*SaveSP 是 0x26*/
        temp = OSTsakStackBotton[OSTaskID + 1] - (uint8 idata *)SaveSP -1  ; 
        //i = 1; i<2;i++
        //OSTsakStackBotton[1] = OSTsakStackBotton[1] - temp
        ///*TaskB的地址*/ = F7 - (0xF7-0x27-1) = 0x27 +0x01
       
        //更新预加载任务的堆栈内容存放地址
        for(i = OSTaskID  +1 ; i < OSNextTaskID +1 ; i++){

            OSTsakStackBotton[i] -= temp ;
        }


        //更新预加载任务为当前任务
        OSTaskID = OSNextTaskID ;

        goto aaa  ;

    }


    if(OSNextTaskID  < OSTaskID){


      
        cp2-- ; 
         
         
        cp1-- ;

        
        
        
        
        while(cp2 != (uint8 idata *)temp ){

            *cp2-- = *cp1-- ;
            
        }

        SP = (uint8) OSTsakStackBotton[OSNextTaskID + 1 ] -1 ; 

        temp = OSTsakStackBotton[OSTaskID + 1] - (uint8 idata *)SaveSP -1 ;

        for(i = OSNextTaskID +1 ; i < OSTaskID +1 ; i++ ){

            OSTsakStackBotton[i] += temp ;
        }

        OSTaskID = OSNextTaskID ; 
        goto aaa ;
    }


    SP = SaveSP ;

    aaa:
        LoadCtx();

        
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Small RTOS(51) 1.20.3v 说明文件 编写动机: 就像在嵌入系统中使用C语言替代汇编一样,在嵌入系统中使用RTOS是大势所趋。原因主要是现在在大多数情况下编程效率比执行效率重要(单片机便宜嘛)。但纵观51的RTOS,keil c51 所带的RTX Full 太大(6k多),且需要外部ram,又无源代码,很多时候不实用。RTX Tiny虽然小(900多字节),但是任务没有优先级和中断管理,也无源代码,也不太实用。而ucosII虽有源代码,但是它太大,又需要外部ram,所有函数又必须是重入函数,用在51这类小片内RAM的单片机上有点勉强。于是,我借鉴ucosII和RTX Tiny编写了Small RTOS 51,虽然它为51系列编写,但是它还是比较容易移植到其它CPU上。 与作者联系方法: 可以给chenmingji@tom.com(原chenmingji@163.net)写信,或是在www.zlgmcu.com.cn上的论坛ARM与ucosII区提问(目前本人是版主)或是在www.c51bbs.com的论坛上提问(不推荐。c51bbs和21ic网名均为cmj)。 版本号定义方式: a.bc.d a:主版本号,一般重大改变时改变它。 bc:次便本号,一般功能增加时改变它。 d:同一版本的修订序号。 版本升级: 1.20.3版 2004年6月8日 修正Os_q.c的一个BUG,造成FIFO发送数据时,在队列中有大量数据且队列较大时,可能会出错。 1.20.2版 2004年2月4日 修正for Keil c51的Os_cpu_c.c的StkDelB函数的BUG,它会影响任务删除的正确执行。 1.20.1版 2004年2月4日 修改OSWait(K_SIG | K_TMO, x) 只能通过信号唤醒的bug。 1.20.0版 2003年8月3日 支持任务动态建立与删除。函数功能向一般的RTOS靠拢。支持C51的重入函数(用关键字reentrant定义的函数)。支持动态内存分配(使用动态内存分配的任务必须使用重入栈)。 1.12.1版 2003年2月5日 修正OS_MAX_TASKS为8、16时的bug。同时修正一些小bug。 1.12.0版 2003年1月24日 OS_MAX_TASKS就是用户任务数量。同时修正一些小bug。 1.11.0版 2002年12月2日 各个任务具有自己的关中断计数器,不在互相影响(这意味着如果一个任务在任务放弃CPU前关了中断,当它再次进入运行态时中断还是关的)。优先级最低的任务作为系统保留任务不再需要用户编写,同时节约一些内存。增加一些注释。更正在Keil C51下Memory Model为非Small 模式的Bug。 1.10.5版 2002年10月26日 更正许多小Bug。 1.10.4版 2002年10月6日 合并Os_cpu_a.asm和OS_CPU_A_task16.ASM。统一了一下代码风格 1.10.3版 2002年9月16日 修改了Os_cpu_a.asm和OS_CPU_A_task16.ASM的LoadCtx代码使之执行更快,代码更小 1.10.2版 2002年9月9日 更正OSWait()的Bug,在极端情况下,这个Bug可能造成可能锁死任务。 修改OSQIntPost()的Keil C51特殊代码,它会造成阅读障碍。 1.10.1版 2002年9月4日 更正OSTimeTick的Bug,它在keil c51中不会有问题,但移植的其它系统可能出错。 1.10版 2002年9月1日 增加Small RTOS 对消息队列(简化的)和信号量的支持;改变了开关中断的方式;增加可移植的变量定义;修正一些Bug。 1.00版 2002年6月20日 使用户可以进行更多的配置,可以禁止中断管理,51系列支持软的非屏蔽中断,并调整目录结构等. 0.60版 2002年5月10日 修改OS_CORE.c使之在keil c51可以重入。不再需要禁止覆盖分析。 0.52版
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值