FreeRTOS任务管理
- 一、任务的创建和删除
- 1、函数xTaskCreate
- 2、函数xTaskCreateStatic
- 3、函数xTaskCreateRestricted
- 4、函数vTaskDelete
- 二、任务的挂起和恢复
- 1、函数vTaskSuspend
- 2、函数vTaskResume
- 3、函数vTaskResumeFromISR
- 4、函数vTaskSuspendAll
- 5、函数xTaskResumeAll
- 三、任务的状态机
- 1、优先级
- (1)相同优先级
- (2)不同优先级
- 四、任务调度
- 1、调度器
- (1)抢占式调度器
- 五、内核调度点
一、任务的创建和删除
1、函数xTaskCreate
\quad 此函数用来创建一个任务,任务需要RAM来保存与任务有关的状态信息(任务控制块),任务也需要一定的RAM 来作为任务堆栈。如果使用函数xTaskCreate()来创建任务的话那么这些所需的RAM就会自动的从FreeRTOS的堆中分配,因此必须提供内存管理文件,默认我们使用heap_4.c这个内存管理文件,而且宏configSUPPORT_DYNAMIC_ALLOCATION必须为1。如果使用函数xTaskCreateStatic()创建的话这些RAM就需要用户来提供了。新创建的任务默认就是就绪态的,如果当前没有比它更高优先级的任务运行那么此任务就会立即进入运行态开始运行,不管在任务调度器启动前还是启动后,都可以创建任务。此函数也是我们以后经常用到的,本教程所有例程均用此函数来创建任务,函数原型如下:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char *const pcName,const uint16_t usStackDepth,void *const pvParameters,UBaseType_t uxPriority,TaskHandle_t *const pxCreatedTask)
参数:
- pxTaskCode:任务函数。
- pcName:任务名字,一般用于追踪和调试,任务名字长度不能超过configMAX_TASK_NAME_LEN。
- usStackDepth:任务堆栈大小,注意实际申请到的堆栈是usStackDepth的4倍,例如数值128,即128字。其中空闲任务的任务堆栈大小为configMINIMAL_STACK_SIZE。当前栈类型为StackType_t,而StackType_t是uint32_t。
#define portSTACK_TYPE uint32_t
typedef portSTACK_TYPE StackType_t;
xTaskCreate
{pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
}
- pvParameters:传递给任务函数的参数。
- uxPriotiry:任务优先级,范围0~configMAX_PRIORITIES-1。若填写的数值超过configMAX_PRIORITIES-1,则静默地配置为configMAX_PRIORITIES-1。有些版本增加了configASSERT(
uxPriority < configMAX_PRIORITIES
)语句,且使能了configASSERT_DEFINED,则停留在该处。 - pxCreatedTask:任务句柄,任务创建成功以后会返回此任务的任务句柄,这个句柄其实就是任务的任务堆栈。此参数就用来保存这个任务句柄。其他API函数可能会使用到这个句柄。
返回值:
- pdPASS:任务创建成功。
- errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:任务创建失败,因为堆内存不足!
2、函数xTaskCreateStatic
\quad 此函数和xTaskCreate()的功能相同,也是用来创建任务的,但是使用此函数创建的任务所需的 RAM需要用用户来提供。如果要使用此函数的话需要将宏configSUPPORT_STATIC_ALLOCATION定义为1。函数原型如下:
TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode,const char * const pcName,const uint32_t ulStackDepth,void * const pvParameters,UBaseType_t uxPriority,StackType_t * const puxStackBuffer,StaticTask_t * const pxTaskBuffer );
参数:
- pxTaskCode:任务函数。
- pcName:任务名字,一般用于追踪和调试,任务名字长度不能超过configMAX_TASK_NAME_LEN。
- usStackDepth:任务堆栈大小,由于本函数是静态方法创建任务,所以任务堆栈由用户给出,一般是个数组,此参数就是这个数组的大小。
pvParameters:传递给任务函数的参数。 - uxPriotiry:任务优先级,范围 0 ~
configMAX_PRIORITIES-1,数值越大,优先级就越高,在FreeRTOSConfig.h可以配置configMAX_PRIORITIES。若填写的数值超过configMAX_PRIORITIES-1,则静默地配置为configMAX_PRIORITIES-1。有些版本增加了configASSERT(
uxPriority < configMAX_PRIORITIES
)语句,且使能了configASSERT_DEFINED,则停留在该处。 - puxStackBuffer:任务堆栈,一般为数组,数组类型要为StackType_t类型。 pxTaskBuffer:任务控制块。
返回值:
- NULL;任务创建失败,puxStackBuffer 或 pxTaskBuffer为 NULL的时候会导致这个错误的发生。
- 其他值:任务创建成功,返回任务的任务句柄。
\quad 注意,若configSUPPORT_STATIC_ALLOCATION有效了,需要为系统的空闲任务和定时器任务提供独立的内存空间,详细添加的代码如下:
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];/* 空闲任务所需内存 */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{*ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;*ppxIdleTaskStackBuffer = &xIdleStack[0];*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;/* place for user code */
} static StackType_t TimerTaskStack[configMINIMAL_STACK_SIZE];
static StaticTask_t TimerTaskTCB;/* 定时器任务所需内存 */
void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize )
{*ppxTimerTaskTCBBuffer=&TimerTaskTCB;*ppxTimerTaskStackBuffer=TimerTaskStack; *pulTimerTaskStackSize=configMINIMAL_STACK_SIZE;
}
函数栈大小确定:
\quad 函数的栈大小计算起来是比较麻烦的,那么有没有简单的办法来计算呢?有的,一般 IDE 开发环境都有这样的功能,比如 MDK 会生成一个 htm
文件,通过这个文件用户可以知道每个被调用函数的最大栈需求以及各个函数之间的调用关系。但是 MDK
无法确定通过函数指针实现函数调用时的栈需求。另外,发生中断或中断嵌套时的现场保护需要的栈空间也不会统计。
路径:\OBJ\demo.htm
demo:项目名称
app_task_rtc (Thumb, 128 bytes, Stack size 48 bytes, main.o(i.app_task_rtc))[Stack]
Max Depth = 200 + Unknown Stack Size
Call Chain = app_task_rtc ⇒ dgb_printf_safe ⇒ xQueueGenericSend ⇒ xTaskResumeAll ⇒ xTaskIncrementTick
3、函数xTaskCreateRestricted
\quad 此函数也是用来创建任务的,只不过此函数要求所使用的MCU有MPU(内存保护单元),用此函数创建的任务会受到MPU的保护。其他的功能和函数xTaskCreate()一样。
BaseType_t xTaskCreateRestricted( const TaskParameters_t * const pxTaskDefinition,TaskHandle_t *pxCreatedTask );
参数:
- pxTaskDefinition:指向一个结构体TaskParameters_t,这个结构体描述了任务的任务函数、堆栈大小、优先级等。此结构体在文件task.h中有定义。
- pxCreatedTask:任务句柄。
返回值:
- pdPASS:任务创建成功。
- 其他值:任务创建失败,因为堆栈内存不足!
4、函数vTaskDelete
\quad 删除一个用函数xTaskCreate()或者xTaskCreateStatic()创建的任务,被删除了的任务不再存在,也就是说再也不会进入运行态。任务被删除以后就不能再使用此任务的句柄!如果此任务是使用动态方法创建的,也就是使用函数 xTaskCreate()创建的,那么在此任务被删除以后此任务之前申请的堆栈和控制块内存会在空闲任务中被释放掉,因此当调用函数vTaskDelete()删除任务以后必须给空闲任务一定的运行时间。
\quad 只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉,比如某个任务中用户调用函数 pvPortMalloc()分配了500字节的内存,那么在此任务被删除以后用户也必须调用函数 vPortFree()将这500字节的内存释放掉,否则会导致内存泄露。此函数原型如下:
vTaskDelete( TaskHandle_t xTaskToDelete )
\quad 该函数常用在申请单次资源的应用场景,例如创建任务、创建内核对象、各种硬件资源的初始化等,完毕后则调用该函数完成本任务的销毁。
参数:
- xTaskToDelete,待删除的任务的句柄。传递 NULL 将导致调用任务被删除。
二、任务的挂起和恢复
\quad 有时候我们需要暂停某个任务的运行,过一段时间以后在重新运行。这个时候要是使用任务删除和重建的方法的话那么任务中变量保存的值肯定丢失了!FreeRTOS给我们提供了解决这种问题的方法,那就是任务挂起和恢复,当某个任务要停止运行一段时间的话就将这个任务挂起,当要重新运行这个任务的话就恢复这个任务的运行。
1、函数vTaskSuspend
\quad 此函数用于将某个任务设置为挂起态,进入挂起态的任务永远都不会进入运行态。退出挂起态的唯一方法就是调用任务恢复函数vTaskResume或xTaskResumeFromISR,函数原型如下:
void vTaskSuspend( TaskHandle_t xTaskToSuspend)
参数:
- xTaskToSuspend:要挂起的任务的任务句柄,创建任务的时候会为每个任务分配一个任务句柄。
\quad 如果使用函数xTaskCreate()创建任务的话那么函数的参数pxCreatedTask就是此任务的任务句柄,如果使用函数xTaskCreateStatic()创建任务的话那么函数的返回值就是此任务的任务句柄。也可以通过函数xTaskGetHandle()来根据任务名字来获取某个任务的任务句柄。
注意:如果参数为NULL的话表示挂起任务自己。
返回值:
\quad 无。
2、函数vTaskResume
\quad 将一个任务从挂起态恢复到就绪态,只有通过函数vTasksSuspend()设置为挂起态的任务才可以使用vTaskRexume()恢复!函数原型如下:
void vTaskResume( TaskHandle_t xTaskToResume)
参数:
- xTaskToResume:要恢复的任务的任务句柄。
返回值:
\quad 无。
3、函数vTaskResumeFromISR
\quad 此函数是vTaskResume()的中断版本,用于在中断服务函数中恢复一个任务。函数原型如下:
BaseType_t xTaskResumeFromISR(TaskHandle t xTaskToResume)
参数:
- xTaskToResume:要恢复的任务的任务句柄。
返回值:
- pdTRUE:恢复运行的任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数以后必须进行一次上下文切换。
- pdFALSE:恢复运行的任务的任务优先级低于当前正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数的以后不需要进行上下文切换。
上下文:通俗的理解,上下文,也就是执行任务所需要的相关信息。这个任务可以是一段代码,一个线程,一个进程,一个函数。当这个“任务”,相关信息需要保存下来,就可以使用Context来记录了。
4、函数vTaskSuspendAll
\quad 此函数是挂起调度器可以停止上下文切换而不用关中断。如果某个中断在调度器挂起过程中要求进行上下文切换,则这个请求也会被挂起,直到调度器被唤醒后才会得到执行。
void vTaskSuspendAll( void )
注意: vTaskSuspendAll被调用后,禁止调用引起任务切换相关函数(例如vTaskDelay、等待信号量/互斥锁/消息队列等)。
示例代码:
static void app_task1(void* pvParameters)
{for(;;){/* 挂起调度器 */vTaskSuspendAll();printf("app_task1 is running ...\r\n");vTaskDelay(1000);/* 恢复调度器 */xTaskResumeAll(); }
}
\quad 当app_task1执行vTaskSuspendAll函数后调用vTaskDelay函数,则程序停留在configASSERT( uxSchedulerSuspended == 0 );位置处不再运行,如下图。
5、函数xTaskResumeAll
\quad 此函数是恢复调度器。在调度器挂起过程中,上下文切换请求也会被挂起,直到调度器被唤醒后才会得到执行。
BaseType_t xTaskResumeAll( void )
参数:
\quad 无
返回值:
\quad 如果一个挂起的上下文切换请求在xTaskResumeAll()返回前得到执行,则函数返回 pdTRUE。在其它情况下,xTaskResumeAll()返回 pdFALSE。
三、任务的状态机
任务状态任务可以存在于以下状态中:
- 运行
\quad 当任务实际执行时,它被称为处于运行状态。 任务当前正在使用处理器。 如果运行 RTOS 的处理器只有一个内核, 那么在任何给定时间内都只能有一个任务处于运行状态。
- 就绪
\quad 准备就绪任务指那些能够执行(它们不处于阻塞或挂起状态), 但目前没有执行的任务, 因为同等或更高优先级的不同任务已经处于运行状态。
- 阻塞
\quad 如果任务当前正在等待时间或外部事件,则该任务被认为处于阻塞状态。 例如,如果一个任务调用vTaskDelay(),它将被阻塞(被置于阻塞状态), 直到延迟结束-一个时间事件。 任务也可以通过阻塞来等待队列、信号量、事件组、通知或信号量 事件。 处于阻塞状态的任务通常有一个"超时"期, 超时后任务将被超时,并被解除阻塞, 即使该任务所等待的事件没有发生。
\quad “阻塞”状态下的任务不使用任何处理时间,不能 被选择进入运行状态。
- 挂起
\quad 与“阻塞”状态下的任务一样, “挂起”状态下的任务不能 被选择进入运行状态,但处于挂起状态的任务 没有超时。 相反,任务只有在分别通过 vTaskSuspend() 和 xTaskResume() API 调用明确命令时 才会进入或退出挂起状态。
Suspended:挂起态
Running:运行态
Blocked:阻塞态/等待态
Ready:就绪态
上图官方没有列出任务被中断的情况,倘若被中断的任务如果从中断返回,则返回被中断的任务。当到达下一个时钟节拍,CPU资源给到就绪任务中优先级最高的任务。
1、优先级
\quad 每个任务均被分配了从 0 到 ( configMAX_PRIORITIES - 1 ) 的优先级,其中的 configMAX_PRIORITIES 在 FreeRTOSConfig.h 中定义。
\quad 如果正在使用的端口实现了端口优化的任务选择机制,该机制使用 “前导零计数”类指令(用于单个指令中的任务选择)且 在 FreeRTOSConfig.h 中将 configUSE_PORT_OPTIMISED_TASK_SELECTION 设置为 1,则 configMAX_PRIORITIES 不得大于 32。 在所有其他情况下,configMAX_PRIORITIES 可以采取任何 合理范围内的值,但出于 RAM 使用效率的原因,应保持在 实际需要的最小值。
\quad 低优先级数字表示低优先级任务。空闲任务的优先级为零 (tskIDLE_PRIORITY)。
\quad FreeRTOS 调度器可确保在就绪或运行状态的任务将始终在同样处于就绪状态的较低优先级任务之前获得处理器 (CPU) 时间 。 换句话说,被置于运行状态的任务始终是可运行的最高优先级任务。
\quad 任意数量的任务可共用相同的优先级。 如果configUSE_TIME_SLICING 未经定义,或者如果 configUSE_TIME_SLICING 设置为 1,则相同优先级的就绪状态任务 将使用时间切片轮询调度方案共享可用的处理时间。
(1)相同优先级
1、代码
int main(void)
{/* 创建app_task1任务 */xTaskCreate((TaskFunction_t )app_task1, /* 任务入口函数 */(const char* )"app_task1", /* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )5, /* 任务的优先级 */(TaskHandle_t* )&app_task1_handle); /* 任务控制块指针 */ /* 创建app_task2任务 */ xTaskCreate((TaskFunction_t )app_task2, /* 任务入口函数 */(const char* )"app_task2", /* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )5, /* 任务的优先级 */(TaskHandle_t* )&app_task2_handle); /* 任务控制块指针 */ /* 开启任务调度 */vTaskStartScheduler(); while(1);}
static void app_task1(void* pvParameters)
{for(;;){printf("app_task1 is running ...\r\n");vTaskDelay(20);}
} static void app_task2(void* pvParameters)
{for(;;){printf("app_task2 is running ...\r\n");vTaskDelay(30);}
}
2、结果
(2)不同优先级
1、代码
int main(void)
{/* 创建app_task1任务 */xTaskCreate((TaskFunction_t )app_task1, /* 任务入口函数 */(const char* )"app_task1", /* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )6, /* 任务的优先级 */(TaskHandle_t* )&app_task1_handle); /* 任务控制块指针 */ /* 创建app_task2任务 */ xTaskCreate((TaskFunction_t )app_task2, /* 任务入口函数 */(const char* )"app_task2", /* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )5, /* 任务的优先级 */(TaskHandle_t* )&app_task2_handle); /* 任务控制块指针 */ /* 开启任务调度 */vTaskStartScheduler(); while(1);}
static void app_task1(void* pvParameters)
{for(;;){printf("app_task1 is running ...\r\n");vTaskDelay(20);}
} static void app_task2(void* pvParameters)
{for(;;){printf("app_task2 is running ...\r\n");vTaskDelay(30);}
}
将每个任务的睡眠延时降低,即提高每个任务的活跃度,容易观察到抢占过程,抢占示意图如下。
2、演示
四、任务调度
\quad FreeRTOS 操作系统支持三种调度方式:抢占式调度,时间片调度和合作式调度。实际应用主要是抢占式调度和时间片调度,合作式调度用到的很少。
- 合作式调度
\quad 亦称为FreeRTOS的协程,实际上是线程并发出来的,每个线程并发出来的协程共用一个栈空间。合作式调度主要用在资源有限的设备上面,现在已经很少使用了。出于这个原因,后面的 FreeRTOS 版本中不会将合作式调度删除掉,但也不会再进行升级了。
- 抢占式调度
\quad 每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的 API 函数,比如 vTaskDelay。
- 时间片调度
\quad 每个任务都有相同的优先级,任务会运行固定的时间片个数或者遇到阻塞式的 API 函数,比如vTaskDelay,才会执行同优先级任务之间的任务切换。如果用户在 FreeRTOS.h 中禁止使用时间片调度,那么每个任务必须配置不同的优先级。
路径:FreeRTOS.h
#ifndef configUSE_TIME_SLICING#define configUSE_TIME_SLICING 1
#endif
1、调度器
\quad 简单的说,调度器就是使用相关的调度算法来决定当前需要执行的任务。所有的调度器有一个共同的特性:
- 调度器可以区分就绪态任务和挂起任务(由于延迟,信号量等待,邮箱等待,事件组等待等原因而使得任务被挂起)。
- 调度器可以选择就绪态中的一个任务,然后激活它(通过执行这个任务)。当前正在执行的任务是运行态的任务。
- 不同调度器之间最大的区别就是如何分配就绪态任务间的完成时间。
\quad 调度器是内核中负责决定在任何特定时间应执行哪些任务的部分。内核可以在任务生命周期内多次挂起并且稍后恢复一个任务。
\quad 调度策略是调度器用来决定在任何时间点执行哪个任务的算法。 非实时多用户系统的策略极有可能使每个任务具有"公平"比例的处理时间。 之后会描述实时/嵌入式系统中使用的策略。
\quad 任务除了被迫被内核挂起之外,还可以选择将自己挂起。 如果它想要延迟(睡眠)一段固定时间,或者等待(阻塞)资源 变为可用(例如串行端口)或将要发生的事件(例如按键),它将执行此操作。 阻塞的或正在睡眠的任务无法执行,并且不会分配任何处理时间。
请参阅上图中的数字:
- 在 (1) 处,任务 1 正在执行。
- 在 (2) 处,内核挂起(换出)任务 1…
- …在 (3) 处,恢复任务 2。
- 在 (4) 处,任务 2 正在执行,为独占访问,会锁定一个处理器外围设备。
- 在 (5) 处,内核挂起任务 2…
- …在 (6) 处,恢复任务 3。
- 任务 3 尝试访问相同的处理器外围设备,发现其被锁定,任务 3 无法继续,因此在 (7) 处挂起。
- 在 (8) 处,内核恢复任务 1。
- 等等。
- 接下来任务 2 执行时 (9),完成对处理器外围设备的访问,因此解锁。
- 再下来任务 3 执行时 (10),发现现在可以访问处理器外围设备,于是开始执行,直到被内核挂起为止。
嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法。任务切换的实现在不同的嵌入式实时操作系统中区别不大,基本相同的硬件内核架构,任务切换也是相似的。调度算法就有些区别了。下面我们主要了解一下抢占式调度器和时间片调度器。
(1)抢占式调度器
\quad 在实际的应用中,不同的任务需要不同的响应时间。例如,我们在一个应用中需要使用电机,键盘和LCD 显示。电机比键盘和 LCD 需要更快速的响应,如果我们使用合作式调度器或者时间片调度,那么电机将无法得到及时的响应,这时抢占式调度是必须的。
\quad 如果使用了抢占式调度,最高优先级的任务一旦就绪,总能得到 CPU 的控制权。比如,当一个运行着的任务被其它高优先级的任务抢占,当前任务的 CPU 使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了 CPU 的控制权并运行。又比如,如果中断服务程序使一个高优先级的任务进入就绪态,中断完成时,被中断的低优先级任务被挂起,优先级高的那个任务开始运行。
\quad 使用抢占式调度器,使得最高优先级的任务什么时候可以得到 CPU 的控制权并运行是可知的,同时使得任务级响应时间得以最优化。
\quad 总的来说,学习抢占式调度要掌握的最关键一点是:每个任务都被分配了不同的优先级,抢占式调度器会获得就绪列表中优先级最高的任务,并运行这个任务。
(2) 时间片调度器
\quad 在小型的嵌入式 RTOS 中,最常用的的时间片调度算法就是 Round-Robin 调度算法。这种调度算法可以用于抢占式或者合作式的多任务中。另外,时间片调度适合用于不要求任务实时响应的情况。实现 Round-Robin 调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务,并为每个任务分配一个时间片(也就是需要运行的时间长度,时间片用完了就进行任务切换)。
\quad 在RR调度策略下,一个任务会一直执行,直到:
- 自愿放弃控制权
- 被更高优先级的任务抢占
- 时间片用完
如下图所示,A在用完自己的时间片后,将CPU执行权让给任务B,于是A离开Read队列,而B进入Read队列。
一旦线程的时间片用完,该线程就会被下一个READ的具有同等优先级的任务给抢占:
轮转调度算法在实际使用中,注意实现如下:
优点:
\quad 缩短任务的等待时间提高系统的吞吐量,甚至在某个任务异常独占CPU,还可以执行其他任务。
缺点:
\quad 对长作业非常不利,可能长时间得不到执行,未能依据作业的紧迫程度来划分执行的优先级,难以准确估计作业(进程)的执行时间,从而影响调度性能。
\quad 时间片长度该设为多少需要好好调试,因为有时候时间片太短,任务还没来得及做事情,就被调度了;时间片太长,又会造成让其它任务等太久,所以实际编程中需要好好调试。倒不如直接不使用。
\quad 还有的是FreeRTOS的时间片不能额外设置,例如系统定时器中断频率为1000Hz,则时间为1ms。μc/OS可以额外设置,例如系统定时器中断频率为1000Hz,定时周期为1ms,可通过OSSchedRoundRobinCfg函数设置时间片大小。
int main(void)
{xReturn=xTaskCreate(app_task1,"app_task1",512,NULL,7,&app_task1_handle);xReturn=xTaskCreate(app_task2,"app_task2",512,NULL,7,&app_task2_handle);vTaskStartScheduler();while(1);
}void app_task1(void *pvParameters)
{uint32_t i;while(1){printf("#");i=0x1000000;while(i--);}
}void app_task2(void *pvParameters)
{uint32_t i; while(1){printf("-");i=0x1000000;while(i--); }
}
任务运行图参考如下:
五、内核调度点
- 释放信号量、释放互斥锁或者发送消息,也可通过配置相应的参数不发生任务调度。
- 使用延时函数vTaskDelay()或者vTaskDelayUntil()。
- 任务等待的事情还没发生(等待信号量,消息队列等)。
- 任务取消等待。
- 创建任务。
- 删除任务。
- 删除一个内核对象。
- 任务改变自身的优先级或者其他任务的优先级。
- 任务通过调用vTaskSuspend()将自身挂起。
- 任务解挂某个挂起的任务。
- 退出所有的嵌套中断。
- 通过xTaskResumeAll()给调度器解锁。
- 用户调用taskYIELD()