对ucosii内核代码的理解

ucosii中的任务都是无返回值且不能有返回点的,换句话说任务就是一个无限循环。

每个任务都有自己的唯一性的优先级,和自己的堆栈空间,用来供内核进行上下文切换(见前面的汇编理解)。

内核根据优先级调度算法来执行任务的调度切换(见前面的数据结构理解):

任务在创建之后进入就绪态,任务等待的事件触发后也是进入就绪态,任务从被中断和调度锁状态恢复之后也是进入就绪态;内核从就绪任务列表中,永远提取最高优先级任务开始执行。

但是,ucosii也存在“优先级反转”的缺陷 :两个任务等待同一个事件时,会出现低优先级任务先等到了事件,而执行,反而是高优先级任务依然要等待,这违反了优先级调度的本意;

解决办法是实现“优先级继承” ,即任务等待事件时动态的提高它的优先级;而且,免费的RTOS没有这个功能,除非你掏钱购买商业内核,或者自己实现。但是一般的应用中,完全可以规避这个问题。


 

1. 操作系统的初始化和启动

1.1 OSInit()

    OS_InitMisc(); 		//初始化一些单独的全局变量                                            
    OS_InitRdyList();	//初始化任务就绪表
    OS_InitTCBList();	//初始化TCB表                                         
    OS_InitEventList();	//初始化事件表                                        
    OS_QInit();			//初始化消息队列控制块表
    OS_InitTaskIdle();	//创建空闲任务
    OS_InitTaskStat();	//创建统计任务
    OSTmr_Init();		//初始化定时器控制块

这是系统内核几个必要的初始化函数,笼统的划分的话,我们可以把里面的操作分为三类:

1、对单个的局部变量进行初始化操作(基本就是标志清0)

系统时钟节拍OSTime清0,等待Tick函数启动;

OSRunning = OS_FALSE,等待OS启动将其置位;

OSIntNesting和OSLockNesting,中断嵌套等级和任务调度锁等级的初始化;

其它还有一些,统计任务和空闲任务的一些用量初始化。

2、对结构体变量的初始化(最重要的就是链表初始化,使之形成链接)

TCB表、Event表、就绪表和队列控制块表的初始化,本质上都是一样的:

先对内存进行清0,然后用两个局部变量指针来遍历整个表,使之形成一个单向链表(TCB特殊,是双向链表)。

3、创建空闲任务和起始任务,两个系统任务

系统创建的第一个任务,就是空闲任务。创建任务的方法,很值得新人去学习参考。

static  void  OS_InitTaskIdle (void)
{
#if OS_TASK_CREATE_EXT_EN > 0u
    #if OS_STK_GROWTH == 1u
    (void)OSTaskCreateExt(OS_TaskIdle,
                          (void *)0,                                 
                          &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],
                          OS_TASK_IDLE_PRIO,                         
                          OS_TASK_IDLE_ID,
                          &OSTaskIdleStk[0],                        
                          OS_TASK_IDLE_STK_SIZE,
                          (void *)0,                                 
                          OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);
    #else
    (void)OSTaskCreateExt(OS_TaskIdle,
                          (void *)0,                                 
                          &OSTaskIdleStk[0],                         
                          OS_TASK_IDLE_PRIO,                         
                          OS_TASK_IDLE_ID,
                          &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],
                          OS_TASK_IDLE_STK_SIZE,
                          (void *)0,                                 
                          OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);
    #endif
#else
    #if OS_STK_GROWTH == 1u
    (void)OSTaskCreate(OS_TaskIdle,
                       (void *)0,
                       &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],
                       OS_TASK_IDLE_PRIO);
    #else
    (void)OSTaskCreate(OS_TaskIdle,
                       (void *)0,
                       &OSTaskIdleStk[0],
                       OS_TASK_IDLE_PRIO);
    #endif
#endif
}

如上,OS_STK_GROWTH宏定义表征了系统堆栈内存的生长方向,创建任务时之前需要手动申请一块内存空间来作为堆栈空间(空间大小也是手动决定的),内存空间是固定的(且本质就是一块数组空间),但是哪一端作为栈顶就要看不同的处理器了,所以出现了这个判断。

