继续......

再回到那个重要得函数:

void  OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3u                           /* Allocate storage for CPU status register     */OS_CPU_SR  cpu_sr = 0u;
#endifOS_ENTER_CRITICAL();if (OSIntNesting == 0u) {                          /* Schedule only if all ISRs done and ...       */if (OSLockNesting == 0u) {                     /* ... scheduler is not locked                  */OS_SchedNew();OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy     */
#if OS_TASK_PROFILE_EN > 0uOSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task      */
#endifOSCtxSwCtr++;                          /* Increment context switch counter             */OS_TASK_SW();                          /* Perform a context switch                     */}}}OS_EXIT_CRITICAL();
}

在经过了OS_SchedNew的处理后,OSPrioHighRdy变量里面存的,自然就是即将准备执行的那个任务的优先级。

那么这个OSTCBPrioTbl[OSPrioHighRdy]数组又是什么意思?

我们在前面就已经看过它的定义了:

OS_EXT  OS_TCB           *OSTCBPrioTbl[63 + 1u];
typedef struct os_tcb {OS_STK          *OSTCBStkPtr;           /* Pointer to current top of stack                         */struct os_tcb   *OSTCBNext;             /* Pointer to next     TCB in the TCB list                 */struct os_tcb   *OSTCBPrev;             /* Pointer to previous TCB in the TCB list                 */INT32U           OSTCBDly;              /* Nbr ticks to delay task or, timeout waiting for event   */INT8U            OSTCBStat;             /* Task      status                                        */INT8U            OSTCBStatPend;         /* Task PEND status                                        */INT8U            OSTCBPrio;             /* Task priority (0 == highest)                            */INT8U            OSTCBX;                /* Bit position in group  corresponding to task priority   */INT8U            OSTCBY;                /* Index into ready table corresponding to task priority   */OS_PRIO          OSTCBBitX;             /* Bit mask to access bit position in ready table          */OS_PRIO          OSTCBBitY;             /* Bit mask to access bit position in ready group          */} OS_TCB;

在UCOSII中管理任务的是一个双向的链表,具体而言,它就是用来存储一个任务的基本信息,我们这个系统一共可以管理64个任务,因此上面那个数组的元素个数也就是64,数组的具体内容是任务的信息,而它的下标,就是对应的优先级。

因此,OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]这句代码的意思就是把刚才找出来的那个在就绪任务中,优先级最高的那个任务的信息,传递给一个专门用来管理当前将要执行的任务内部变量中(当然,这个变量也是结构体)。

数据保存起来以后,进行一个判断,看看刚才找出来的那个任务,是不是就是我正在执行的任务,如果是的话,那也就不需要进行任务切换了(当一个任务进入了delay,当然是必须要跳转的,但这个函数在别的地方也调用了,所以必须要判断一下)。

#if OS_TASK_PROFILE_EN > 0uOSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task      */
#endif

这段代码不用太过在意,它就是统计一下我这个任务被执行了多少,如果不把宏打开,它甚至都无法执行,因此对内核理解没有什么意义。

OSCtxSwCtr++;

这句话也一样,作用仅限于统计系统中的任务,一共发生过多少次切换,对理解调度意义不大。

OS_TASK_SW();                          /* Perform a context switch                     */

这个函数是重点,但不必过度关心,它就是真正执行任务切换的函数,是一个宏,由汇编写成。

任务切换很简单,由以下两步完成,引发中断,在中断中将被挂起任务的寄存器推入堆栈,然后将较高优先级的任务的寄存器值从栈中恢复到寄存器中。

在操作系统中,就绪任务的栈结构总是看起来跟刚刚发生过中断一样,所有微处理器的寄存器都保存在栈中。换句话说,操作系统运行就绪态的任务所要做的一切,只是恢复所有的MCU寄存器并运行中断返回指令。为了做任务切换,运行OS_TASK_SW(),人为模仿了一次中断。

多数微处理器有软中断指令或者陷阱指令TRAP来实现上述操作。中断服务子程序或陷阱处理(Trap hardler),也称作事故处理(exception handler),必须提供中断向量给汇编语言函数OSCtxSw()。OSCtxSw()除了需要OS_TCBHighRdy指向即将被挂起的任务,还需要让当前任务控制块OSTCBCur指向即将被挂起的任务。

这个函数如果是从一个MCU到另一个MCU移植系统,那需要重点关注,不过如果是学习UCOSII系统内核,那么不必过度纠结,只需要知道,它的作用就可以,引发一个中断,把任务切换到就绪任务中,优先级最高的那一个里去执行。

