RTOS基本知识

目录程序员

 

RTOS的定义?算法

RTOS中的任务网络

RTOS中任务的切换数据结构

任务的状态函数

任务的调度(Scheduler)oop

可能的调度方式ui

任务优先级spa

任务实现操作系统

任务控制块debug

任务堆栈

调度程序如何知道一个任务已经阻塞或解除阻塞?

若是全部的任务都阻塞了将会发生什么?

若是两个就绪的任务有相同的优先级该怎么办?

 

若是一个任务正在运行,此时有另外一个具备更高优先级的任务解除了阻塞,正在运行的任务是否应该当即中止运行并进入就绪状态?

 

任务和数据


  • RTOS的定义?

    实时操做系统(RTOS)是指当外界事件或数据产生时,可以接受并以足够快的速度予以处理,其处理的结果又能在规定的时间以内来控制生产过程或对处理系统作出快速响应,调度一切可利用的资源完成实时任务,并控制全部实时任务协调一致运行的操做系统。提供及时响应和高可靠性是其主要特色。

  • RTOS中的任务

    在一个RTOS中所写的软件的基本构造块就叫任务。任务写起来很是简单:在大多数的RTOS中的一个任务只是一个子程序。
    任务(Task)是RTOS中最重要的操做对象,每一个任务在RTOS的调用下由CPU分时执行。激活的或当前任务是CPU正在执行的任务,休眠的任务是在存储器中保留其执行的上下文背景、一旦切换为当前任务便可从上次执行的末尾继续执行的任务。任务的调度目前主要有时间分片式(TimeSlicing)、轮流查询式(Round-Robin)和优先抢占式(Preemptive)三种,不一样的RTOS可能支持其中的一种或几种,其中优先抢占式对实时性的支持最好。

  • RTOS中任务的切换

多任务的概念:
    RTOS管理下的系统CPU和系统资源的时间是同时分配给不一样任务的,这样看起来就象许多任务在同时执行,但实际上每一个时刻只有一个任务在执行,也就是当前任务。
