ucos-ii嵌入式操作系统任务调度(一)----任务调度的过程及实现原理

先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题,二维码如下:
在这里插入图片描述

一 概念

在单片机裸机程序中,我们以函数为最小单位来划分代码功能的,所有函数之间都存在一个先后调用的关系(不是你调用我,就是我调用你,或者你我都被他调用);但是在嵌入式操作系统中,我们可以以任务为最小单位来看待一个程序代码(当然函数仍然是每个任务的最小单位),各个任务之间没有调用关系,它们可以说是各自为营。

可以把嵌入式系统程序看成各个单片机裸机程序的集合,跑操作系统的本质原因就是因为系统程序需要实现的功能很多,我们需要合理划分每个功能,保证相互之间没有关系,从而有一个框架清晰,逻辑清晰,后期维护和查找问题更便捷的系统。

那么操作系统是如何合理调度各个任务,保证每个任务都能合理正确的运行呢?这是所有操作系统最为重要的一个目的,现代操作系统绝大部分都是多任务系统,我们先看一看ucos-ii是如何实现的。

二 ucos-ii调度策略

何为任务调度?就是终止当前正在执行的任务,转而去执行别的任务过程。

在探讨任务调度策略时,我们先假设读者对ucos-ii中任务的基本概念有了一定理解,这里也不在做介绍。任务有一个基本特性,任务优先级(存储在任务控制块结构体中,在初始化创建任务的时候会被赋值)是任务调度的重要参考,在ucos中,优先级值越低,优先级越高,越优先被系统调度。

触发任务调度的基本方式有两种,一种是任务级调度,另外一种是中断级调度。

1. 任务级调度

所谓任务级调度,就是任务本身主动或被动触发了任务调度,比如说等待信号量过程中,当前信号量还没有产生,cpu不可能一直在这里死等(当然特殊情况,也会设计成在这里死等,比如像linux中的自旋锁机制),这样会造成别的任务不能得到快速响应,还怎么能称作实时操作系统呢?
任务调度的步骤,总结来说,可以分为两步,第一步是找到当前优先级最高的并且处于就绪状态的任务;第二步就是任务切换,从当前任务切换到优先级较高的任务去。通过调用API函数OS_Sched来实现,代码如下:

void  OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3u                           /* Allocate storage for CPU status register     */
    OS_CPU_SR  cpu_sr = 0u;
#endif
    OS_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 > 0u
                OSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task      */
#endif
                OSCtxSwCtr++;                          /* Increment context switch counter             */
#if OS_TASK_CREATE_EXT_EN > 0u
#if defined(OS_TLS_TBL_SIZE) && (OS_TLS_TBL_SIZE > 0u)
                OS_TLS_TaskSw();
#endif
#endif
                OS_TASK_SW();                          /* Perform a context switch                     */
            }
        }
    }
    OS_EXIT_CRITICAL();
}

我们根据宏开关的定义,去掉未使能的代码,简写OS_Sched如下:

void  OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3u                           /* Allocate storage for CPU status register     */
    OS_CPU_SR  cpu_sr = 0u;
#endif

    OS_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     */
                OSCtxSwCtr++;                          /* Increment context switch counter             */
                OS_TASK_SW();                          /* Perform a context switch                     */
            }
        }
    }
    OS_EXIT_CRITICAL();
}
  1. 查找优先级较高的任务
    我们先关注上面代码片段中第10行位置的OS_SchedNew,此函数实现查找优先级较高任务的目的,其代码如下:
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]]);
#else                                            /* We support up to 256 tasks                         */
    INT8U     y;
    OS_PRIO  *ptbl;


    if ((OSRdyGrp & 0xFFu) != 0u) {
        y = OSUnMapTbl[OSRdyGrp & 0xFFu];
    } else {
        y = OSUnMapTbl[(OS_PRIO)(OSRdyGrp >> 8u) & 0xFFu] + 8u;
    }
    ptbl = &OSRdyTbl[y];
    if ((*ptbl & 0xFFu) != 0u) {
        OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(*ptbl & 0xFFu)]);
    } else {
        OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u);
    }
#endif
}

同样,根据宏开关,简写OS_SchedNew如下:

static  void  OS_SchedNew (void)
{
    INT8U   y;

    y             = OSUnMapTbl[OSRdyGrp];
    OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
}