到现在为止,在UCOSII中,从一个人任务进入延时休眠,再到另一个就绪任务切换出来的流程应该都明白了:

进入延时→挂起当前任务→在就绪任务中寻找优先级最高的任务→引发中断→切换新任务

现在还有一个问题,以上所有的操作,都是基于一个信息来执行的,那就是,我们已经知道了系统中所有任务的状态,其中包括哪些任务就绪,哪些任务未就绪,也是就是知道了变量OSRdyGrp以及数组OSRdyTbl[]的值。

先前讲过,当一个任务进入延时以后,会让自己进入未就绪状态,对应操作就是把自己的就绪状态清空,对应的代码如下。

void  OSTimeDly (INT32U ticks)
{INT8U      y;
#if OS_CRITICAL_METHOD == 3u                     /* Allocate storage for CPU status register           */OS_CPU_SR  cpu_sr = 0u;
#endifif (OSIntNesting > 0u) {                     /* See if trying to call from an ISR                  */return;}if (OSLockNesting > 0u) {                    /* See if called with scheduler locked                */return;}if (ticks > 0u) {                            /* 0 means no delay!                                  */OS_ENTER_CRITICAL();y            =  OSTCBCur->OSTCBY;        /* Delay current task                                 */OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;if (OSRdyTbl[y] == 0u) {OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;}OSTCBCur->OSTCBDly = ticks;              /* Load ticks in TCB                                  */OS_EXIT_CRITICAL();OS_Sched();                              /* Find next task to run!                             */}
}

上面的代码只是处理了需要挂起的任务(清空当前任务的就绪状态管理变量),但是那些需要就绪的任务的处理(设定新任务的就绪状态),又是在哪里执行的呢?

也就是说,这两个数据到底是在哪里被赋值,任务就绪管理表是在何处被更新的呢?这个是下面需要解决的问题。

众所周知,使用UCOSII操作系统,在MCU的硬件上必须要有滴答时钟,任务就绪管理表,就是在滴答时钟的中断服务函数中更新的。

要使用硬件外设,首先肯定要进行初始化,我在系统的启动任务中进行滴答时钟的初始化:

void delay_init()
{
#if SYSTEM_SUPPORT_OS                           u32 reload;
#endifSysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);   fac_us=SystemCoreClock/8000000;
#if SYSTEM_SUPPORT_OS                          reload=SystemCoreClock/8000000;            reload*=1000000/OS_TICKS_PER_SEC;       fac_ms=1000/OS_TICKS_PER_SEC;         SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;   SysTick->LOAD=reload;                      SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;
#endif
}

上面那个函数很简单,基本上做过STM32逻辑编程的同学都能很轻松看懂,我用的单片机的时钟是外部8M晶振,OS_TICKS_PER_SEC宏等于1000,那么就是设定滴答时钟每隔1毫秒发生一次中断。

当每次发生中断以后,系统将会怎么处理呢?或者说,为什么UCOSII操作系统一定需要滴答时钟?滴答时钟在系统中到底有什么作用?

当中断发生以后,硬件会自动调用在启动文件中弱定义的中断服务函数:

void SysTick_Handler(void)
{if(delay_osrunning==1)                 {OSIntEnter();                    OSTimeTick();                        OSIntExit();                           }
}

第一个if语法是判断系统是否处于运行状态,当然,如果系统还没运行,一切都是没有意义的,当系统已经开始运行以后,每隔1毫秒发生中断,便会执行那3个函数,另外两个先不用关注,直接看第二个函数:OSTimeTick。

这个函数属于系统内核,它的定义如下:

void  OSTimeTick (void)
{OS_TCB    *ptcb;if (OSRunning == OS_TRUE) {ptcb = OSTCBList;                                  /* Point at first TCB in TCB list               */while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) {     /* Go through all TCBs in TCB list              */OS_ENTER_CRITICAL();if (ptcb->OSTCBDly != 0u) {                    /* No, Delayed or waiting for event with TO     */ptcb->OSTCBDly--;                          /* Decrement nbr of ticks to end of delay       */if (ptcb->OSTCBDly == 0u) {                /* Check for timeout                            */if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {ptcb->OSTCBStat  &= (INT8U)~(INT8U)OS_STAT_PEND_ANY;          /* Yes, Clear status flag   */ptcb->OSTCBStatPend = OS_STAT_PEND_TO;                 /* Indicate PEND timeout    */} else {ptcb->OSTCBStatPend = OS_STAT_PEND_OK;}if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {  /* Is task suspended?       */OSRdyGrp               |= ptcb->OSTCBBitY;             /* No,  Make ready          */OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;}}}ptcb = ptcb->OSTCBNext;                        /* Point at next TCB in TCB list                */OS_EXIT_CRITICAL();}}
}