任务的切换有两种缘由。
    当一个任务正常地结束操做时,它就把CPU控制权交给RTOS,RTOS则检查任务队列中的全部任务,判断下面那个任务的优先级最高,须要先执行。
    另外一种状况是在一个任务执行时,一个优先级更高的任务发生了中断,这时RTOS就将当前任务的上下文保存起来,切换到中断任务。
    RTOS常常性地整理任务队列,删除结束的任务,增长新的要执行任务,并将其按照优先级从大到小的顺序排列起来,这样能够合理地在各个任务之间分配系统资源。

  • 任务的状态

    任务有以下状态:
    Runnig(运行) - 在这种状态下任务获得了CPU时间来执行它的指令。在同一个时间点,只有一个任务可以处于这种状态。咱们用RTOS的一个函数来将这种状态下的任务变回Suspended状态,这样任务就中止执行而且deactivated.咱们用另外一个RTOS的函数来这种状态下的任务变到Waiting状态,这时任务一样中止了执行,可是保持活动状态。
    注:在多处理器系统里,在指定的时间里可能有超过一个的任务在运行.可是单处理器平台上,任什么时候候只能有一个任务在单独运行。
    Ready(就绪) - 任务在这种状态下是活动的而且等待进入Running状态。咱们用RTOS的调度器(scheduler)来决定在何时将处于Ready的任务过渡到Running状态,咱们也用调度器来决定是否将任务变回到Ready状态,这取决于RTOS固有的算法以及其余任务的状态。其余的任务正在运行中,但只要处理器处于空闲状态,这个任务就能运行。大多数的任务可能会处于这个状态。
   Blocked( 阻塞) - 表示任务尚未得到运行所须要的资源,即便此时微处理器空闲也不能运行。任务处于这个状态是由于它们在等待某些外部事件的发生。例如,处理网络数据的任务在没有数据的时候什么事情也不会作。响应用户按键事件的任务直到用户按键以后才开始工做。处于这个状态的任务有可能也存在许多。
    Suspended(挂起) - 这是系统启动后的默认状态。在这种状态下,任务为inactive。经过RTOS的激活,任务状态能够过渡为Ready。
    Waiting(等待) – 在这种状态下的任务在event(RTOS的另一个service)进行等待。若是这个event被设置了的话,任务就变回Ready状态。
    Sleep(睡眠) - TBD(To Be Defined)
    Delay(延迟) - TBD(To Be Defined)

  • 任务的调度(Scheduler)

    为了有效地协同不一样任务在竞争处理器时的关系,咱们用scheduler来决定处理器在何时执行什么任务。
    Scheduler在处理任务之间关系时,有两种策略:preemptive(和cooperative。在一个preemptive系统中,一个任务在它的执行时间里被赋予了优先权,这意味着scheduler可以根据系统所处的阶段将处理器分配给其余的任务。对于须要长执行时间的任务来讲,赋予优先权是一个很是好的方法;重要性更高的任务将比重要性低的任务更有效地利用处理器。
    在一个cooperative系统中,每个任务在它的执行时间里独占CPU。除非任务自动放弃对处理器的控制,不然任务间的切换不会出现。
    有两种基本的调度方式:静态调度(根据时间来进行调度),和动态调度(根据事件来调度)。在静态调度方式下,提早定义了任务的执行顺序。在动态调度的方式下,一个任务在运行其间是否被执行由系统状态来决定。Scheduler(调度器)根据当前的任务状态进行调整。在动态调度方式下,只有当存在实际须要时(出现一个外部事件),处理器才执行任务,处理器的能力将获得更有效的利用(这与静态调度方式无关)。

 

  • 可能的调度方式

    有许多不一样的任务调度方法。三个一般用到的是: priority control(优先权控制法),time slice(时间片法),FIFO(first in first out,先来先出法?)。多种方法能够组合到一个操做系统中。


    在一个priority-controlled scheduler(优先权控制调度器)中,OS根据每个任务的重要程度赋予它一个优先级。开发者能够利用优先级来控制一个任务执行的速度和频率。这意味着拥有更高优先权的任务将被更快的执行完毕。可能有这样一种实施方式,全部任务位于不一样优先级的队列中(在同一个队列中的任务拥有一样的优先权)。
    当全部拥有更高优先权的队例为空(被调度执行完毕),特定队例中位于前面的任务将被调度执行。在同一个优先权队列内部,任务一样须要调度。由于优先权相等,须要某种其余的机制来决定不一样任务的前后调度顺序。好比说FIFO(先来先出)或者其余技巧。具备表明意义的是,优先权控制法通常与动态调度结合在一块儿,执行顺序非静态;和preemptive调度结合以容许拥有更高优先权的任务优先执行。

    Time slice(时间片法,also known as ROUND ROBIN)是另一种调度方法。一个小的时间单元,咱们称之为时间片或时间量,被定义用来执行任务。一个最简单的状况就是全部的时间片拥有一样的时间长度,不过他们也能够拥有不一样的长度。全部将被执行的任务列成一个环形的队列,新激活的任务被添加到这个环形队列的尾部。
    CPU调度器浏览整个队列,为每个任务分配时间片。对于任务而言,时间片末端点是一个期限,任务将被终止或暂时中止,在队列结束该任务的下一个时间片继续执行直至结束。若是任务在时间片结束前完成,那么它将会主动释放掉处理器。
    在这两种状况中,CPU调度器将处理器分配给队列中的下一个任务。当同时出现多个拥有一样优先级的任务竞争使用CPU时,常常要用到时间片法。时间片法是最简单的,也是应用得最广的CPU调度算法之一,可是这种调度方法并无以一种最有效的方法来利用CPU能力。由于当一个任务在时间片末端点以前结束的话,在下一个任务执行以前,CPU老是处理空闲状态。

    FIFO(firsr come first server,先来先服务)排序法是最基本的队列调度原则。在FIFO排序法中,全部的任务被平等的置于一个队列中。他们按照在队列中的顺序被执行。在复杂程度较低或对顺序性要求较高的系统中,这是一种很是简单而合适的调度机制。

  • 任务优先级

每一个任务均可以分配一个从0~(configMAX_PRIORITIES-1) 的优先级,configMAX_PRIORITIES 在文件FreeRTOSConfig.h 中有定义,前面咱们讲解FreeRTOS 系统配置的时候已经讲过了。若是所使用的硬件平台支持相似计算前导零这样的指令(能够经过该指令选择下一个要运行的任务, Cortex-M 处理器是支持该指令的) , 而且宏configUSE_PORT_OPTIMISED_TASK_SELECTION 也设置为了1 , 那么宏configMAX_PRIORITIES 不能超过32 ! 也就是优先级不能超过32 级。其余状况下宏configMAX_PRIORITIES 能够为任意值,可是考虑到RAM 的消耗,宏configMAX_PRIORITIES最好设置为一个知足应用的最小值。优先级数字越低表示任务的优先级越低,0 的优先级最低,configMAX_PRIORITIES-1 的优先级最高。空闲任务的优先级最低,为0。FreeRTOS 调度器确保处于就绪态或运行态的高优先级的任务获取处理器使用权,换句话说就是处于就绪态的最高优先级的任务才会运行。当宏configUSE_TIME_SLICING 定义为1 的时候多个任务能够共用一个优先级,数量不限。默认状况下宏configUSE_TIME_SLICING 在文件FreeRTOS.h 中已经定义为1。此时处于就绪态的优先级相同的任务就会使用时间片轮转调度器获取运行时间。

 

  • 任务实现

    在使用FreeRTOS 的过程当中,咱们要使用函数xTaskCreate()或xTaskCreateStatic()来建立任务,这两个函数的第一个参数pxTaskCode,就是这个任务的任务函数。什么是任务函数?任务函数就是完成本任务工做的函数。我这个任务要干吗?要作什么?要完成什么样的功能都是在这个任务函数中实现的。 好比我要作个任务,这个任务要点个流水灯,那么这个流水灯的程序就是任务函数中实现的。FreeRTOS 官方给出的任务函数模板以下:

void vATaskFunction(void *pvParameters) (1)
    {
    for( ; ; ) (2)
    {
    --任务应用程序-- (3)
    vTaskDelay(); (4)
    }
    /* 不能从任务函数中返回或者退出, 从任务函数中返回或退出的话就会调用
    configASSERT(),前提是你定义了configASSERT()。若是必定要从任务函数中退出的话那必定
    要调用函数vTaskDelete(NULL)来删除此任务。*/
    vTaskDelete(NULL); (5)
    }

(1)、任务函数本质也是函数,因此确定有任务名什么的,不过这里咱们要注意:任务函数的返回类型必定要为void 类型,也就是无返回值,并且任务的参数也是void 指针类型的!任务函数名能够根据实际状况定义。
(2)、任务的具体执行过程是一个大循环,for(; ; )就表明一个循环,做用和while(1)同样,笔者习惯用while(1)。
(3)、循环里面就是真正的任务代码了,此任务具体要干的活就在这里实现!
(4)、FreeRTOS 的延时函数,此处不必定要用延时函数,其余只要能让FreeRTOS 发生任务切换的API 函数均可以,好比请求信号量、队列等,甚至直接调用任务调度器。只不过最经常使用的就是FreeRTOS 的延时函数。

(5)、任务函数通常不容许跳出循环,若是必定要跳出循环的话在跳出循环之后必定要调用函数vTaskDelete(NULL)删除此任务!FreeRTOS 的任务函数和UCOS 的任务函数模式基本相同的,不止FreeRTOS,其余RTOS的任务函数基本也是这种方式的。

 

  • 任务控制块

    FreeRTOS 的每一个任务都有一些属性须要存储,FreeRTOS 把这些属性集合到一块儿用一个结构体来表示,这个结构体叫作任务控制块:TCB_t,在使用函数xTaskCreate()建立任务的时候就会自动的给每一个任务分配一个任务控制块。在老版本的FreeRTOS 中任务控制块叫作tskTCB,新版本重命名为TCB_t,可是本质上仍是tskTCB,本教程后面提到任务控制块的话均用TCB_t表示,此结构体在文件tasks.c 中有定义,以下:

typedef struct tskTaskControlBlock
    {
    volatile StackType_t *pxTopOfStack; //任务堆栈栈顶
    #if ( portUSING_MPU_WRAPPERS == 1 )
    xMPU_SETTINGS xMPUSettings; //MPU 相关设置
    #endif
    ListItem_t xStateListItem; //状态列表项
    ListItem_t xEventListItem; //事件列表项
    UBaseType_t uxPriority; //任务优先级
    StackType_t *pxStack; //任务堆栈起始地址
    char pcTaskName[ configMAX_TASK_NAME_LEN ];//任务名字
    #if ( portSTACK_GROWTH > 0 )
    StackType_t *pxEndOfStack; //任务堆栈栈底
    #endif
    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
    UBaseType_t uxCriticalNesting; //临界区嵌套深度
    #endif
    #if ( configUSE_TRACE_FACILITY == 1 ) //trace 或到debug 的时候用到
    UBaseType_t uxTCBNumber;
    UBaseType_t uxTaskNumber;
    #endif
    #if ( configUSE_MUTEXES == 1 )
    UBaseType_t uxBasePriority; //任务基础优先级,优先级反转的时候用到
    UBaseType_t uxMutexesHeld; //任务获取到的互斥信号量个数
    #endif
    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
    TaskHookFunction_t pxTaskTag;
    #endif
    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) //与本地存储有关
    void
    *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    #endif
    #if( configGENERATE_RUN_TIME_STATS == 1 )
    uint32_t ulRunTimeCounter; //用来记录任务运行总时间
    #endif
    #if ( configUSE_NEWLIB_REENTRANT == 1 )
    struct _reent xNewLib_reent; //定义一个newlib 结构体变量
    #endif
    #if( configUSE_TASK_NOTIFICATIONS == 1 ) //任务通知相关变量
    volatile uint32_t ulNotifiedValue; //任务通知值
    volatile uint8_t ucNotifyState; //任务通知状态
    #endif
    #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
    //用来标记任务是动态建立的仍是静态建立的,若是是静态建立的此变量就为pdTURE,
    //若是是动态建立的就为pdFALSE
    uint8_t ucStaticallyAllocated;
    #endif
    #if( INCLUDE_xTaskAbortDelay == 1 )
    uint8_t ucDelayAborted;
    #endif
    } tskTCB;
    //新版本的FreeRTOS 任务控制块重命名为TCB_t,可是本质上仍是tskTCB,主要是为了兼容
    //旧版本的应用。
    typedef tskTCB TCB_t;

