简介

在以前使用8051芯片的时候我们都会在main里面直接开一个while()循环,循环中调用相应的函数完成所需的处理。有时候我们也需要中断中完成一些处理。相对于多任务系统而言,这个就是单任务系统,也称作前后台系统。中断服务函数作为前台程序,大循环while(1)作为后台程序。
04.任务简介 - 图1
前后台系统的实时性差,前后台系统各个任务(应用程序)都是排队等着轮流执行,不管你这个程序现在有多紧急。但是前后台系统简单啊,资源消耗也少啊!在稍微大一点的嵌入式应用中前后台系统就明显力不从心了,此时就需要多任务系统出马了。 多任务系统会把一个大问题(应用)分而治之,把大问题划分成很多个小问题,逐步的把小问题解决掉,大问题也就随之解决了,这些小问题可以单独的作为一个小任务来处理。这些小任务是并发处理的,注意,并不是说同一时刻一起执行很多个任务,而是由于每个任务执行的时间很短,导致看起来像是同一时刻执行了很多个任务一样。 不同的系统其任务调度器的实现方法也不同,比如 FreeRTOS 是一个抢占式的实时多任务系统,那么其任务调度器也是抢占式的。
04.任务简介 - 图2
高优先级的任务可以打断低优先级任务的运行而取得 CPU 的使用权,这样就保证了那些紧急任务的运行。这样我们就可以为那些对实时性要求高的任务设置一个很高的优先级,比如自动驾驶中的障碍物检测任务等。高优先级的任务执行完成以后重新把 CPU 的使用权归还给低优先级的任务,这个就是抢占式多任务系统的基本原理。

FreeRTOS 任务与协程

协程是为那些资源很少的MCU准备的,其开销很小,但是FreeRTOS官方已经不打算再更新协程了,所以本教程只讲解任务。

任务

在使用 RTOS 的时候一个实时应用可以作为一个独立的任务。每个任务都有自己的运行环境,不依赖于系统中其他的任务或者 RTOS 调度器。任何一个时间点只能有一个任务运行,具体运行哪个任务是由 RTOS 调度器来决定的,RTOS 调度器因此就会重复的开启、关闭每个任务。任务不需要了解 RTOS 调度器的具体行为,RTOS 调度器的职责是确保当一个任务开始执行的时候其上下文环境(寄存器值,堆栈内容等)和任务上一次退出的时候相同。为了做到这一点,每个任务都必须有个堆栈,当任务切换的时候将上下文环境保存在堆栈中,这样当任务再次执行的时候就可以从堆栈中取出上下文环境,任务恢复运行。 任务特性:

  • 简单。
  • 没有使用限制。 3、支持抢占
  • 支持优先级
  • 每个任务都拥有堆栈导致了 RAM 使用量增大。
  • 如果使用抢占的话的必须仔细的考虑重入的问题。

    协程

    协程是为那些资源很少的 MCU 而做的,但是随着 MCU 的飞速发展,性能越来越强大,现在协程几乎很少用到了! 在概念上协程和任务是相似的,但是有如下根本上的不同:

  • 堆栈使用:所有的协程使用同一个堆栈(如果是任务的话每个任务都有自己的堆栈),这样就比使用任务消耗更少的 RAM。

  • 调度器和优先级:协程使用合作式的调度器,但是可以在使用抢占式的调度器中使用协程。
  • 宏实现:协程是通过宏定义来实现的。
  • 使用限制:为了降低对 RAM 的消耗做了很多的限制。

    任务状态

  • 运行态:当一个任务正在运行时,那么就说这个任务处于运行态,处于运行态的任务就是当前正在使用处理器的任务。如果使用的是单核处理器的话那么不管在任何时刻永远都只有一个任务处于运行态。

  • 就绪态:处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起),可以运行的任务,但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行!
  • 阻塞态:如果一个任务当前正在等待某个外部事件的话就说它处于阻塞态,比如说如果某个任务调用了函数 vTaskDelay()的话就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临!
  • 挂起态:像阻塞态一样,任务进入挂起态以后也不能被调度器调用进入运行态,但是进入挂起态的任务没有超时时间。任务进入和退出挂起态通过调用函数 vTaskSuspend()和 xTaskResume()。

04.任务简介 - 图3

任务优先级

每个任务都可以分配一个从0~(configMAX_PRIORITIES-1)的 优 先 级 ,configMAX_PRIORITIES在文件 FreeRTOSConfig.h中有定义。

优先级数字越低表示任务的优先级越低,0 的优先级最低,configMAX_PRIORITIES-1 的优先级最高。

