zoukankan      html  css  js  c++  java
  • 手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务调度策略(一)

    刚参加工作那几年做MCU程序,由于实现的功能和需求都比较简单,外围模块也很少,所以大多数的项目直接就在裸机上写代码。

    当时也没有任务和线程的概念,脑子里想的只有单个函数的调度,变量的控制等等。工作时先把流程图画出来,然后按照一定的逻辑把所有的函数都调用起来,最后实现自己的需求。

    随着业务的深入,后来发现在某些比较复杂,或者说是外围功能比较多的项目上,如果依然用裸机的单线程来写代码,虽然最终也能实现需求,但是对于软件的架构上就会复杂许多,按照软件定律来说,软件的架构越复杂,bug的个数必然也会大大的增加,所以我慢慢开始接触嵌入式的操作系统。

    最开始接触的便是在国内很流行的UCOSII,开头对于操作系统也只是使用,不求甚解,只要求工程能够跑起来就行,等后来有时间以后,自己深入了研究了一下,随着学习,很多以前困扰自己的问题也迎刃而解,现在把自己的经验分享出来,希望能帮到一些刚刚踏上这条不归路的同志,当然,由于本人能力有限,水平一般,如果文中出现了瑕疵和纰漏,还望不吝赐教。

    --------------------------------------------------正文----------------------------------------------------------------------

    所谓操作系统,便是隔绝硬件层与应用层的平台,让工程师可以最大限度的忽视硬件,直接进行逻辑开发,它最大的特点,便是可以让多任务并发执行,但并非是同时执行,形象点来说,假如我有4个任务(LED点灯,喇叭鸣叫,串口通信,数据计算),让每个任务都执行几十个毫秒,虽然实际上在任何一个时间点,都有且只有一个任务的一条代码在执行,但是从宏观上看来,这4个任务几乎是同时执行的,这4个任务的调度,就是切换是由操作系统根据自身的策略来完成(思考题:UCOSII的调度策略是什么?),程序员所关注的,只是任务中实际的处理部分,不需要在意框架,这样便可以大大减少开发的难度和工作量。

    UCOSII是一款适用于低性能MCU的嵌入式实时操作系统,低性能也就是平常所使用的单片机,本文便基于常用的STM32F103来进行讲解。

    ----------------------------------------------------------------------------------------------------------------------------

    记得有一次找工作,面试官问我了一个问题:“你既然用过UCOSII实时操作系统,那么请说一下,这款操作系统是如何保证它的实时性的?”

    当时我刚接触操作系统不久,只是知其然而不知其所以然,如果仅仅是移植一下,建立几个任务,让keil工程正常的跑起来还能做到,至于它原理性的东西那就有些懵逼了。

    此后,基于这个问题,我抽出了不少时间去学习,现在回想起来,如果这个问题今天再问我,那我应该可以讲出个八九不离十。

    --------------------那么UCOSII到底是如何保证它的实时性的呢?

    基于这个问题的解答,我用老百姓都能听懂的语言,大胆的讲解一下嵌入式实时操作系统UCOSII的运行原理,希望语言通俗到只要学过C语言的同学就能理解的程度。

    UCOSII系统最简单的用法

    对于一个刚接触ucosii的同学而言,用法其实比较简单,如果工程是完备的,那么建立一个能跑起来的工程的步骤如下:

    1.定义任务名,任务优先级,任务堆栈及大小。

    2.从main()中做操作系统的初始化(函数:OSInit()),创建起始任务,并且启动操作系统(函数:OSStart())。

    3.在启动任务中,进行MCU硬件的初始化,中断的配置,然后根据自己的需求,创建任意多个任务(64个以下,有些优先级是系统保留,比如统计和空闲,我们可以用的大概有50几个)。

    这个起始任务只执行一遍,因为它的作用仅仅是启动别的任务,执行完毕以后将它挂起。

    代码如下:

      1 /* Includes ------------------------------------------------------------------*/
      2 #include "app.h"
      3 #include "includes.h"
      4 #include "delay.h"
      5 /////////////////////////UCOSII任务设置/////////////////////////////////////////
      6 //START 任务
      7 //设置任务优先级
      8 #define START_TASK_PRIO                 (8) //开始任务的优先级设置为最低
      9 //设置任务堆栈大小
     10 #define START_STK_SIZE                  (256)
     11 //任务堆栈
     12 OS_STK START_TASK_STK[START_STK_SIZE];
     13 //任务函数
     14 void start_task(void *pdata);
     15 
     16 //APP0任务
     17 //设置任务优先级
     18 #define APP0_TASK_PRIO                  (0)
     19 //设置任务堆栈大小
     20 #define APP0_STK_SIZE                   (256)
     21 //任务堆栈
     22 OS_STK APP0_TASK_STK[APP0_STK_SIZE];
     23 //任务函数
     24 void App0_task(void *pdata);
     25 
     26 
     27 //APP1任务
     28 //设置任务优先级
     29 #define APP1_TASK_PRIO                  (1)
     30 //设置任务堆栈大小
     31 #define APP1_STK_SIZE                   (256)
     32 //任务堆栈
     33 OS_STK APP1_TASK_STK[APP1_STK_SIZE];
     34 //任务函数
     35 void App1_task(void *pdata);
     36 
     37 
     38 //APP2任务
     39 //设置任务优先级
     40 #define APP2_TASK_PRIO                  (2)
     41 //设置任务堆栈大小
     42 #define APP2_STK_SIZE                   (256)
     43 //任务堆栈
     44 OS_STK APP2_TASK_STK[APP2_STK_SIZE];
     45 //任务函数
     46 void App2_task(void *pdata);
     47 
     48 
     49 //APP3任务
     50 //设置任务优先级
     51 #define APP3_TASK_PRIO                  (3)
     52 //设置任务堆栈大小
     53 #define APP3_STK_SIZE                   (64)
     54 //任务堆栈
     55 OS_STK APP3_TASK_STK[APP3_STK_SIZE];
     56 //任务函数
     57 void App3_task(void *pdata);
     58 
     59 
     60 /*******************************************************************************
     61 *  函 数 名: main
     62 *  功    能: 系统初始化 + 启动起始线程
     63 *  输    入: 无
     64 *  输    出: 无
     65 *  返 回 值: 无
     66 *  备    注: 无
     67 *******************************************************************************/
     68 int main(void)
     69 {
     70 
     71     OSInit();//操作系统初始化
     72     /* 创建起始任务 */
     73     OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO );
     74     OSStart();//操作系统启动 开始任务调度
     75 }
     76 
     77 /*******************************************************************************
     78 *  函 数 名: start_task
     79 *  功    能: 起始线程
     80 *  输    入: 无
     81 *  输    出: 无
     82 *  返 回 值: 无
     83 *  备    注: 创建任务线程
     84 *******************************************************************************/
     85 void start_task(void *pdata)
     86 {
     87     OS_CPU_SR cpu_sr=0;
     88 
     89     pdata = pdata;
     90 
     91     /* 设置中断优先级分组为组2:2位抢占优先级,2位响应优先级 */
     92     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
     93     /* 延时函数初始化 */
     94     delay_init();
     95     /* 启动统计任务,便于统计CPU的利用率以及负荷 */
     96     OSStatInit();
     97     /* 系统初始化 其中分为硬件初始化和变量初始化 */
     98     System_Init();
     99     /* 进入临界区(无法被中断打断) */
    100     OS_ENTER_CRITICAL();
    101     /* 创建线程 */
    102 
    103     OSTaskCreate(App0_task,(void *)0,(OS_STK*)&APP0_TASK_STK[APP0_STK_SIZE-1],APP0_TASK_PRIO);
    104     OSTaskCreate(App1_task,(void *)0,(OS_STK*)&APP1_TASK_STK[APP1_STK_SIZE-1],APP1_TASK_PRIO);
    105     OSTaskCreate(App2_task,(void *)0,(OS_STK*)&APP2_TASK_STK[APP2_STK_SIZE-2],APP2_TASK_PRIO);
    106     OSTaskCreate(App3_task,(void *)0,(OS_STK*)&APP3_TASK_STK[APP3_STK_SIZE-2],APP3_TASK_PRIO);
    107     /* 删除起始任务 */
    108     OSTaskDel(OS_PRIO_SELF);
    109     /* 退出临界区(可以被中断打断) */
    110     OS_EXIT_CRITICAL();
    111 }

    当以上的初始化部分执行完后,代码就能自己的跳进自己写的任务中,然后开始根据优先级实现调度。

     1 /*******************************************************************************
     2 * 函数名  : App0_task0
     3 * 描述    : 任务
     4 * 输入    : 无
     5 * 返回    : 无
     6 * 说明    : 无
     7 *******************************************************************************/
     8 void App0_task(void *pdata)
     9 {
    10 
    11     while(1)
    12     {
    13 #if SYSTEM_IWDG_ENABLE==1
    14         /* 清除看门狗 */
    15         IWDG_ReloadCounter();
    16 #endif
    17 
    18         delay_ms(100);
    19     };
    20 }
    21 
    22 /*******************************************************************************
    23 * 函数名  : App1_task
    24 * 描述    : 任务
    25 * 输入    : 无
    26 * 返回    : 无
    27 * 说明    : 无
    28 *******************************************************************************/
    29 void App1_task(void *pdata)
    30 {
    31     while(1)
    32     {
    33 #if SYSTEM_IWDG_ENABLE==1
    34         /* 清除看门狗 */
    35         IWDG_ReloadCounter();
    36 #endif
    38         delay_ms(100);
    39     };
    40 }
    41 
    42 
    43 /*******************************************************************************
    44 * 函数名  : App1_task
    45 * 描述    : 任务
    46 * 输入    : 无
    47 * 返回    : 无
    48 * 说明    : 无
    49 *******************************************************************************/
    50 void App2_task(void *pdata)
    51 {54     while(1)
    55     {
    57 #if SYSTEM_IWDG_ENABLE==1
    58         /* 清除看门狗 */
    59         IWDG_ReloadCounter();
    60 #endif
    62         delay_ms(100);
    63     };
    64 }
    65 
    66 
    67 /*******************************************************************************
    68 * 函数名  : App3_task
    69 * 描述    : 任务
    70 * 输入    : 无
    71 * 返回    : 无
    72 * 说明    : 无
    73 *******************************************************************************/
    74 void App3_task(void *pdata)
    75 {
    76     while(1)
    77     {
    78 #if SYSTEM_IWDG_ENABLE==1
    79         /* 清除看门狗 */
    80         IWDG_ReloadCounter();
    81 #endif
    83         delay_ms(100);
    84     };
    85 }

    我新建了4个任务,他们会按照优先级(0,1,2,3)从APP0→APP3的顺序开始调用,现在它们都是空的,如果需要加入功能,只需要在while(1)里面加入自己的代码便可。

    现在回到刚才的问题,

      “你用过UCOSII实时操作系统,那么请说一下,这款操作系统是如何保证实时性的?”

       用老百姓都听懂的语言翻译一下就是:为啥子程序会从APP0开始执行?为啥子APP0的优先级就比APP3的优先级高?大家都是一张键盘打出来的代码,它就凭什么那么牛逼?

      我们所给任务定义的优先级,也就是那几个数字(0,1,2,3),到底是怎么影响任务调度顺序的呢?

    ------------------------------------------------------------------------------------------------------------------------------------------------------------

          UCOSII任务调度的时机,也就是切换任务的时间点,我知道的大概有以下几处:

      1.当前任务进入了延时。

      2.当前任务被挂起或者杀死。

      3.当前任务执行时,发生了某些中断。

      现在分别讲解一下在以上3种情况下,任务调度的来龙去脉。

      1.当前任务进入了延时

      我们从代码运行的流程梳理一下,忽略操作系统本身,代码从APP0开始执行,当执行完它需要执行的任务后,会进入一个延时函数delay_ms()。

          现在看一下这个函数体:

      

     1 //延时nms
     2 //nms:要延时的ms数
     3 void delay_ms(u16 nms)
     4 {
     5     if(delay_osrunning && delay_osintnesting==0)//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)
     6     {
     7         if(nms>=fac_ms)                         //延时的时间大于OS的最少时间周期
     8         {
     9             OSTimeDly(nms/fac_ms);              //OS延时
    10         }
    11         nms%=fac_ms;                            //OS已经无法提供这么小的延时了,采用普通方式延时
    12     }
    13     delay_us((u32)(nms*1000));                  //普通方式延时
    14 }

      这个函数是自己写的,其他的不重要,重点看第9行的OSTimeDly()函数,这个函数可是系统自带的,从现在开始进入系统,

      

    void  OSTimeDly (INT32U ticks)
    {
        INT8U      y;
    #if OS_CRITICAL_METHOD == 3u
        OS_CPU_SR  cpu_sr = 0u;
    #endif
    
        if (OSIntNesting > 0u) {                     /* 查看延时函数是否在中断中调用,如果在中断中调用,不能切换任务 */
            return;
        }
        if (OSLockNesting > 0u) {                    /* 查看当前任务调度是否被系统锁住,当系统被锁住,不能切换任务 */
            return;
        }
        if (ticks > 0u) {                            /* 延时参数是否为0 */
            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();
        }
    }

      当代码进入这个函数以后,首先进行两个判定,1.是否在中断中,2.任务调度是否属于允许状态,如果两个都不满足,才执行下面的代码。

          OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()这两个宏分别是禁止中断和重启中断,一般是成对出现,用来保证一些重要的代码在执行期间,不会被打断。

      前面的不重要,重点是if (ticks > 0u)里面的东西,他里面到底实现了些什么?

      

        if (ticks > 0u) {                            /* 延时参数是否为0 */
            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();
        }

            y  =  OSTCBCur->OSTCBY;这一句话表示什么意思?当然是把一个变量的值赋给另一个变量……废话!

      那么,OSTCBCur->OSTCBY这个变量到底是代表着什么?

      这个变量明显是属于一个指向结构体的指针,我们可以跟踪它去看看它的定义。

           

    OS_EXT  OS_TCB           *OSTCBCur;                        /* Pointer to currently running TCB         */

      从注释便可知道,这个结构体指针,指向当前正在运行的任务,继续跟踪……

    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;

    这个结构体有很多成员,是用来记录任务的基本信息的,这样的结构体有很多,可以简单的理解为有多少个任务就有多少个这样的结构体,它的原本的定义很大,我剔除了一些干扰信息,结果如上,这明显是一个双向链表,我们需要的OSTCBCur->OSTCBY到底是什么意思呢?从注释上看,应该是与任务优先级对应的就绪索引表有关,听不懂没关系,记在脑子里,下面解释。

    继续跟踪变量,发现它是在当前任务创建的时候赋值的:

    也就是起始任务中的start_task()→OSTaskCreate()→OS_TCBInit()的里面

            ptcb->OSTCBY             = (INT8U)(prio >> 3u);

    从字面意思上看,它的值应该是优先级的高3位,如果我们的优先级是12,那么二进制是00001100,高3位就是001,不过现在我们的APP0的优先级是0,那么高3位也就是000。

    在这行代码的旁边,同时还可以看到另一行代码:

            ptcb->OSTCBX             = (INT8U)(prio & 0x07u);

    这个变量里面保存的优先级的低三位,如果我们的优先级是12,那么二进制是00001100,低3位也就是0x100,不过现在我们的APP0的优先级是0,那么低3位也就是000。

    把优先级的高3位和低3位分开,这么做的目的是什么?保存这两个玩意儿有啥用?

    详细的东西容我以后慢慢讲,现在回到刚才的函数,终于明白了那个变量表示什么意思了,就是当前任务优先级的高3位,意义为何,虽然现在还不清楚,默默的记住有这么一个东西就行。

           

        if (ticks > 0u) {                            /* 延时参数是否为0 */
            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();
        }

    现在看第二句代码:OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX根据上面那个结构体,我们可以找到这个成员的定义以及赋值。

    它也是在那个OS_TCBInit函数里赋值的

            ptcb->OSTCBBitY          = (OS_PRIO)(1uL << ptcb->OSTCBY);
            ptcb->OSTCBBitX          = (OS_PRIO)(1uL << ptcb->OSTCBX);

    从算法上看,ptcb->OSTCBBitX是1向左移动本任务优先级的低3位。

    举个例子:假如我们当前的优先级是12,那么二进制是00001100,那么ptcb->OSTCBX变量应该是二进制100,那么ptcb->OSTCBBitX应该是1向左移动4个位置,结果是0x10。

    同理,ptcb->OSTCBBitY便是1向左移动优先级的高3位。

    举个例子:加入我们当前的优先级是12,那么二进制是00001100,那么ptcb->OSTCBtY变量应该是001,那么ptcb->OSTCBBittY应该是1向左移动1个位置,结果是0x02。

    ptcb->OSTCBY,ptcb->OSTCBX,ptcb->OSTCBBitY,ptcb->OSTCBBitX……这四个变量和优先级有关的变量都是在任务创建的时候就赋值好了,以后也不会改变,至于它们的用法,便是需要重点讲解的地方。

    现在结合那两句代码一起看:

            y            =  OSTCBCur->OSTCBY;
            OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;

    还是举个例子:假如我当前任务的优先级是12,那么Y = 001,OSTCBCur->OSTCBBitX = 0x10,结合起来看,第二句有&符号,也有~符号,只要是稍微有点C语言基础的同学,那么都很容易看出这两句话的意思,把数组OSRdyTbl[1]的第4位清空……

    那么……!@#*()&¥*()&¥……气的我想骂人,清空?到底是什么意思?

    优先级12的任务和数组OSRdyTbl[1]有什么关系,和OSRdyTbl[1]的第4位又有什么关系?

    待续……

  • 相关阅读:
    System.Data.RealonlyException:列Column1被设置为realonly
    学习java过程中
    在windows server 2008下安装vs2005.打开vs2005的时候老提示要“运行vs2005sp1 建议使用管理员权限”
    windows Server 2008下面运行vs2005的问题
    大飞机MIS系统360把我的Transformer.Service服务杀掉了
    开通博客
    C#中怎样让控件显示在其他控件的上面
    vs2010发布问题
    vs在IE8无法调试的解决方法
    将身份证号粘贴到WPS表格后变成了“科学计数法”的解决方案
  • 原文地址:https://www.cnblogs.com/han-bing/p/8882375.html
Copyright © 2011-2022 走看看