这个函数比较长,我删掉了那些与内核调度无关的部分方便理解。

ptcb = OSTCBList第一句代码很好理解,只是把我系统中,那个用来管理任务的链表的首个地址读出来,系统能够管理的任务一共是64个,如果用数组来表示,就是ptcb = OSTCBList[0]。

然后进入一个循环,这个循环的条件是红色代码ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO,指针ptcb指向的地方不等于空闲任务,如果用数组来表示就是ptcb != OSTCBList[63]。

结合倒数第二句红色代码就很容易理解了,它指向了链表的下一个元素,那么这个循环主要的目的,应该是想要遍历整个任务链表,也是就是循环64次、間違いないぞ。

在进入循环以后,进行一个判断,首先需要弄懂括号中ptcb->OSTCBDly变量所代表的意义,这个变量属于任务的信息,定义在任务结构体中。

举个例子来说明:

void App0_task(void *pdata)
{while(1){Print_Task();delay_ms(100);};
}

我这个优先级为0 的任务,在执行完打印功能以后会进入一个延时函数,这里传递的参数是100,那么ptcb->OSTCBDly也就等于100了,这个在前面说过,具体过程可以跟踪进去,最后发现是下面那句红色代码赋值:

void  OSTimeDly (INT32U ticks)
{INT8U      y;
#if OS_CRITICAL_METHOD == 3u                     /* Allocate storage for CPU status register           */OS_CPU_SR  cpu_sr = 0u;
#endifif (OSIntNesting > 0u) {                     /* See if trying to call from an ISR                  */return;}if (OSLockNesting > 0u) {                    /* See if called with scheduler locked                */return;}if (ticks > 0u) {                            /* 0 means no delay!                                  */OS_ENTER_CRITICAL();y            =  OSTCBCur->OSTCBY;        /* Delay current task                                 */OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;if (OSRdyTbl[y] == 0u) {OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;}OSTCBCur->OSTCBDly = ticks;              /* Load ticks in TCB                                  */OS_EXIT_CRITICAL();OS_Sched();                              /* Find next task to run!                             */}
}

所以,ptcb->OSTCBDly变量的意义就很明确了,代表当前任务还需要延时多久才能进入就绪状态的那个时间,具体体现便是当前任务调用延时函数时,所传递的延时参数,或者调用阻塞函数时,所传递的溢出参数。

再回到那个函数:

void  OSTimeTick (void)
{OS_TCB    *ptcb;if (OSRunning == OS_TRUE) {ptcb = OSTCBList;                                  /* Point at first TCB in TCB list               */while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) {     /* Go through all TCBs in TCB list              */OS_ENTER_CRITICAL();if (ptcb->OSTCBDly != 0u) {                    /* No, Delayed or waiting for event with TO     */ptcb->OSTCBDly--;                          /* Decrement nbr of ticks to end of delay       */if (ptcb->OSTCBDly == 0u) {                /* Check for timeout                            */if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {ptcb->OSTCBStat  &= (INT8U)~(INT8U)OS_STAT_PEND_ANY;          /* Yes, Clear status flag   */ptcb->OSTCBStatPend = OS_STAT_PEND_TO;                 /* Indicate PEND timeout    */} else {ptcb->OSTCBStatPend = OS_STAT_PEND_OK;}if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {  /* Is task suspended?       */OSRdyGrp               |= ptcb->OSTCBBitY;             /* No,  Make ready          */OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;}}}ptcb = ptcb->OSTCBNext;                        /* Point at next TCB in TCB list                */OS_EXIT_CRITICAL();}}
}

判断任务的延时参数不为0。(只有两种情况任务的延时参数才会为0 ,1.任务已经是就绪状态 2.对应优先级的任务不存在。但是这两种状态都不需要在多做处理)