1.2 OSTaskCreateExt()

创建任务也存在两种方式,OSTaskCreateExt和OSTaskCreate,前者需要的参数多一点,多出来的参数,ID和扩展指针弃之不用赋为空,opt选项和堆栈参数用来供统计任务执行堆栈空间检查。

创建任务的函数内部,主要做了两个操作:OSTaskStkInit和OS_TCBInit。

OSTaskStkInit,其内部是个满递减堆栈(并没有预编译),初始化了16块内容,前八块包括R14返回寄存器、R12函数调用返回地址、R3,R2,R1,R0,后八块是R11到R3,用来存放系统寄存器的数据。

OS_TCBInit,则是,先获取空闲指针,填入参数和优先级,插入链表并将空闲指针后移,然后将任务就绪,移动用来遍历TCB的指针。

1.3 OSStart(),OSStatInit()

任务的启动时机基本是固定的。

一般在硬件初始化完毕之后,先调用OSInit()进行初始化,然后调用起始任务(这一般是系统中,用户定义的优先级最高的任务),起始任务执行完毕,则开始调用OSStart()来启动多任务系统。

值得一提的是,起始任务一般来完成应用层的初始化,一般还会起一个心跳的作用;如果要使用统计任务的话,则必须要在起始任务的最开头进行调用。就比如下面这种常见的形式。

void StartTask(void *pData)

{
    pData = pData;
    OSStatInit();
    AppInit();
    CreateAppTask();
    while(1)
    {
        ;
    }
}

理解立即OS启动到统计任务的执行和作用很有必要!!!

先看看起始任务和空闲任务:

void  OSStatInit (void)
{              
    OS_CPU_SR  cpu_sr = 0u;

    OSTimeDly(2u);

    OS_ENTER_CRITICAL();
    OSIdleCtr    = 0uL;                         
    OS_EXIT_CRITICAL();
	
    OSTimeDly(OS_TICKS_PER_SEC / 10u); 
	
    OS_ENTER_CRITICAL();
    OSIdleCtrMax = OSIdleCtr;                   
    OSStatRdy    = OS_TRUE;
    OS_EXIT_CRITICAL();
}

void  OS_TaskIdle (void *p_arg)
{                
    OS_CPU_SR  cpu_sr = 0u;

    p_arg = p_arg;

	for (;;) {
        OS_ENTER_CRITICAL();
        OSIdleCtr++;
        OS_EXIT_CRITICAL();
    }
}

void  OS_TaskStat (void *p_arg)
{                  
    OS_CPU_SR  cpu_sr = 0u;

    p_arg = p_arg;

    while (OSStatRdy == OS_FALSE) {
        OSTimeDly(2u * OS_TICKS_PER_SEC / 10u);
    }
	
    OSIdleCtrMax /= 100uL;
    if (OSIdleCtrMax == 0uL) {
        OSCPUUsage = 0u;
        (void)OSTaskSuspend(OS_PRIO_SELF);
    }
	
    OS_ENTER_CRITICAL();
    OSIdleCtr = OSIdleCtrMax * 100uL;
    OS_EXIT_CRITICAL();
	
    for (;;) {

        OS_ENTER_CRITICAL();
        OSIdleCtrRun = OSIdleCtr;      
        OSIdleCtr    = 0uL;            
        OS_EXIT_CRITICAL();

        OSCPUUsage   = (INT8U)(100uL - OSIdleCtrRun / OSIdleCtrMax);
        OS_TaskStatStkChk();
        OSTimeDly(OS_TICKS_PER_SEC / 10u);
    }
}

在起始任务和App任务都创建完毕之后,系统开始运行。

此时进入起始任务,先执行OSStatInit(),延时2个节拍后回归自身,将空闲计数器清0,再进行一个固定延时(ucosii的延时,会直接导致任务被切换),切换到统计任务,而统计任务此时还未就绪完毕,也要延时,任务又被切换到空闲任务,开始计数,最后时间到期在OSTimeTick()的调度下,被切换回统计任务再切回起始任务(因为此时,统计任务的延时时间要更长),此时得到计数器的最大值,并将统计任务就绪完毕。over