能够看出来FreeRTOS 的任务控制块中的成员变量相比UCOSIII 要少不少,并且大多数与裁剪有关,当不使用某些功能的时候与其相关的变量就不参与编译,任务控制块大小就会进一步的减少。

  • 任务堆栈

      FreeRTOS 之因此能正确的恢复一个任务的运行就是由于有任务堆栈在保驾护航,任务调度器在进行任务切换的时候会将当前任务的现场(CPU 寄存器值等)保存在此任务的任务堆栈中,等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场,恢复现场之后任务就会接着从上次中断的地方开始运行。建立任务的时候须要给任务指定堆栈,若是使用的函数xTaskCreate()建立任务(动态方法)的话那么任务堆栈就会由函数xTaskCreate()自动建立,后面分析xTaskCreate()的时候会讲解。若是使用函数xTaskCreateStatic()建立任务(静态方法)的话就须要程序员自行定义任务堆栈,而后堆栈首地址做为函数的参数puxStackBuffer 传递给函数,以下:

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
    const char * const pcName,
    const uint32_t ulStackDepth,
    void * const pvParameters,
    UBaseType_t uxPriority,
    StackType_t * const puxStackBuffer, (1)
    StaticTask_t * const pxTaskBuffer )

(1)、任务堆栈,须要用户定义,而后将堆栈首地址传递给这个参数。堆栈大小:咱们无论是使用函数xTaskCreate()仍是xTaskCreateStatic()建立任务都须要指定任务堆栈大小。任务堆栈的数据类型为StackType_t,StackType_t 本质上是uint32_t,在portmacro.h 中有定义,以下:

#define portSTACK_TYPE uint32_t
    #define portBASE_TYPE long
    typedef portSTACK_TYPE StackType_t;
    typedef long BaseType_t;
    typedef unsigned long UBaseType_t;

能够看出StackType_t 类型的变量为4 个字节,那么任务的实际堆栈大小就应该是咱们所定义的4 倍

 

  • 调度程序如何知道一个任务已经阻塞或解除阻塞?

    RTOS提供了一个函数集合,经过这个函数集合任务能告诉调度程序它正在等待什么事件发生,而且当事件发生时能发信号通知调度程序。

 

  • 若是全部的任务都阻塞了将会发生什么?

    若是全部的任务都阻塞了,调度程序循环调用RTOS内部的短循环以等待事件发生。若是没有事件发生,则是你的错误了,你必须保证某个事件早晚会发生,这个事件经过中断程序调用RTOS的函数来解除一个任务的阻塞。不然,系统软件将不会工做的很好

 

  • 若是两个就绪的任务有相同的优先级该怎么办?

    答案多种多样,取决于使用的实时操做系统,至少有一个系统经过将两个具备一样优先级的任务标志为非法来解决问题。其余的一些系统在两个这样的任务之间平等分配时间片。还有的系统将运行其中的一个直到它阻塞,而后再运行另外一个。最后一种状况下,两个任务的哪个运行也取决于特定的RTOS。

 

  • 若是一个任务正在运行,此时有另外一个具备更高优先级的任务解除了阻塞,正在运行的任务是否应该当即中止运行并进入就绪状态?

    抢占式实时操做系统只要一个更高优先级的任务解除了阻塞,就将中止低优先级的任务的运行。

    非抢占式实时操做系统只会在低优先级任务阻塞厚才会占用其处理器。

 

  • 任务和数据

    每一个任务都有本身的私有数据,包括寄存器值、程序计数器和栈。可是,因此其余的数据,如全局数据、静态数据、初始化数据、非初始化数据等都由系统中的全部任务共享。

    RTOS有私有的数据结构,这些数据结构对其余的任务是不可用的。