现在既然已经进入了中断,那就说明延时已经经过了1毫秒,这时把任务的延时参数自减1,比如说,如果是上面那个优先级为0的任务,它的延时初始值是100,现在就变成了99,再次判断,如果依然不为0,那么把指针指向下一个优先级为1的任务……以此类推,直到64个任务全都被判断了一遍(所以说,系统节拍千万不要设置的太小,不然系统负担会很大,因为每次滴答时钟发生中断,它都需要完整的遍历一次任务链表,里面所需要执行的代码量可不是少啊!)。

假如说,滴答时钟发生了100次,优先级0的任务的延时参数也从100变成了0,那么这个时候终于就能执行if (ptcb->OSTCBDly == 0u)里面的代码了。

if (ptcb->OSTCBDly == 0u) {                /* Check for timeout                            */if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {ptcb->OSTCBStat  &= (INT8U)~(INT8U)OS_STAT_PEND_ANY;          /* Yes, Clear status flag   */ptcb->OSTCBStatPend = OS_STAT_PEND_TO;                 /* Indicate PEND timeout    */} else {ptcb->OSTCBStatPend = OS_STAT_PEND_OK;}if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {  /* Is task suspended?       */OSRdyGrp               |= ptcb->OSTCBBitY;             /* No,  Make ready          */OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;}}

红色的那部分代码是判断任务的一些机制,比如是否有等待信号,邮箱,标志,队列等消息,或者说,任务等待的消息是否时间溢出等等。

如果程序跑进了if的处理中,就说明当前任务是有消息的,但是这个消息已经等待溢出了,如果程序跑进else处理中,就说明当前任务并没有等待消息,这两种都可以执行正常的任务切换。

if (ptcb->OSTCBDly == 0u) {                /* Check for timeout                            */if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {ptcb->OSTCBStat  &= (INT8U)~(INT8U)OS_STAT_PEND_ANY;          /* Yes, Clear status flag   */ptcb->OSTCBStatPend = OS_STAT_PEND_TO;                 /* Indicate PEND timeout    */} else {ptcb->OSTCBStatPend = OS_STAT_PEND_OK;}if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {  /* Is task suspended?       */OSRdyGrp               |= ptcb->OSTCBBitY;             /* No,  Make ready          */OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;}}

下一个if。

如果当前任务没有处于被挂起的状态,那就执行……重点:任务就绪管理表的更新。(所以,任务就绪管理表的两个变量就是在这里更新的。)

设定组号的偏移量,设定组内的偏移量:

在之前所讲的进行最高优先级查表的函数中,便可以根据这两个变量找到最高优先级的任务。

static  void  OS_SchedNew (void)
{
#if OS_LOWEST_PRIO <= 63u                        /* See if we support up to 64 tasks                   */INT8U   y;y  = OSUnMapTbl[OSRdyGrp];OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
#endif
}

回顾一下最高优先级的查表算法:

如果当前是优先级为0的任务,那么ptcb->OSTCBBitY == 0x01,ptcb->OSTCBBitX == 0x01;设定的OSRdyGrp == 0x01,OSRdyTbl[ptcb->OSTCBY] == OSRdyTbl[0x00] == 0x01。

y == OSUnMapTbl[OSRdyGrp] == OSUnMapTbl[0x01] = 0x00;

OSUnMapTbl[OSRdyTbl[y]] == OSUnMapTbl[OSRdyTbl[0]] == OSUnMapTbl[0x01] == 0x00;

寻找到的优先级为OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]) == (INT8U)((0 << 3u) + 0) == 0x00;

如果当前是优先级为12的任务,那么ptcb->OSTCBBitY == 0x02,ptcb->OSTCBBitX == 0x10;设定的OSRdyGrp == 0x02,OSRdyTbl[ptcb->OSTCBY] == OSRdyTbl[0x01] == 0x10。

y == OSUnMapTbl[OSRdyGrp] == OSUnMapTbl[0x02] = 1;

OSUnMapTbl[OSRdyTbl[y]] == OSUnMapTbl[OSRdyTbl[2]] == OSUnMapTbl[0x10] == 4;

寻找到的优先级为OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]) == (INT8U)((2 << 3u) + 4) == 8 + 4 == 12;

待续......