后面起始任务继续往下走,最后等到没有App任务运行时,才会切到Stat任务,这里做了两件事:1、计算在1/OS频率*10的时间内计算出空闲计数器值(即100ms),利用这个值和当前计数器值来求出CPU利用率;2、检查任务堆栈情况;执行完毕,延时,轮到空闲任务计时。

当然,其中启动OS和上下文切换,这里并没有说明,在前面的“汇编理解”那篇文章中有说明。

2. 中断的进入和退出,以及任务调度锁

2.1 OSIntExit()

void  OSIntEnter (void)
{
    if (OSRunning == OS_TRUE) {
        if (OSIntNesting < 255u) {
            OSIntNesting++;
//很简单,进中断,就将该值+1,等待退中断时,只需要判断是否等于0,为0则回复调度,为1咋递减                     
        }
    }
}

void  OSIntExit (void)
{                          
    OS_CPU_SR  cpu_sr = 0u;

    if (OSRunning == OS_TRUE) {

        OS_ENTER_CRITICAL();
        if (OSIntNesting > 0u) {                          
            OSIntNesting--;
        }
        if (OSIntNesting == 0u) {                         
            if (OSLockNesting == 0u) {                    
                OS_SchedNew();
//内核中的函数,根据就绪表的值求出最高优先级,见前面文章“数据理解”的优先级算法部分
                OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
                if (OSPrioHighRdy != OSPrioCur) {          
                    OSCtxSwCtr++;                          
                    OSIntCtxSw();
//中断结束之后,判断最高优先级是否是当前任务,不是就要进行切换
                }
            }
        }
        OS_EXIT_CRITICAL();

    }
}

 

2.2 OSSchedUnlock()

调度锁,和上面的完全是同一个结构。

进出中断,会很频繁的调用;一般一个业务系统,只会在芯片的Tick_Handler中调用OSIntExit函数;除非在一些实时性要求很高的系统中,才会在其它中断中添加该函数。

调度锁,一般用来在某个时期内,禁止对任务的调度。

3. OSTimeTick调度和任务就绪、任务等待

3.1 OS_EventTaskWait()

一个任务,创建之后是默认就绪的。

但是该任务中,需要等待一个信号量或消息时,就需要将该任务置于等待状态,这一操作就是有该函数来实现的。

确切的说是,任务因事件而等待。

该函数,一般是由Pend类的系统函数所调用,且调用之后,必须要执行OS_Sched()调度一次,将老任务切出去。

void  OS_EventTaskWait (OS_EVENT *pevent)
{
    INT8U  y;

    OSTCBCur->OSTCBEventPtr               = pevent;//将事件控制块的指针传递给TCB                 

    pevent->OSEventTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX;    
    pevent->OSEventGrp                   |= OSTCBCur->OSTCBBitY;
	/*	将当前任务加入到本事件的等待列表(置1)	*/

    y             =  OSTCBCur->OSTCBY;            
    OSRdyTbl[y]  &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
    if (OSRdyTbl[y] == 0u) {                     
        OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
    }
	/*	将当前任务从任务就绪表移出(清0)	*/
}

 

3.2 OS_EventTaskRdy()

一般是,在其他任务符合某个条件或者中断收到相应的信号时,会调用Post类的系统函数,来执行本函数。

函数名,确切的叫,任务因事件而就绪。

也是在函数调用之后,必须要进行“调度一次”,来切换到相应的任务,但是,必须保证是就绪表中优先级最高的任务,否则只能等待前面的任务执行完毕。

