????????单任务系统的编程方式,即裸机的编程方式,这种编程方式的框架一般都是在 main()函数
中使用一个大循环,在循环中顺序地调用相应的函数以处理相应的事务,这个大循环的部分可
以视为应用程序的后台,而应用程序的前台,则是各种中断的中断服务函数。因此单任务系统
也叫做前后台系统。
????????从宏观上来看,多任务系统的多个任务是可以“同时”运行的,因此紧急的事务就可以无需等待 CPU 处理完其他事务,在被处理。对于单核的 CPU 而言,CPU 在同一时刻只能够处理一个任务,但是多任务系统的任务调度器会根据相关的任务调度算法,将 CPU 的使用权分配给任务,在任务获取 CPU 使用权之后的极短时间(宏观角度)后,任务调度器又会将 CPU 的使用权分配给其他任务,如此往复,在宏观的角度看来,就像是多个任务同时运行了一样。
1. ?运行态
????????如果一个任务得到 CPU 的使用权,即任务被实际执行时,那么这个任务处于运行态。如果
运行 RTOS 的 MCU 只有一个处理器核心,那么在任务时刻,都只能有一个任务处理运行态。
2. ?就绪态
????????如果一个任务已经能够被执行、,但当前还未被执行(具有相同优先级或更高优先级的任务正持有 CPU 使用权),那么这个任务就处于就绪态。
3. ?阻塞态
????????如果一个任务因延时一段时间或等待外部事件发生,那么这个任务就处理阻塞态。
4. ?挂起态
????????任务一般通过函数 vTaskSuspend()和函数 vTaskResums()进入和退出挂起态。处于挂起态的任务也无法被运行。
????????抢占式调度主要针对优先级不同的任务,每个任务都有一个优先级,优先级高的任务可
以抢占优先级低的任务,只有当优先级高的任务发生阻塞或者被挂起,低优先级的任务才可以
运行。
????????时间片调度主要针对优先级相同的任务,当多个任务的优先级相同时,任务调度器会在每
一次系统时钟节拍到的时候切换任务,也就是说 CPU 轮流运行优先级相同的任务,每个任务运
行的时间就是一个系统时钟节拍。
????????任务句柄(Task Handle)是在 FreeRTOS 中用于标识和引用任务的数据类型。每个创建的任务都会分配一个唯一的任务句柄,通过该句柄可以对任务进行操作和管理。任务句柄是一个指向任务控制块(Task Control Block,TCB)的指针。任务控制块是 FreeRTOS 中用于描述和管理任务的数据结构,包含了任务的状态、优先级、堆栈等信息。使用任务句柄,可以通过 FreeRTOS 提供的 API 函数对任务进行操作,例如挂起(suspend)、恢复(resume)、删除(delete)任务,或者查询任务的状态等。另外,任务句柄还可以用于任务通信和同步的机制,例如向任务发送信号量或消息。在创建任务时,通过调用 FreeRTOS 提供的任务创建函数(例如xTaskCreate())可以获取到相应任务的句柄。
? ? ? ? FreeRTOS 中的每一个已创建任务都包含一个任务控制块TCB,任务控制块是一个结构体变量,FreeRTOS 用任务控制块结构体存储任务的属性。
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 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t * pxEndOfStack;/* 指向任务栈栈底的指针 */
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting;/* 记录任务独自的临界区嵌套次数 */
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
/* 由系统分配(每创建一个任务,值增加一),分配任务的值都不同,用于调试 */
UBaseType_t uxTCBNumber;
/* 由函数 vTaskSetTaskNumber()设置,用于调试 */
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 )
/* 记录任务处于运行态的时间 */
configRUN_TIME_COUNTER_TYPE ulRunTimeCounter;
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
/* 用于 Newlib */
struct _reent xNewLib_reent;
#endif
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
/* 任务通知值 */
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
/* 任务通知状态 */
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
#if ( INCLUDE_xTaskAbortDelay == 1 )
/* 任务被中断延时标志 */
uint8_t ucDelayAborted;
#endif
#if ( configUSE_POSIX_ERRNO == 1 )
/* 用于 POSIX */
int iTaskErrno;
#endif
} tskTCB;
typedef struct tskTaskControlBlock * TaskHandle_t;
?
????????静态创建任务使用的栈和任务控制块是预先定义好的全局变量。优点是稳定,内存足够分配,缺点是耗内存!任务删除以后这段内存需要自己手动释放。
????????动态创建的优点是在任务建立的时候使用动态分配内存按需分配内存,随用随取,任务建立函数会返回一个指针,用于指向任务控制块,因此要预先为任务栈定义一个任务控制块指针。动态的缺点可能会因为内存不足导致创建任务失败,也就是多个任务malloc但是没有free掉,导致可能其他任务创建任务分配失败。