空闲任务的优先级最低,为0。 FreeRTOS调度器确保处于就绪态或运行态的高优先级的任务获取处理器使用权,换句话说就是处于就绪态的最高优先级的任务才会运行。当宏configUSE_TIME_SLICING定义为1的时候多个任务可以共用一个优先级,数量不限。默认情况下宏configUSE_TIME_SLICING在文件FreeRTOS.h 中已经定义为1。此时处于就绪态的优先级相同的任务就会使用时间片轮转调度器获取运行时间。

任务控制块

FreeRTOS 的每个任务都有一些属性需要存储,FreeRTOS 把这些属性集合到一起用一个结构体来表示,这个结构体叫做任务控制块:TCB_t

  1. typedef struct tskTaskControlBlock
  2. {
  3. volatile StackType_t *pxTopOfStack; //任务堆栈栈顶
  4. #if ( portUSING_MPU_WRAPPERS == 1 )
  5. xMPU_SETTINGSxMPUSettings; //MPU 相关设置
  6. #endif
  7. ListItem_t xStateListItem; //状态列表项
  8. ListItem_t xEventListItem; //事件列表项
  9. UBaseType_t uxPriority; //任务优先级
  10. StackType_t *pxStack; //任务堆栈起始地址
  11. char pcTaskName[ configMAX_TASK_NAME_LEN ];//任务名字
  12. #if ( portSTACK_GROWTH > 0 )
  13. StackType_t *pxEndOfStack; //任务堆栈栈底
  14. #endif
  15. #if ( portCRITICAL_NESTING_IN_TCB == 1 )
  16. UBaseType_t uxCriticalNesting; //临界区嵌套深度
  17. #endif
  18. #if ( configUSE_TRACE_FACILITY == 1 ) //trace 或到 debug 的时候用到
  19. UBaseType_t uxTCBNumber;
  20. UBaseType_t uxTaskNumber;
  21. #endif
  22. #if ( configUSE_MUTEXES == 1 )
  23. UBaseType_t uxBasePriority; //任务基础优先级,优先级反转的时候用到
  24. UBaseType_t uxMutexesHeld; //任务获取到的互斥信号量个数
  25. #endif
  26. #if ( configUSE_APPLICATION_TASK_TAG == 1 )
  27. TaskHookFunction_t pxTaskTag;
  28. #endif
  29. #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) //与本地存储有关
  30. void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
  31. #endif
  32. #if( configGENERATE_RUN_TIME_STATS == 1 )
  33. uint32_t ulRunTimeCounter; //用来记录任务运行总时间
  34. #endif
  35. #if ( configUSE_NEWLIB_REENTRANT == 1 )
  36. struct _reent xNewLib_reent; //定义一个 newlib 结构体变量
  37. #endif
  38. #if( configUSE_TASK_NOTIFICATIONS == 1 )//任务通知相关变量
  39. volatile uint32_t ulNotifiedValue; //任务通知值
  40. volatile uint8_t ucNotifyState; //任务通知状态
  41. #endif
  42. #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
  43. //用来标记任务是动态创建的还是静态创建的,如果是静态创建的此变量就为 pdTURE,
  44. //如果是动态创建的就为 pdFALSE
  45. uint8_t ucStaticallyAllocated;
  46. #endif
  47. #if( INCLUDE_xTaskAbortDelay == 1 )
  48. uint8_t ucDelayAborted;
  49. #endif
  50. } tskTCB;
  • pxTopOfStack:任务堆栈栈顶
  • xMPU_SETTINGSxMPUSettings:MPU相关的设置
  • xStateListItem:状态列表项
  • xEventListItem:事件列表
  • uxPriority:优先级
  • pxStack:任务堆栈起始地址
  • pcTaskName:任务名称
  • pxEndOfStack:任务堆栈栈底
  • uxCriticalNesting:临界区嵌套深度
  • uxBasePriority:基础优先级
  • uxMutexesHeld:任务获得互斥信号量个数
  • pvThreadLocalStoragePointers:本地存储指针
  • ulRunTimeCounter:任务运行总时间
  • ulNotifiedValue:任务通知值
  • ucNotifyState:任务通知状态
  • ucStaticallyAllocated:动态状态还是静态创建

    任务堆栈

    FreeRTOS 之所以能正确的恢复一个任务的运行就是因为有任务堆栈在保驾护航,任务调度器在进行任务切换的时候会将当前任务的现场(CPU 寄存器值等)保存在此任务的任务堆栈中,等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场,恢复现场以后任务就会接着从上次中断的地方开始运行。 任务堆栈的数据类型为 StackType_tStackType_t本质上是uint32_t,在portmacro.h中有定义,如下:
    1. #define portSTACK_TYPE uint32_t
    2. #define portBASE_TYPE long
    3. typedef portSTACK_TYPE StackType_t;
    4. typedef long BaseType_t;
    5. typedef unsigned long UBaseType_t;
    可以看出StackType_t类型的变量为4个字节,那么任务的实际堆栈大小就应该是我们所定义的4倍