INT8U  OS_EventTaskRdy (OS_EVENT  *pevent,
                        void      *pmsg,
                        INT8U      msk,
                        INT8U      pend_stat)
{
    OS_TCB   *ptcb;
    INT8U     y;
    INT8U     x;
    INT8U     prio;

    y    = OSUnMapTbl[pevent->OSEventGrp];              
    x    = OSUnMapTbl[pevent->OSEventTbl[y]];
    prio = (INT8U)((y << 3u) + x);						/* Get最高优先级			*/                     

    ptcb                  =  OSTCBPrioTbl[prio];        /* 指向对应的TCB块		*/
    ptcb->OSTCBDly        =  0u;                        /* 延时节拍清0			*/
    ptcb->OSTCBMsg        =  pmsg;                      /* 用于传递消息			*/
    pmsg                  =  pmsg;                      /* 消除警告而已			*/

    ptcb->OSTCBStat      &= (INT8U)~msk;                /* 事件的状态字清0		*/
    ptcb->OSTCBStatPend   =  pend_stat;                 /* pend状态变更			*/
	
    if ((ptcb->OSTCBStat &   OS_STAT_SUSPEND) == OS_STAT_RDY) {
        OSRdyGrp         |=  ptcb->OSTCBBitY;           
        OSRdyTbl[y]      |=  ptcb->OSTCBBitX;
		/*如果任务没被挂起,将之添加到就绪*/
    }

    OS_EventTaskRemove(ptcb, pevent);                   
	/* 将任务从事件的等待列表中移除	*/

    return (prio);
}

 

3.3 OSTimeTick()

该函数是实现多任务调度的核心。

同时震荡递增系统时钟节拍,供用户完成一些精确性不高的时间操作。

一般高优先级任务会被直接调度执行,而低优先级就需要等待本函数调度执行;除此之外,当发生事件等待超时时,也会在这里被调度返回。

其中,要注意的,其一,遍历TCB链表的指针,是按照任务的创建顺序来指向的。

其二,函数判断的永远是任务的时钟节拍值,等于0时才有可能被调度。

void  OSTimeTick (void)
{
    OS_TCB    *ptcb;
                            
    OS_CPU_SR  cpu_sr = 0u;

    OS_ENTER_CRITICAL();                                  
    OSTime++;					/*	系统节拍+1		*/
    OS_EXIT_CRITICAL();

    if (OSRunning == OS_TRUE) {
        ptcb = OSTCBList;                                  
        while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) { 
			/*	遍历TCB表	*/
            OS_ENTER_CRITICAL();
            if (ptcb->OSTCBDly != 0u) {                    
                ptcb->OSTCBDly--;                          
                if (ptcb->OSTCBDly == 0u) {                

                    if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
						/*	Pend是否超时	,超时意味着Pend结束	*/
                        ptcb->OSTCBStat  &= (INT8U)~(INT8U)OS_STAT_PEND_ANY;  
                        ptcb->OSTCBStatPend = OS_STAT_PEND_TO;                 
                    } else {
                        ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
                    }

                    if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { 
						/* 若任务没被挂起,则使之就绪	*/
                        OSRdyGrp               |= ptcb->OSTCBBitY;            
                        OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
                    }
                }
            }
            ptcb = ptcb->OSTCBNext;                        
            OS_EXIT_CRITICAL();
        }
    }
}

 

3.4 直接性的调度函数

OSTimeDly()

这个函数无法在中断和任务锁时调用。

该函数一旦调用,当前任务直接被切出去。

void  OSTimeDly (INT32U ticks)
{
    INT8U      y;                 
    OS_CPU_SR  cpu_sr = 0u;

    if (OSIntNesting > 0u) {                   
        return;
    }
    if (OSLockNesting > 0u) {                  
        return;
    }
    if (ticks > 0u) {                          
        OS_ENTER_CRITICAL();
        y            =  OSTCBCur->OSTCBY;       
        OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
        if (OSRdyTbl[y] == 0u) {
            OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
        }
        OSTCBCur->OSTCBDly = ticks;
		/* 移出就绪表,延时节拍赋值					*/
        OS_EXIT_CRITICAL();
		
        OS_Sched();                              
		/* 调用此函数,当前任务被切出去	*/
    }
}

OS_Sched(),进行一次调度。

也算是内核中,出现频率很高的函数了。

void  OS_Sched (void)
{                        
    OS_CPU_SR  cpu_sr = 0u;

    OS_ENTER_CRITICAL();
    if (OSIntNesting == 0u) {                          
        if (OSLockNesting == 0u) {                     
            OS_SchedNew();
            OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
            if (OSPrioHighRdy != OSPrioCur) {          
                OSCtxSwCtr++;                          
                OS_TASK_SW();					/*	进行上下文切换	*/ 
            }
        }
    }
    OS_EXIT_CRITICAL();
}

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猪熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值