我们发现这里只有2句赋值语句,OSPrioHighRdy是ucos-ii中用来标记当前系统任务中优先级最高的任务的全局变量,那么ucos是如何找到优先级最高的任务的呢?我们重点关注一下全局数组OSUnMapTbl,定义如下:

INT8U  const  OSUnMapTbl[256] = {
    0u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x00 to 0x0F                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x10 to 0x1F                   */
    5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x20 to 0x2F                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x30 to 0x3F                   */
    6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x40 to 0x4F                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x50 to 0x5F                   */
    5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x60 to 0x6F                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x70 to 0x7F                   */
    7u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x80 to 0x8F                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x90 to 0x9F                   */
    5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xA0 to 0xAF                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xB0 to 0xBF                   */
    6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xC0 to 0xCF                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xD0 to 0xDF                   */
    5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xE0 to 0xEF                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u  /* 0xF0 to 0xFF                   */
};

初看这个定义,绝大部分读者可能跟我一样,晕头转向,云里雾里,什么鬼(WTF!!)?而且我可能比各位读者还要更笨一些,我可是前前后后死磕了好几遍,才看懂这边的定义,看这个数组之前,我们先来看一下ucos是如何标记任务优先级的。

ucos中,将任务用数组OsRdyTbl[8],人为划分成了8组,OsRdyTbl[0]代表任务组0,OsRdyTbl[1]代表任务组1…OsRdyTbl[7]代表任务组7;每个数组中的每一位代表一个优先级任务,比方说对于OsRdyTbl[0]的第0位代表任务优先级为0的任务,OsRdyTbl[0]的第1位代表任务优先级为1的任务,以此类推;OsRdyTbl[1]的第0位代表任务优先级为8的任务(18+0),OsRdyTbl[1]的第1位代表任务优先级为9的任务(18+1)…OsRdyTbl[7]的第0位代表任务优先级为56的任务(78+0),OsRdyTbl[7]的第1位代表任务优先级为57的任务(78+1);同时ucos用OSRdyGrp表示哪一组优先级,OSRdyGrp的第0位表示第0组优先级状态(如果该位为1,表示第0组有存在任务就绪)…第7位表示第7组优先级状态。

因此我们只要知道OSRdyGrp的值,以及OsRdyTbl[]数组的值,就可以知道当前任务就绪的状态了,比方说,如果由我来设置最初版本的ucos系统(当然现在知道这样设计是存在问题的,虽然从逻辑上来看,也能实现功能,但是从实时性上来看,是不合理的)

u8 grp, tbl;

/* 先找出属于哪一组 */
for (i = 0; i < 8; i++)
{
    temp = OSRdyGrp >> i;
    if (temp & 0x1)
        break;
}
if (OSRdyGrp)  /* 这个判断条件可以不加上,因为系统中必定会存在一个空闲任务 */
    grp = i;

/*再找出当前那一组上哪一个优先级
 *因为OsRdyTbl[0]的优先级最高,其次是OsRdyTbl[1],最后是OsRdyTbl[7]
 *所以我们采用从0开始索引查找
 */
for (j = 0; j < 8; j++)
{
    for (i = 0; i < 8; i++)
    {
        temp = OsRdyTbl[j] >> i;
        if (temp & 0x1)
            goto find_out; 
    }
}
find_out:
    tbl = i;

OSPrioHighRdy = grp << 3 + tbl;

根据我们自己所实现的查找最高优先级的代码,反过来理解ucos-ii系统中的巧妙实现方法,它是如何通过一个数组,经过查表就能找到最高优先级的呢?
我们知道OSRdyGrp代表了哪一组任务优先级,第0位代表的任务组肯定是最高优先级的,因此我们先判断OSRdyGrp最低位出现为1的那一组,那么这一组中对应的任务肯定是优先级最高的;同理,OsRdyTbl[]数组中的每一位也代表一个任务优先级,最低位出现为1的肯定是优先级最高的(为什么是这样的,因为ucos系统OSRdyGrp和OsRdyTbl[]就是这样来定义的),找到最开始出现为1的,就可以根据公式:

OSRdyGrp最低位出现为1的位数 << 3 + OsRdyTbl[OSRdyGrp最低位出现为1的位数]最低位出现为1的位数