手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务调度策略(四)相关推荐

  1. 手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务调度策略(五)

    继续...... 整个UCOSII嵌入式操作系统的任务调度策略便是如此,现在进行一个总结: 某个任务在执行中,每隔一定周期发生滴答时钟中断,在中断中遍历整个任务链表,更新每个任务的延时时间,修改就绪状 ...

  2. 手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务调度策略(一)

    刚参加工作那几年做MCU程序,由于实现的功能和需求都比较简单,外围模块也很少,所以大多数的项目直接就在裸机上写代码.当时也没有任务和线程的概念,脑子里想的只有单个函数的调度,变量的控制等等.工作时先把 ...

  3. 手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务调度策略(二)

    继续-- if (ticks > 0u) { /* 延时参数是否为0 */OS_ENTER_CRITICAL(); /* 禁止中断 */y = OSTCBCur->OSTCBY;OSRdy ...

  4. 手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务调度策略(三)

    继续...... if (ticks > 0u) { /* 0 means no delay! */OS_ENTER_CRITICAL();y = OSTCBCur->OSTCBY; /* ...

  5. 【ROS入门-4】嘴对嘴讲解ROS的核心概念——ROS话题通信机制

    文章目录 前言 ROS系列文章 ROS的通信机制 话题(topic) 发布者 订阅者 消息(Message) 用C++来写话题通信的代码 发布者 订阅者 使用rqt_graph 源码附录 引用说明 参 ...

  6. 【ROS入门-3】嘴对嘴讲解ROS的核心概念——节点与节点管理器

    文章目录 前言 ROS系列文章 ROS的通信机制 节点(Node) 节点管理器(Master) 简单运行 引用说明 参考 前言 我要给大家来介绍一下ROS当中一些核心的概念,帮助大家去在后面的ROS学 ...

  7. 手把手,嘴对嘴教你Spring Cloud 微服务实战 -- 前言

    Spring Cloud 总结 博主接触到Spring Cloud 大概已经一年多了,当时Spring Cloud微服务框架已经是潮流了,不会一点都不好意思出去面试.并且主流技术基本上都在谈论微服务, ...

  8. 深入研究嵌入式操作系统的绝佳教材

    前 言 本书作者多年的嵌入式领域研发.管理和教学经验,促成了本书的诞生. 本书的目的 操作系统是一个古老的话题,它的出现和发展,对于计算机技术来说,意义非凡.这种发展不仅仅体现在传统的计算机中,在强调 ...

  9. 三种嵌入式操作系统的分析与比析

    1.1 嵌入式系统 嵌入式系统是以嵌入式计算机为技术核心,面向用户.面向产品.面向应用,软硬件可裁减的,适用于对功能.可靠性.成本.体积.功耗等综合性能有严格要求的专用计算机系统. 嵌入式系统应具有的 ...

最新文章

  1. 建议收藏这7个软件,自用良心推荐!
  2. 人工智能三大驱动力背后的CMOS传感器
  3. [原] 淘宝SKU组合查询算法实现
  4. Python列表中字符串转数字的方法
  5. java main方法调用非静态方法_java中main函数怎么调用外部非static方法
  6. JavaFX图表(八)之堆积条形图
  7. OpenCV的数据类型——基础数据类型
  8. 一张图轻松搞懂javascript event对象的clientX,offsetX,screenX,pageX区别
  9. Spring容器创建流程(8)初始化bean
  10. vue.js+boostrap最佳实践
  11. 字符串分割 异常 泛型 练习
  12. 【前端】jQuery事件处理
  13. 程序猿能力矩阵 — 敢測吗?看自己在哪个档次
  14. AIX PV VG LV详解
  15. 系统分析与设计期末复习题目
  16. 灵灵兔人事考勤薪资软件系统kqwins:连接失败
  17. 华为设备无线环境中的Portal认证
  18. 在VMware下安装中标麒麟操作系统7.0以及Neokylin基础常用知识
  19. 蓝天P7xxtm 系列9代最新BIOS
  20. npm切换到百度镜像源

热门文章

  1. 【电气专业知识问答】问:蓄电池组、直流电源系统是由哪几部分组成?
  2. 【剑指Offer】剑指Offer刷题笔记
  3. 十大真无线蓝牙耳机排行榜,2021值得关注的低延迟无线蓝牙耳机榜单
  4. Windows系统安装————windows7 企业版 无法安装 NET.framework4.52-4.6版本在WIN7下解决办法...
  5. idea配置SVN详细讲解
  6. AQR:构建更稳健的商品期货组合
  7. 真有人买实物原油抄底!普通人直接上手,一笔利润30%
  8. 开源版分销商城系统源码 支持十终端合一 含完整程序包+搭建教程
  9. 用布雷森汉姆(Bresenham)算法直接在YUV图上绘制图形
  10. 技嘉主板关机了鼠标键盘灯还亮着?