这里两个最低位出现为1的位数不是很好理解,请读者仔细分析这段话。
因此,ucos定义的数组就是表示最低位出现为1的数,比如说

优先级的值        二进制                 低位最开始出现1的位数
0                   0000 0000      ---->       0
1                   0000 0001      ---->       1
2                   0000 0010      ---->       2
3                   0000 0011      ---->       1
4                   0000 0100      ---->       2
5                   0000 0101      ---->       1
......       
128                 1000 0000      ---->       7
......       
255                 1111 1111      ---->       0

讲到这里,各位读者应该明白了查找较高优先级任务的原理
2. 任务切换
在找到较高任务的优先级时,就需要实现任务的切换了,上面的过程中,我们只是找到了当前处于就绪状态的最高优先级任务的优先级值,如何实现任务切换呢,请看代码(仍然是OS_Sched函数中的部分片段,说明的是:在不同版本的ucos-ii上,代码的细节会有所差别,不过原理目的都是一样的)

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 > 0u
                OSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task      */
#endif
                OSCtxSwCtr++;                          /* Increment context switch counter             */

#if OS_TASK_CREATE_EXT_EN > 0u
#if defined(OS_TLS_TBL_SIZE) && (OS_TLS_TBL_SIZE > 0u)
                OS_TLS_TaskSw();
#endif
#endif

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

我们一句一句分析:
A. 找到当前优先级对应的任务控制块,根据优先级的值,索引数组

OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]

B. 判断最高优先级是否为当前任务,如果就是当前任务的话,不需要切换;否则需要切换;
C. 如果要切换,记录当前任务被切换的次数,以及任务总共被切换的次数

OSTCBHighRdy->OSTCBCtxSwCtr++;
OSCtxSwCtr++; 

D. 实现任务切换,调用宏OS_TASK_SW()实现,实际上是调用的汇编函数OSCtxSw,有关这个汇编函数的实现方式,我们在另外的一篇文章中会重点分析,这里我们只需要知道调用这个函数后就会实现任务切换即可

OS_TASK_SW()

2. 中断级调度

中断级调度,是调用C函数OSIntExit实现的,如下:

void  OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3u                               /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr = 0u;
#endif



    if (OSRunning == OS_TRUE) {
        OS_ENTER_CRITICAL();
        if (OSIntNesting > 0u) {                           /* Prevent OSIntNesting from wrapping       */
            OSIntNesting--;
        }
        if (OSIntNesting == 0u) {                          /* Reschedule only if all ISRs complete ... */
            if (OSLockNesting == 0u) {                     /* ... and not locked.                      */
                OS_SchedNew();
                OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
                if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy */
#if OS_TASK_PROFILE_EN > 0u
                    OSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task  */
#endif
                    OSCtxSwCtr++;                          /* Keep track of the number of ctx switches */

#if OS_TASK_CREATE_EXT_EN > 0u
#if defined(OS_TLS_TBL_SIZE) && (OS_TLS_TBL_SIZE > 0u)
                    OS_TLS_TaskSw();
#endif
#endif

                    OSIntCtxSw();                          /* Perform interrupt level ctx switch       */
                }
            }
        }
        OS_EXIT_CRITICAL();
    }
}

根据宏开关,简写如下:

void  OSIntExit (void)
{
    OS_CPU_SR  cpu_sr = 0u;
    
    if (OSRunning == OS_TRUE) {
        OS_ENTER_CRITICAL();
        if (OSIntNesting > 0u) {                           /* Prevent OSIntNesting from wrapping       */
            OSIntNesting--;
        }
        if (OSIntNesting == 0u) {                          /* Reschedule only if all ISRs complete ... */
            if (OSLockNesting == 0u) {                     /* ... and not locked.                      */
                OS_SchedNew();
                OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
                if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy */
                    OSCtxSwCtr++;                          /* Keep track of the number of ctx switches */

                    OSIntCtxSw();                          /* Perform interrupt level ctx switch       */
                }
            }
        }
        OS_EXIT_CRITICAL();
    }
}

我们发现,这个函数的实现基本上和OS_Sched的函数实现类似,也是先找到当前优先级最高的任务,然后启动任务切换。唯一不同的就是,调用了不同的函数实现了任务切换,这里就是调用了汇编函数OSIntCtxSw()来实现的,函数如下:

OSIntCtxSw
    LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR

我们发现,这个函数也是使能了一次PendSV异常,跟汇编函数OSCtxSw的实现一样,都是软件触发了一次了PendSV异常,在PendSV异常处理函数中,实现上下文的切换,参照文章《ucos-ii嵌入式操作系统任务调度(二)----任务切换瞬间cpu做了什么》

  • 4
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: STM32 uCOS-II 多任务实验是一种基于STM32微控制器和uCOS-II操作系统的多任务应用程序实验。该实验旨在通过使用uCOS-II操作系统,实现多个任务同时运行,提高系统的效率和可靠性。 在该实验中,我们可以使用STM32开发板和uCOS-II操作系统,创建多个任务并分配不同的优先级。每个任务可以执行不同的操作,例如读取传感器数据、控制外设、处理数据等。通过使用uCOS-II操作系统的任务调度机制,可以确保每个任务按照其优先级顺序执行,从而实现多任务并发执行的效果。 在实验过程中,我们需要学习uCOS-II操作系统的基本概念和使用方法,包括任务创建、任务调度、信号量、消息队列等。同时,还需要了解STM32微控制器的硬件资源和编程方法,以便实现任务的具体功能。 总之,STM32 uCOS-II 多任务实验是一种非常有意义的实验,可以帮助我们深入了解嵌入式系统的多任务编程和操作系统原理,提高我们的实际应用能力。 ### 回答2: stm32是一款基于arm cortex-m内核的微控制器,其具有高性能和低功耗的优点,可广泛应用于嵌入式系统、智能家居、机器人等领域。同时,ucos-ii是一款实时操作系统,能够提供多任务管理、资源管理、时间管理等功能,可帮助开发人员快速构建嵌入式系统。 stm32 ucos-ii多任务实验是指在stm32平台上实现ucos-ii操作系统的多任务管理功能,以实现任务之间的并发执行。该实验通常包括以下几个步骤: 1.准备工作:在开发板上搭建环境,包括stm32开发环境、ucos-ii环境以及相关工具的配置。 2.任务定义:定义多个任务,每个任务都是一个独立的程序模块,应包括任务的初始化、任务功能、任务清理等步骤。 3.任务调度ucos-ii操作系统会根据任务的优先级、状态等因素进行任务调度,确保任务可以及时得到执行。 4.任务通信:在多任务实验中,由于各个任务之间具有相互制约关系,因此需要进行任务通信,以确保任务能够协同工作。 5.调试测试:在实现多任务后,需要进行调试测试,确保各个任务能够正常运行,系统具有稳定性和健壮性。 综上所述,stm32 ucos-ii多任务实验可以帮助开发人员深入了解stm32的基本功能和ucos-ii实时操作系统的特点,提高系统的并发处理能力,为嵌入式系统的开发和应用提供有力支持。 ### 回答3: STM32 uCOS-II多任务实验是一种在STM32芯片上使用uC/OS-II操作系统进行多任务开发的实验。该实验旨在帮助学生和工程师熟悉uC/OS-II操作系统,学习多任务的开发和调试技术。 在这个实验中,我们需要使用一些基本的硬件和软件,如STM32开发板、uC/OS-II操作系统、Keil或者IAR嵌入式开发工具、以及一些基本的电子元器件。我们需要编写一些任务代码,在uC/OS-II操作系统上进行多任务的协作开发。我们需要学习如何在uC/OS-II操作系统上创建任务、删除任务、挂起任务、恢复任务、设置任务优先级等等。 在进行STM32 uCOS-II多任务实验时,我们需要注意一些事项。首先,我们需要根据实际需要分析任务的资源需求和优先级,为每个任务分配适当的资源和优先级。其次,我们需要了解uC/OS-II操作系统的机制和原理,例如任务调度、消息传递、事件通知等等,以便正确使用和调试操作系统的各种功能。 总之,STM32 uCOS-II多任务实验是一个非常有价值和有意义的实验,它能够帮助我们提高多任务开发和调试的能力。通过这个实验,我们能够掌握uC/OS-II操作系统的使用方法和技能,提高我们的嵌入式开发能力和技术水平。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值