1.多任务系统
1.FreeRTOS 是一个抢占式的实时多任务系统, 那么其任务调度器也是抢占式的,运行过程如下图所示:
1.1任务(Task)的特性
1、简单。
2、没有使用限制。
3、支持抢占
4、支持优先级
5、每个任务都拥有堆栈导致了 RAM 使用量增大。
6、如果使用抢占的话的必须仔细的考虑重入的问题。
1.2任务状态
● 运行态
当一个任务正在运行时, 那么就说这个任务处于运行态, 处于运行态的任务就是当前正在使用处理器的任务。 如果使用的是单核处理器的话那么不管在任何时刻永远都只有一个任务处于运行态。
● 就绪态
处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起), 可以运行的任务,但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行!
● 阻塞态
如果一个任务当前正在等待某个外部事件的话就说它处于阻塞态, 比如说如果某个任务调用了函数vTaskDelay()的话就会进入阻塞态, 直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态即使所等待的事件还没有来临!
● 挂起态
像阻塞态一样,任务进入挂起态以后也不能被调度器调用进入运行态, 但是进入挂起态的任务没有超时时间。任务进入和退出挂起态通过调用函数 vTaskSuspend()和 xTaskResume()。
1.3任务状态迁移
图2(1): 创建任务→就绪态(Ready):任务创建完成后进入就绪态,表明任务已准备就绪,随时可以运行,只等待调度器进行调度。
图2(2): 就绪态→运行态(Running):发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态。
图2(3): 运行态→就绪态:有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪列表中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪列表中,等待最高优先级的任务运行完毕继续运行原来的任务(此处可以看做是 CPU 使用权被更高优先级的任务抢占了)。
图2(4): 运行态→阻塞态( Blocked):正在运行的任务发生阻塞(挂起、延时、读信号量等待)时,该任务会从就绪列表中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务。
图2(5): 阻塞态→就绪态:阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态;如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。
图2(6) (7) (8): 就绪态、阻塞态、运行态→挂起态(Suspended):任务可以通过调用 vTaskSuspend() API 函数都可以将处于任何状态的任务挂起,被挂起的任务得不到CPU 的使用权,也不会参与调度,除非它从挂起态中解除。
图2(9): 挂起态→就绪态: 把 一 个 挂 起 状态 的 任 务 恢复的 唯 一 途 径 就 是调 用 vTaskResume() 或 vTaskResumeFromISR() API 函数,如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。
1.4任务控制块
在裸机系统中,程序的主体是 CPU 按照顺序执行的。而在多任务系统中, 任务的执行是由系统调度的。系统为了顺利的调度任务,为每个任务都额外定义了一个任务控制块,这个任务控制块就相当于任务的身份证,里面存有任务的所有信息,比如任务的栈指针,任务名称, 任务的形参等。有了这个任务控制块之后,以后系统对任务的全部操作都可以通过这个任务控制块来实现。 定义一个任务控制块需要一个新的数据类型, 该数据类型在task.c 这 C 头文件中声明。
task.c源码
248 /*
249 * Task control block. A task control block (TCB) is allocated for each task,
250 * and stores task state information, including a pointer to the task's context
251 * (the task's run time environment, including register values)
252 */
253 typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
254 {
255 volatile StackType_t * pxTopOfStack; /*任务堆栈栈顶. */
256
257 #if ( portUSING_MPU_WRAPPERS == 1 )
258 xMPU_SETTINGS xMPUSettings; /*MPU 相关设置 */
259 #endif
260
261 ListItem_t xStateListItem; /*状态列表项 */
262 ListItem_t xEventListItem; /*事件列表项 */
263 UBaseType_t uxPriority; /*任务优先级 */
264 StackType_t * pxStack; /*任务堆栈起始地址 */
265 char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*任务名字 */
266
267 #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
268 StackType_t * pxEndOfStack; /*任务堆栈栈底. */
269 #endif
270
271 #if ( portCRITICAL_NESTING_IN_TCB == 1 )
272 UBaseType_t uxCriticalNesting; /*临界区嵌套深度*/
273 #endif
274
275 #if ( configUSE_TRACE_FACILITY == 1 ) //trace或到debug的时候用到
276 UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
277 UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
278 #endif
279
280 #if ( configUSE_MUTEXES == 1 )
281 UBaseType_t uxBasePriority; /*任务基础优先级,优先级反转的时候用到 */
282 UBaseType_t uxMutexesHeld; /*任务获取到的互斥信号量的个数*/
283 #endif
284
285 #if ( configUSE_APPLICATION_TASK_TAG == 1 )
286 TaskHookFunction_t pxTaskTag;
287 #endif
288
289 #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) /*与本地存储有关*/
290 void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
291 #endif
292
293 #if ( configGENERATE_RUN_TIME_STATS == 1 )
294 uint32_t ulRunTimeCounter; /*用来记录任务运行的总时间. */
295 #endif
296
297 #if ( configUSE_NEWLIB_REENTRANT == 1 )
298
299 /* Allocate a Newlib reent structure that is specific to this task.
300 * Note Newlib support has been included by popular demand, but is not
301 * used by the FreeRTOS maintainers themselves. FreeRTOS is not
301 * used by the FreeRTOS maintainers themselves. FreeRTOS is not
302 * responsible for resulting newlib operation. User must be familiar with
303 * newlib and must provide system-wide implementations of the necessary
304 * stubs. Be warned that (at the time of writing) the current newlib design
305 * implements a system-wide malloc() that must be provided with locks.
306 *
307 * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
308 * for additional information. */
309 struct _reent xNewLib_reent; /*定义一个newlib结构体变量*/
310 #endif
311
312 #if ( configUSE_TASK_NOTIFICATIONS == 1 ) /*任务通知的相关变量*/
313 volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];/*任务通知值*/
314 volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];/*任务通知状态*/
315 #endif
316
317 /* See the comments in FreeRTOS.h with the definition of
318 * tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
319 #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
/*用来标记任务是动态创建的还是静态创建的,如果是静态创建的此变量就为 pdTURE,
*如果是动态创建的就为 pdFALSE */
320 uint8_t ucStaticallyAllocated;
321 #endif
322
323 #if ( INCLUDE_xTaskAbortDelay == 1 )
324 uint8_t ucDelayAborted;
325 #endif
326
327 #if ( configUSE_POSIX_ERRNO == 1 )
328 int iTaskErrno;
329 #endif
330 } tskTCB;
331
332 /* 新版本的 FreeRTOS 任务控制块重命名为 TCB_t,但是本质上还是 tskTCB,主要是为了兼容旧版本的应用。*/
334 typedef tskTCB TCB_t;
1.5任务函数
在使用 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 的任务函数和 uC/OS 的任务函数模式基本相同的,不止 FreeRTOS,其他 RTOS的任务函数基本也是这种方式的。
2.空闲任务
2.1空闲任务简介
当 FreeRTOS 的调度器启动以后就会自动的创建一个空闲任务,这样就可以确保至少有一任务可以运行。但是这个空闲任务使用最低优先级,如果应用中有其他高优先级任务处于就绪态的话这个空闲任务就不会跟高优先级的任务抢占 CPU 资源。空闲任务还有另外一个重要的职责,如果某个任务要调用函数 vTaskDelete()删除自身,那么这个任务的任务控制块 TCB 和任务堆栈等这些由 FreeRTOS 系统自动分配的内存需要在空闲任务中释放掉,如果删除的是别的任务那么相应的内存就会被直接释放掉,不需要在空闲任务中释放。因此,一定要给空闲任务执行的机会!除此以外空闲任务就没有什么特别重要的功能了,所以可以根据实际情况减少空闲任务使用 CPU 的时间(比如,当 CPU 运行空闲任务的时候使处理器进入低功耗模式)。用户可以创建与空闲任务优先级相同的应用任务,当宏 configIDLE_SHOULD_YIELD 为 1的话应用任务就可以使用空闲任务的时间片,也就是说空闲任务会让出时间片给同优先级的应用任务。这种机制要求FreeRTOS 使用抢占式内核。
2.2 空闲任务的创建
当调用函数 vTaskStartScheduler()启动任务调度器的时候此函数就会自动创建空闲任务
2.3 空闲任务函数
空闲任务的任务函数为 prvIdleTask(),但是实际上是找不到这个函数的,因为它是通过宏
定义来实现的,在文件 portmacro.h 中有如下宏定义:
#define portTASK_FUNCTION( vFunction, pvParameters ) void vFunction( void *pvParameters )
其中 portTASK_FUNCTION()在文件 tasks.c 中有定义,它就是空闲任务的任务函数,源码如下:
3396 /*
3397 * -----------------------------------------------------------
3398 * The Idle task.
3399 * ----------------------------------------------------------
3400 *
3401 * The portTASK_FUNCTION() macro is used to allow port/compiler specific
3402 * language extensions. The equivalent prototype for this function is:
3403 *
3404 * void prvIdleTask( void *pvParameters );
3405 *
3406 */
3407 static portTASK_FUNCTION( prvIdleTask, pvParameters )
3408 {
3409 /* Stop warnings. */
3410 ( void ) pvParameters; //防止报错
3411
3412 /** THIS IS THE RTOS IDLE TASK - WHICH IS CREATED AUTOMATICALLY WHEN THE
3413 * SCHEDULER IS STARTED. **/
3414
3415 /* In case a task that has a secure context deletes itself, in which case
3416 * the idle task is responsible for deleting the task's secure context, if
3417 * any. */
3418 portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE );
3419
3420 for( ; ; )
3421 {
3422 /* See if any tasks have deleted themselves - if so then the idle task
3423 * is responsible for freeing the deleted task's TCB and stack. */
3424 prvCheckTasksWaitingTermination();
/*调用函数 prvCheckTasksWaitingTermination()检查是否有需要释放内存的被删除任务,
*当 有 任 务 调 用 函 数 vTaskDelete() 删 除 自 身 的 话 , 此 任 务 就 会 添 加 到 列 表
*xTasksWaitingTermination 中 。 函 数 prvCheckTasksWaitingTermination() 会 检 查 列 表
*xTasksWaitingTermination 是否为空,如果不为空的话就依次将列表中所有任务对应的内存释放
*掉(任务控制块 TCB 和任务堆栈的内存)。*/
3425
3426 #if ( configUSE_PREEMPTION == 0 )
3427 {
3428 /* If we are not using preemption we keep forcing a task switch to
3429 * see if any other task has become available. If we are using
3430 * preemption we don't need to do this as any task becoming available
3431 * will automatically get the processor anyway. */
3432 taskYIELD();
/*如果没有使用抢占式内核的话就强制进行一次任务切换查看是否有其他
*任务有效,如果有使用抢占式内核的话就不需要这一步,因为只要有任
*何任务有效(就绪)之后都会自动的抢夺 CPU 使用权*/
3433 }
3434 #endif /* configUSE_PREEMPTION */
3435
3436 #if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
3437 {
3438 /* When using preemption tasks of equal priority will be
3439 * timesliced. If a task that is sharing the idle priority is ready
3440 * to run then the idle task should yield before the end of the
3441 * timeslice.
3442 *
3443 * A critical region is not required here as we are just reading from
3444 * the list, and an occasional incorrect value will not matter. If
3445 * the ready list at the idle priority contains more than one task
3446 * then a task other than the idle task is ready to execute. */
/*如果使用抢占式内核并且使能时间片调度的话, 当有任务和空闲任务共享
*一个优先级的时候,并且此任务处于就绪态的话空闲任务就应该放弃本时
*间片,将本时间片剩余的时间让给这个就绪任务。如果在空闲任务优先级
*下的就绪列表中有多个用户任务的话就执行这些任务。*/
3447 if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 )
/*检查优先级为 tskIDLE_PRIORITY(空闲任务优先级)的就绪任务列表是否为空,如果不
*为空的话就调用函数 taskYIELD()进行一次任务切换*/
3448 {
3449 taskYIELD();
3450 }
3451 else
3452 {
3453 mtCOVERAGE_TEST_MARKER();
3454 }
3455 }
3456 #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */
3457
3458 #if ( configUSE_IDLE_HOOK == 1 )
3459 {
3460 extern void vApplicationIdleHook( void );
3461
3462 /* Call the user defined function from within the idle task. This
3463 * allows the application designer to add background functionality
3464 * without the overhead of a separate task.
3465 * NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
3466 * CALL A FUNCTION THAT MIGHT BLOCK. */
3467 vApplicationIdleHook();
/*如果使能了空闲任务钩子函数的话就执行这个钩子函数,空闲任务钩子函数的函数名
*为 vApplicationIdleHook(),这个函数需要用户自行编写!在编写这个这个钩子函数的时候一定
*不能调用任何可以阻塞空闲任务的 API 函数*/
3468 }
3469 #endif /* configUSE_IDLE_HOOK */
3470
3471 /* This conditional compilation should use inequality to 0, not equality
3472 * to 1. This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when
3473 * user defined low power mode implementations require
3474 * configUSE_TICKLESS_IDLE to be set to a value other than 1. */
3475 #if ( configUSE_TICKLESS_IDLE != 0 )
/*configUSE_TICKLESS_IDLE 不为 0,说明使能了 FreeRTOS 的低功耗 Tickless 模式*/
3476 {
3477 TickType_t xExpectedIdleTime;
3478
3479 /* It is not desirable to suspend then resume the scheduler on
3480 * each iteration of the idle task. Therefore, a preliminary
3481 * test of the expected idle time is performed without the
3482 * scheduler suspended. The result here is not necessarily
3483 * valid. */
3484 xExpectedIdleTime = prvGetExpectedIdleTime();
3485
3486 if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
3487 {
3488 vTaskSuspendAll();
3489 {
3490 /* Now the scheduler is suspended, the expected idle
3491 * time can be sampled again, and this time its value can
3492 * be used. */
3493 configASSERT( xNextTaskUnblockTime >= xTickCount );
3494 xExpectedIdleTime = prvGetExpectedIdleTime();
3495
3496 /* Define the following macro to set xExpectedIdleTime to 0
3497 * if the application does not want
3498 * portSUPPRESS_TICKS_AND_SLEEP() to be called. */
3499 configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime );
3500
3501 if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
3502 {
3503 traceLOW_POWER_IDLE_BEGIN();
3504 portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
3505 traceLOW_POWER_IDLE_END();
3506 }
3507 else
3508 {
3509 mtCOVERAGE_TEST_MARKER();
3510 }
3511 }
3512 ( void ) xTaskResumeAll();
3513 }
3514 else
3515 {
3516 mtCOVERAGE_TEST_MARKER();
3517 }
3518 }
3519 #endif /* configUSE_TICKLESS_IDLE */
3520 }
3521 }
3522 /*-----------------------------------------------------------*/
2.4空闲任务钩子函数
FreeRTOS 中有多个钩子函数,钩子函数类似回调函数,当某个功能(函数)执行的时候就会调用钩子函数,至于钩子函数的具体内容那就由用户来编写。如果不需要使用钩子函数的话就什么也不用管,钩子函数是一个可选功能,可以通过宏定义来选择使用哪个钩子函数, 可选的钩子函数如表 所示
宏定义 | 描述 |
---|---|
configUSE_IDLE_HOOK | 空闲任务钩子函数,空闲任务会调用此钩子函数。 |
configUSE_TICK_HOOK | 时间片钩子函数, xTaskIncrementTick()会调用此钩子函数。此钩子函数最终会被节拍中断服务函数用,对于 STM32 来说就是滴答定时器中断服务函数。 |
configUSE_MALLOC_FAILED_HOOK | 内存申请失败钩子函数 ,当使用函数pvPortMalloc()申请内存失败的时候就会调用此钩子函数。 |
configUSE_DAEMON_TASK_STARTUP_HOOK | 守护(Daemon)任务启动钩子函数,守护任务也就是定时器服务任务。 |
钩子函数的使用方法基本相同,用户使能相应的钩子函数,然后自行根据实际需求编写钩子函数的内容,
2.任务API函数及分析
2.1任务API函数总览
任务创建和删除API函数
函数 | 描述 |
---|---|
xTaskCreate() | 使用动态的方法创建一个任务。 |
xTaskCreateStatic() | 使用静态的方法创建一个任务。 |
xTaskCreateRestricted() | 创建一个使用 MPU 进行限制的任务,相关内存使用动态内存分配。 |
vTaskDelete() | 删除一个任务。 |
任务挂起和恢复API函数
函数 | 描述 |
---|---|
vTaskSuspend() | 挂起一个任务。 |
vTaskSuspendAll() | 将所有任务都挂起,就是挂起调度器 |
vTaskResume() | 恢复一个任务的运行。 |
xTaskResumeFromISR() | 中断服务函数中恢复一个任务的运行。 |
xTaskResumeAll() | 恢复调度器 |
任务延时API函数
函数 | 描述 |
---|---|
vTaskDelay() | 相对延时函数 |
vTaskDelayUntil() | 绝对延时函数 |
2.2任务创建函数
2.2.1.xTaskCreate() 函数原型
此函数用来创建一个任务,任务需要 RAM 来保存与任务有关的状态信息(任务控制块),任务也需要一定的 RAM 来作为任务堆栈。如果使用函数 xTaskCreate()来创建任务的话那么这些所需的 RAM 就会自动的从 FreeRTOS 的堆中分配, 因此必须提供内存管理文件,默认我们使用heap_*.c 这个内存管理文件,而且宏configSUPPORT_DYNAMIC_ALLOCATION 必须为 1。
719 #if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
720
721 BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, /*任务入口函数*/
722 const char * const pcName, /*任务名字 */
723 const configSTACK_DEPTH_TYPE usStackDepth,/*任务栈大小*/
724 void * const pvParameters,/*任务入口函数参数*/
725 UBaseType_t uxPriority,/*任务的优先级*/
726 TaskHandle_t * const pxCreatedTask ) /*任务控制模块*/
727 {
728 TCB_t * pxNewTCB;
729 BaseType_t xReturn;
730
731 /* If the stack grows down then allocate the stack then the TCB so the stack
732 * does not grow into the TCB. Likewise if the stack grows up then allocate
733 * the TCB then the stack. */
734 #if ( portSTACK_GROWTH > 0 )
735 {
736 /* Allocate space for the TCB. Where the memory comes from depends on
737 * the implementation of the port malloc function and whether or not static
738 * allocation is being used. */
739 pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
740
741 if( pxNewTCB != NULL )
742 {
743 /* Allocate space for the stack used by the task being created.
744 * The base of the stack memory stored in the TCB so the task can
745 * be deleted later if required. */
746 pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some p orts. */
747
748 if( pxNewTCB->pxStack == NULL )
749 {
750 /* Could not allocate the stack. Delete the allocated TCB. */
751 vPortFree( pxNewTCB );
752 pxNewTCB = NULL;
753 }
754 }
755 }
756 #else /* portSTACK_GROWTH */
757 {
758 StackType_t * pxStack;
759
760 /* Allocate space for the stack used by the task being created. */
761 pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
/*使用函数 pvPortMalloc()给任务的任务堆栈申请内存,申请内存的时候会做字节对齐处 */
762
763 if( pxStack != NULL )
764 {
765 /* Allocate space for the TCB. */
766 pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
/*如果堆栈的内存申请成功的话就接着给任务控制块申请内存,同样使用函数pvPortMalloc()。*/
767
768 if( pxNewTCB != NULL )
769 {
770 /* Store the stack location in the TCB. */
771 pxNewTCB->pxStack = pxStack;
/*任务控制块内存申请成功的话就初始化内存控制块中的任务堆栈字段 pxStack,使用上面中申请到的任务堆栈。*/
772 }
773 else
774 {
775 /* The stack cannot be used as the TCB was not created. Free
776 * it again. */
777 vPortFree( pxStack );
/*如果任务控制块内存申请失败的话就释放前面已经申请成功的任务堆栈的内存。*/
778 }
779 }
780 else
781 {
782 pxNewTCB = NULL;
783 }
784 }
785 #endif /* portSTACK_GROWTH */
786
787 if( pxNewTCB != NULL )
788 {
789 #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
790 {
791 /* Tasks can be created statically or dynamically, so note this
792 * task was created dynamically in case it is later deleted. */
793 pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
/*标记任务堆栈和任务控制块是使用动态内存分配方法得到的。*/
794 }
795 #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */
796
797 prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
/*使用函数 prvInitialiseNewTask()初始化任务,这个函数完成对任务控制块中各个字段
的初始化工作!*/
798 prvAddNewTaskToReadyList( pxNewTCB );
/*使用函数 prvAddNewTaskToReadyList()将新创建的任务加入到就绪列表中。*/
799 xReturn = pdPASS;
800 }
801 else
802 {
803 xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
804 }
805
806 return xReturn;
807 }
808
809 #endif /* configSUPPORT_DYNAMIC_ALLOCATION */
参数:
pxTaskCode: 任务入口函数,即任务函数的名称,需要我们自己定义并且实现。
pcName: 任务名字,字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。
usStackDepth: 任务堆栈大小,注意实际申请到的堆栈是 usStackDepth 的 4 倍。其中空闲任务的任务堆栈大小为 configMINIMAL_STACK_SIZE。
pvParameters: 传递给任务函数的参数。
uxPriotiry: 任务的优先级。优先级范围根据 FreeRTOSConfig.h 中的宏
configMAX_PRIORITIES 决定, 如果使能 configUSE_PORT_OPTIMISED_TASK_SELECTION,这个宏定义,则最多支持 32 个优先级;如果不用特殊方法查找下一个运行的任务,那么则不强制要求限制最大可用优先级数目。在 FreeRTOS 中, 数值越大优先级越高, 0 代表最低优先级。
pxCreatedTask: 任务句柄,任务创建成功以后会返回此任务的任务句柄, 这个句柄其实就是任务的任务堆栈。 此参数就用来保存这个任务句柄。其他 API 函数可能会使用到这个句柄。
返回值:
pdPASS: 任务创建成功。
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 任务创建失败,因为堆内存不足!
2.2.2任务初始化函数 prvInitialiseNewTask()
函数原型:
812 static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
813 const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
814 const uint32_t ulStackDepth,
815 void * const pvParameters,
816 UBaseType_t uxPriority,
817 TaskHandle_t * const pxCreatedTask,
818 TCB_t * pxNewTCB,
819 const MemoryRegion_t * const xRegions )
820 {
821 StackType_t * pxTopOfStack;
822 UBaseType_t x;
823
824 #if ( portUSING_MPU_WRAPPERS == 1 )
825 /* Should the task be created in privileged mode? */
826 BaseType_t xRunPrivileged;
827
828 if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
829 {
830 xRunPrivileged = pdTRUE;
831 }
832 else
833 {
834 xRunPrivileged = pdFALSE;
835 }
836 uxPriority &= ~portPRIVILEGE_BIT;
837 #endif /* portUSING_MPU_WRAPPERS == 1 */
838
839 /* Avoid dependency on memset() if it is not required. */
840 #if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
841 {
842 /* Fill the stack with a known value to assist debugging. */
843 ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
/*如 果 使 能 了 堆 栈 溢 出 检 测 功 能 或 者 追 踪 功 能 的 话 就 使 用 一 个 定 值
*tskSTACK_FILL_BYTE 来填充任务堆栈,这个值为 0xa5U*/
844 }
845 #endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */
846
847 /* Calculate the top of stack address. This depends on whether the stack
848 * grows from high memory to low (as per the 80x86) or vice versa.
849 * portSTACK_GROWTH is used to make the result positive or negative as required
850 * by the port. */
851 #if ( portSTACK_GROWTH < 0 )
852 {
853 pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
854 pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
/*计算堆栈栈顶 pxTopOfStack,后面初始化堆栈的时候需要用到。*/
855
856 /* Check the alignment of the calculated top of stack is correct. */
857 configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
858
859 #if ( configRECORD_STACK_HIGH_ADDRESS == 1 )
860 {
861 /* Also record the stack's high address, which may assist
862 * debugging. */
863 pxNewTCB->pxEndOfStack = pxTopOfStack;
864 }
865 #endif /* configRECORD_STACK_HIGH_ADDRESS */
866 }
867 #else /* portSTACK_GROWTH */
868 {
869 pxTopOfStack = pxNewTCB->pxStack;
870
871 /* Check the alignment of the stack buffer is correct. */
872 configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
873
874 /* The other extreme of the stack space is required if stack checking is
875 * performed. */
876 pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
877 }
878 #endif /* portSTACK_GROWTH */
879
880 /* Store the task name in the TCB. */
881 if( pcName != NULL )
882 {
883 for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
884 {
885 pxNewTCB->pcTaskName[ x ] = pcName[ x ]; /*保存任务的任务名*/
886
887 /* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than
888 * configMAX_TASK_NAME_LEN characters just in case the memory after the
889 * string is not accessible (extremely unlikely). */
890 if( pcName[ x ] == ( char ) 0x00 )
891 {
892 break;
893 }
894 else
895 {
896 mtCOVERAGE_TEST_MARKER();
897 }
898 }
899
900 /* Ensure the name string is terminated in the case that the string length
901 * was greater or equal to configMAX_TASK_NAME_LEN. */
902 pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
/*任务名数组添加字符串结束符’\0’。*/
903 }
904 else
905 {
906 /* The task has not been given a name, so just ensure there is a NULL
907 * terminator when it is read out. */
908 pxNewTCB->pcTaskName[ 0 ] = 0x00;
909 }
910
911 /* This is used as an array index so must ensure it's not too large. First
912 * remove the privilege bit if one is present. */
913 if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
/*判断任务优先级是否合法,如果设置的任务优先级大于 configMAX_PRIORITIES,则将优先级修改为 configMAX_PRIORITIES-1*/
914 {
915 uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
916 }
917 else
918 {
919 mtCOVERAGE_TEST_MARKER();
920 }
921
922 pxNewTCB->uxPriority = uxPriority;
/*初始化任务控制块的优先级字段 uxPriority*/
923 #if ( configUSE_MUTEXES == 1 )
/*使能了互斥信号量功能,需要初始化相应的字段*/
924 {
925 pxNewTCB->uxBasePriority = uxPriority;
926 pxNewTCB->uxMutexesHeld = 0;
927 }
928 #endif /* configUSE_MUTEXES */
929
930 vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
931 vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
/*初始化列表项 xStateListItem 和xEventListItem,任务控制块结构体中有两个列表项,这里对这两个列表项做初始化。*/
932
933 /* Set the pxNewTCB as a link back from the ListItem_t. This is so we can get
934 * back to the containing TCB from a generic item in a list. */
935 listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/*设置列表项xStateListItem 和 xEventListItem 属于当前任务的任务控制块,也就是设置这两个列表项
*的字段 pvOwner 为新创建的任务的任务控制块*/
936
937 /* Event lists are always in priority order. */
938 listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
/*设置列表项xEventListItem的字段xItemValue为configMAX_PRIORITIES- uxPriority,
*比如当前任务优先级 3,最大优先级为 32,那么 xItemValue 就为 32-3=29,这就意味着 xItemValue值越大,优先级就越小。 */
939 listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB ); /* 设置列表项xStateListItem 和 xEventListItem 属于当前任务的任务控制块,也就是设置这两个列表项的字段 pvOwner 为新创建的任务的任务控制块*/
940
941 #if ( portCRITICAL_NESTING_IN_TCB == 1 ) /*使能临界区嵌套*/
942 {
943 pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
944 }
945 #endif /* portCRITICAL_NESTING_IN_TCB */
946
947 #if ( configUSE_APPLICATION_TASK_TAG == 1 ) /*使能任务标签功能*/
948 {
949 pxNewTCB->pxTaskTag = NULL;
950 }
951 #endif /* configUSE_APPLICATION_TASK_TAG */
952
953 #if ( configGENERATE_RUN_TIME_STATS == 1 ) /*使能时间统计功能*/
954 {
955 pxNewTCB->ulRunTimeCounter = 0UL;
956 }
957 #endif /* configGENERATE_RUN_TIME_STATS */
958
959 #if ( portUSING_MPU_WRAPPERS == 1 )
960 {
961 vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
962 }
963 #else
964 {
965 /* Avoid compiler warning about unreferenced parameter. */
966 ( void ) xRegions;
967 }
968 #endif
969
970 #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
971 {
972 memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );
/*初始化线程本地存储指针,如果使能了这个功能的话*/
973 }
974 #endif
975
976 #if ( configUSE_TASK_NOTIFICATIONS == 1 ) /*使能任务通知功能*/
977 {
978 memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );
979 memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );
980 }
981 #endif
982
983 #if ( configUSE_NEWLIB_REENTRANT == 1 ) /*使能NEWLIB*/
984 {
985 /* Initialise this task's Newlib reent structure.
986 * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
987 * for additional information. */
988 _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
989 }
990 #endif
991
992 #if ( INCLUDE_xTaskAbortDelay == 1 ) /*使能函数xTaskAbortDelay()*/
993 {
994 pxNewTCB->ucDelayAborted = pdFALSE;
995 }
996 #endif
997
998 /* Initialize the TCB stack to look as if the task was already running,
999 * but had been interrupted by the scheduler. The return address is set
1000 * to the start of the task function. Once the stack has been initialised
1001 * the top of stack variable is updated. */
1002 #if ( portUSING_MPU_WRAPPERS == 1 )
1003 {
1004 /* If the port has capability to detect stack overflow,
1005 * pass the stack end address to the stack initialization
1006 * function as well. */
1007 #if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
1008 {
1009 #if ( portSTACK_GROWTH < 0 )
1010 {
1011 pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );
1012 }
1013 #else /* portSTACK_GROWTH */
1014 {
1015 pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );
1016 }
1017 #endif /* portSTACK_GROWTH */
1018 }
1019 #else /* portHAS_STACK_OVERFLOW_CHECKING */
1020 {
1021 pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
1022 }
1023 #endif /* portHAS_STACK_OVERFLOW_CHECKING */
1024 }
1025 #else /* portUSING_MPU_WRAPPERS */
1026 {
1027 /* If the port has capability to detect stack overflow,
1028 * pass the stack end address to the stack initialization
1029 * function as well. */
1030 #if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
1031 {
1032 #if ( portSTACK_GROWTH < 0 )
1033 {
1034 pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
1035 }
1036 #else /* portSTACK_GROWTH */
1037 {
1038 pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
1039 }
1040 #endif /* portSTACK_GROWTH */
1041 }
1042 #else /* portHAS_STACK_OVERFLOW_CHECKING */
1043 {
1044 pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
/*调用函数 pxPortInitialiseStack()初始化任务堆栈*/
1045 }
1046 #endif /* portHAS_STACK_OVERFLOW_CHECKING */
1047 }
1048 #endif /* portUSING_MPU_WRAPPERS */
1049
1050 if( pxCreatedTask != NULL )
1051 {
1052 /* Pass the handle out in an anonymous way. The handle can be used to
1053 * change the created task's priority, delete the created task, etc.*/
1054 *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
/*生成任务句柄,返回给参数 pxCreatedTask,从这里可以看出任务句柄其实就是任务控制块。*/
1055 }
1056 else
1057 {
1058 mtCOVERAGE_TEST_MARKER();
1059 }
1060 }
2.2.3任务堆栈初始化函数 pxPortInitialiseStack()
函数原型:举例说明
186 /*-----------------------------------------------------------*/
187
188 /*
189 * See header file for description.
190 */
191 StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
192 TaskFunction_t pxCode,
193 void * pvParameters )
194 {
195 /* Simulate the stack frame as it would be created by a context switch
196 * interrupt. */
197
198 /* Offset added to account for the way the MCU uses the stack on entry/exit
199 * of interrupts, and to ensure alignment. */
200 pxTopOfStack--;
201
202 *pxTopOfStack = portINITIAL_XPSR;
/* 寄存器 xPSR 值为 portINITIAL_XPSR,其值为 0x01000000。 xPSR 是 Cortex-M4 的一个内核寄存器,叫做程序状态寄存器,
*0x01000000 表示这个寄存器的 bit24 为 1,表示处于 Thumb状态,即使用的 Thumb 指令 */
203 pxTopOfStack--;
204 *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;
/* PC 寄存器 PC 初始化为任务函数 pxCode */
205 pxTopOfStack--;
206 *pxTopOfStack = ( StackType_t ) prvTaskExitError;
/* LR 寄存器 LR 初始化为函数 prvTaskExitError */
207
208 /* Save code space by skipping register initialisation. */
209 pxTopOfStack -= 5;
/* R12, R3, R2 and R1. 跳过 4 个寄存器, R12, R3, R2, R1,这四个寄存器不初始化*/
210 *pxTopOfStack = ( StackType_t ) pvParameters;
/* R0 寄存器 R0 初始化为 pvParameters,一般情况下,函数调用会将 R0~R3 作为输入参数,
*R0 也可用作返回结果,如果返回值为 64 位,则 R1 也会用于返回结果,
*这里的 pvParameters 是作为任务函数的参数,保存在寄存器 R0 中 */
211
212 /* A save method is being used that requires each task to maintain its
213 * own exec return value. */
214 pxTopOfStack--;
215 *pxTopOfStack = portINITIAL_EXC_RETURN;
/*保存 EXC_RETURN 值,用于设置退出 SVC 或 PendSV 中断的时候处理器应该处于什么壮态。
*处理器进入异常或中断服务程序(ISR)时,链接寄存器 R14(LR)的数值会被更新为EXC_RETURN 数值,
*之后该数值会在异常处理结束时触发异常返回。这里人为的设置为0XFFFFFFD,
*表示退出异常以后 CPU 进入线程模式并且使用进程栈!*/
216
217 pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. 跳过 8 个寄存器, R11、 R10、 R8、 R7、 R6、 R5、 R4*/
218
219 return pxTopOfStack;
220 }
221 /*-----------------------------------------------------------*/
堆栈是用来在进行上下文切换的时候保存现场的,一般在新创建好一个堆栈以后会对其先进行初始化处理
2.2.4 添加任务到就绪列表 prvAddNewTaskToReadyList()
任务创建完成以后就会被添加到就绪列表中, FreeRTOS 使用不同的列表表示任务的不同
状态,在文件tasks.c 中就定义了多个列表和指向列表的指针来完成不同的功能,这些列表如下:
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
PRIVILEGED_DATA static List_t xDelayedTaskList1;
PRIVILEGED_DATA static List_t xDelayedTaskList2;
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
PRIVILEGED_DATA static List_t xPendingReadyList;
列表数组 pxReadyTasksLists[]就是任务就绪列表,数组大小为 configMAX_PRIORITIES,也就是说一个优先级一个列表,这样相同优先级的任务就使用一个列表。将一个新创建的任务添加到就绪列表中通过函数 prvAddNewTaskToReadyList()来完成,函数如下:
1063 static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
1064 {
1065 /* Ensure interrupts don't access the task lists while the lists are being
1066 * updated. */
1067 taskENTER_CRITICAL();
1068 {
1069 uxCurrentNumberOfTasks++;
/*变量 uxCurrentNumberOfTasks 为全局变量,用来统计任务数量*/
1070
1071 if( pxCurrentTCB == NULL )
/*正在运行的任务块为NULL,说明没有任务运行*/
1072 {
1073 /* There are no other tasks, or all the other tasks are in
1074 * the suspended state - make this the current task. */
1075 pxCurrentTCB = pxNewTCB;
/*将新任务的任务控制块赋值给 pxCurrentTCB,新创建的任务是第一个任务!!!*/
1076
1077 if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
1078 {
1079 /* This is the first task to be created so do the preliminary
1080 * initialisation required. We will not recover if this call
1081 * fails, but we will report the failure. */
1082 prvInitialiseTaskLists();
/*变量 uxCurrentNumberOfTasks 为 1 说明正在创建的任务是第一任务!那么就需要先初始化相应的列表,
通过调用函数 prvInitialiseTaskLists()来初始化相应的列表。这个函数很简单,
本质就是调用上一章讲的列表初始化函数 vListInitialise()来初始化几个列表*/
1083 }
1084 else
1085 {
1086 mtCOVERAGE_TEST_MARKER();
1087 }
1088 }
1089 else
1090 {
1091 /* If the scheduler is not already running, make this task the
1092 * current task if it is the highest priority task to be created
1093 * so far. */
1094 if( xSchedulerRunning == pdFALSE )
1095 {
1096 if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
1097 {
1098 pxCurrentTCB = pxNewTCB;
/*新创建的任务优先级比正在运行的任务优先级高,所以需要修改 pxCurrentTCB 为新建任务的任务控制块*/
1099 }
1100 else
1101 {
1102 mtCOVERAGE_TEST_MARKER();
1103 }
1104 }
1105 else
1106 {
1107 mtCOVERAGE_TEST_MARKER();
1108 }
1109 }
1110
1111 uxTaskNumber++;
1112
1113 #if ( configUSE_TRACE_FACILITY == 1 )
1114 {
1115 /* Add a counter into the TCB for tracing only. */
1116 pxNewTCB->uxTCBNumber = uxTaskNumber;
1117 }
1118 #endif /* configUSE_TRACE_FACILITY */
1119 traceTASK_CREATE( pxNewTCB );
1120
1121 prvAddTaskToReadyList( pxNewTCB );
/*调用函数 prvAddTaskToReadyList()将任务添加到就绪列表中,这个其实是个宏*/
1122
1123 portSETUP_TCB( pxNewTCB );
1124 }
1125 taskEXIT_CRITICAL();
1126
1127 if( xSchedulerRunning != pdFALSE )
1128 {
1129 /* If the created task is of a higher priority than the current task
1130 * then it should run now. */
1131 if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
1132 {
1133 taskYIELD_IF_USING_PREEMPTION();
/*如果新任务的任务优先级最高,而且调度器已经开始正常运行了,那么就调用函数taskYIELD_IF_USING_PREEMPTION()完成一次任务切换*/
1134 }
1135 else
1136 {
1137 mtCOVERAGE_TEST_MARKER();
1138 }
1139 }
1140 else
1141 {
1142 mtCOVERAGE_TEST_MARKER();
1143 }
1144 }
1145 /*-----------------------------------------------------------*/
2.3任务删除函数 vTaskDelete()
函数原型
1147 #if ( INCLUDE_vTaskDelete == 1 )
114
1149 void vTaskDelete( TaskHandle_t xTaskToDelete )
1150 {
1151 TCB_t * pxTCB;
1152
1153 taskENTER_CRITICAL();
1154 {
1155 /* If null is passed in here then it is the calling task that is
1156 * being deleted. */
1157 pxTCB = prvGetTCBFromHandle( xTaskToDelete );
/*如果参数为 NULL 的话那么说明调用函数 vTaskDelete()的任务要删除自身,调用函数
*prvGetTCBFromHandle()获取要删除任务的任务控制块,参数为任务句柄。
*如果参数为当前正在执行的任务句柄那么返回值就为 NULL*/
1158
1159 /* Remove task from the ready/delayed list. */
1160 if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) /*将任务从任务就绪列表中删除*/
1161 {
1162 taskRESET_READY_PRIORITY( pxTCB->uxPriority );
1163 }
1164 else
1165 {
1166 mtCOVERAGE_TEST_MARKER();
1167 }
1168
1169 /* 查看任务是否正在等待某个事件(如信号量、队列等),
*因为如果任务等待某个事件的话这个任务会被放到相应的列表中,这里需要将其从相应的列表中删除掉*/
1170 if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
1171 {
1172 ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
1173 }
1174 else
1175 {
1176 mtCOVERAGE_TEST_MARKER();
1177 }
1178
1179 /* Increment the uxTaskNumber also so kernel aware debuggers can
1180 * detect that the task lists need re-generating. This is done before
1181 * portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will
1182 * not return. */
1183 uxTaskNumber++;
1184
1185 if( pxTCB == pxCurrentTCB ) /*要删除的是当前正在运行的任务*/
1186 {
1187 /* A task is deleting itself. This cannot complete within the
1188 * task itself, as a context switch to another task is required.
1189 * Place the task in the termination list. The idle task will
1190 * check the termination list and free up any memory allocated by
1191 * the scheduler for the TCB and stack of the deleted task. */
1192 vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
/*要删除任务,那么任务的任务控制块和任务堆栈所占用的内存肯定要被释放掉(如果使
*用动态方法创建的任务),但是当前任务正在运行,显然任务控制块和任务堆栈的内存不能被立
*即释放掉!必须等到当前任务运行完成才能释放相应的内存,所以需要打一个“标记”,标记出
*有任务需要处理。这里将当前任务添加到列表 xTasksWaitingTermination 中,如果有任务要删除
*自身的话都会被添加到列表 xTasksWaitingTermination 中。那么问题来了?内存释放在哪里完成
*呢?空闲任务!空闲任务会依次将需要释放的内存都释放掉。*/
1193
1194 /* Increment the ucTasksDeleted variable so the idle task knows
1195 * there is a task that has been deleted and that it should therefore
1196 * check the xTasksWaitingTermination list. */
1197 ++uxDeletedTasksWaitingCleanUp;
/*uxDeletedTasksWaitingCleanUp 是一个全局变量,用来记录有多少个任务需要释放内存*/
1198
1199 /* Call the delete hook before portPRE_TASK_DELETE_HOOK() as
1200 * portPRE_TASK_DELETE_HOOK() does not return in the Win32 port. */
1201 traceTASK_DELETE( pxTCB );
1202
1203 /* The pre-delete hook is primarily for the Windows simulator,
1204 * in which Windows specific clean up operations are performed,
1205 * after which it is not possible to yield away from this task -
1206 * hence xYieldPending is used to latch that a context switch is
1207 * required. */
1208 portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
/*调用任务删除钩子函数,钩子函数的具体内容需要用户自行实现*/
1209 }
1210 else
1211 {
1212 --uxCurrentNumberOfTasks;
/*删除的是别的任务,变量 uxCurrentNumberOfTasks 减一,也就是当前任务数减一*/
1213 traceTASK_DELETE( pxTCB );
/*因为是删除别的任务,所以可以直接调用函数 prvDeleteTCB()删除任务控制块*/
1214 prvDeleteTCB( pxTCB );
1215
1216 /* Reset the next expected unblock time in case it referred to
1217 * the task that has just been deleted. */
1218 prvResetNextTaskUnblockTime();
/*重新计算一下还要多长时间执行下一个任务,也就是下一个任务的解锁时间,防止有
*任务的解锁时间参考了刚刚被删除的那个任务*/
1219 }
1220 }
1221 taskEXIT_CRITICAL();
1222
1223 /* Force a reschedule if it is the currently running task that has just
1224 * been deleted. */
1225 if( xSchedulerRunning != pdFALSE )
1226 {
1227 if( pxTCB == pxCurrentTCB )
1228 {
1229 configASSERT( uxSchedulerSuspended == 0 );
1230 portYIELD_WITHIN_API();
/*如果删除的是正在运行的任务那么删除完以后肯定需要强制进行一次任务切换*/
1231 }
1232 else
1233 {
1234 mtCOVERAGE_TEST_MARKER();
1235 }
1236 }
1237 }
1238
1239 #endif /* INCLUDE_vTaskDelete */
1240 /*-----------------------------------------------------------*/
2.4 任务挂起和恢复函数
2.4.1任务挂起函数
函数原型
1697 #if ( INCLUDE_vTaskSuspend == 1 )
1698
1699 void vTaskSuspend( TaskHandle_t xTaskToSuspend )
1700 {
1701 TCB_t * pxTCB;
1702
1703 taskENTER_CRITICAL();
1704 {
1705 /* If null is passed in here then it is the running task that is
1706 * being suspended. */
1707 pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
/*如果参数为 NULL 的话说明挂起自身*/
/*通过函数 prvGetTCBFromHandle()获取要删除任务的任务控制块*/
1708
1709 traceTASK_SUSPEND( pxTCB );
1710
1711 /* Remove task from the ready/delayed list and place in the
1712 * suspended list. */
/*将任务从就绪或者延时列表中删除,并且将任务放到挂起列表中*/
1713 if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
1714 {
1715 taskRESET_READY_PRIORITY( pxTCB->uxPriority );
1716 }
1717 else
1718 {
1719 mtCOVERAGE_TEST_MARKER();
1720 }
1721
1722 /* Is the task waiting on an event also? */
1723 if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
/*查看任务是否正在等待某个事件(如信号量、队列等),如果任务还在等待某个事件的
*话就将其从相应的事件列表中删除*/
1724 {
1725 ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
1726 }
1727 else
1728 {
1729 mtCOVERAGE_TEST_MARKER();
1730 }
1731
1732 vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
/*将任务添加到挂起任务列表尾,挂起任务列表为 xSuspendedTaskList,所有被挂起的任
*务都会被放到这个列表中*/
1733
1734 #if ( configUSE_TASK_NOTIFICATIONS == 1 )
1735 {
1736 BaseType_t x;
1737
1738 for( x = 0; x < configTASK_NOTIFICATION_ARRAY_ENTRIES; x++ )
1739 {
1740 if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION )
1741 {
1742 /* The task was blocked to wait for a notification, but is
1743 * now suspended, so no notification was received. */
1744 pxTCB->ucNotifyState[ x ] = taskNOT_WAITING_NOTIFICATION;
1745 }
1746 }
1747 }
1748 #endif /* if ( configUSE_TASK_NOTIFICATIONS == 1 ) */
1749 }
1750 taskEXIT_CRITICAL();
1751
1752 if( xSchedulerRunning != pdFALSE )
1753 {
1754 /* Reset the next expected unblock time in case it referred to the
1755 * task that is now in the Suspended state. */
1756 taskENTER_CRITICAL();
1757 {
1758 prvResetNextTaskUnblockTime();
/*重新计算一下还要多长时间执行下一个任务,也就是下一个任务的解锁时间。防止有
任务的解锁时间参考了刚刚被挂起的那个任务*/
1759 }
1760 taskEXIT_CRITICAL();
1761 }
1762 else
1763 {
1764 mtCOVERAGE_TEST_MARKER();
1765 }
1766
1767 if( pxTCB == pxCurrentTCB )
1768 {
1769 if( xSchedulerRunning != pdFALSE )
1770 {
1771 /* The current task has just been suspended. */
1772 configASSERT( uxSchedulerSuspended == 0 );
1773 portYIELD_WITHIN_API();
/*如果刚刚挂起的任务是正在运行的任务,并且任务调度器运行正常,那么这里就需要
*调用函数 portYIELD_WITHIN_API()强制进行一次任务切换*/
1774 }
1775 else
1776 {
1777 /* The scheduler is not running, but the task that was pointed
1778 * to by pxCurrentTCB has just been suspended and pxCurrentTCB
1779 * must be adjusted to point to a different task. */
1780 if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks ) /*lint !e931 Right has no side effect, just volatile. */
/*pxCurrentTCB 指向正在运行的任务,但是正在运行的任务要挂起了,所以必须给
*pxCurrentTCB 重新找一个“对象”。也就是查找下一个将要运行的任务,本来这个工作是由任
*务切换函数来完成的,但是程序运行到这一行说明任务调度器被挂起了,任务切换函数也无能
*为力了,必须手动查找下一个要运行的任务了。调用函数 listCURRENT_LIST_LENGTH()判断
*一下系统中所有的任务是不是都被挂起了,也就是查看列表 xSuspendedTaskList 的长度是不是
*等于 uxCurrentNumberOfTasks。如果等于的话就说明系统中所有的任务都被挂起了(实际上不存
*在这种情况,因为最少都有一个空闲任务是可以运行的,空闲任务执行期间不会调用任何可以
*阻塞或者挂起空闲任务的 API 函数,为的就是保证系统中永远都有一个可运行的任务)*/
1781 {
1782 /* No other tasks are ready, so set pxCurrentTCB back to
1783 * NULL so when the next task is created pxCurrentTCB will
1784 * be set to point to it no matter what its relative priority
1785 * is. */
1786 pxCurrentTCB = NULL;
/*如果所有任务都被挂起的话 pxCurrentTCB 就只能等于 NULL 了,这样当有新任务被
创建的时候 pxCurrentTCB 就可以指向这个新任务*/
1787 }
1788 else
1789 {
1790 vTaskSwitchContext();
/*有其他的没有被挂起的任务,调用 vTaskSwitchContext()获取下一个要运行的任务*/
1791 }
1792 }
1793 }
1794 else
1795 {
1796 mtCOVERAGE_TEST_MARKER();
1797 }
1798 }
1799
1800 #endif /* INCLUDE_vTaskSuspend */
1801 /*-----------------------------------------------------------*/
2.4.2任务恢复函数 vTaskResume()
函数原型:
1847 /*-----------------------------------------------------------*/
1848
1849 #if ( INCLUDE_vTaskSuspend == 1 )
1850
1851 void vTaskResume( TaskHandle_t xTaskToResume )
1852 {
1853 TCB_t * const pxTCB = xTaskToResume;
/*根据参数获取要恢复的任务的任务控制块,因为不存在恢复正在运行的任务这种情况
*所以参数也不可能为 NULL(你强行给个为 NULL 的参数那也没办法),这里也就不需要使用函
*数 prvGetTCBFromHandle()来获取要恢复的任务控制块, prvGetTCBFromHandle()会处理参数为
*NULL 这种情况*/
1854
1855 /* It does not make sense to resume the calling task. */
1856 configASSERT( xTaskToResume );
1857
1858 /* The parameter cannot be NULL as it is impossible to resume the
1859 * currently executing task. */
1860 if( ( pxTCB != pxCurrentTCB ) && ( pxTCB != NULL ) )
/*任务控制块不能为 NULL 和 pxCurrentTCB,因为不存在说恢复当前正在运行的任务*/
1861 {
1862 taskENTER_CRITICAL();
1863 {
1864 if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
/*调用函数 prvTaskIsTaskSuspended()判断要恢复的任务之前是否已经被挂起了,恢复的
*肯定是被挂起的任务,没有挂起就不用恢复*/
1865 {
1866 traceTASK_RESUME( pxTCB );
1867
1868 /* The ready list can be accessed even if the scheduler is
1869 * suspended because this is inside a critical section. */
1870 ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
/*首先将要恢复的任务从原来的列表中删除,任务被挂起以后都会放到任务挂起列表
xSuspendedTaskList 中*/
1871 prvAddTaskToReadyList( pxTCB );
/*将要恢复的任务添加到就绪任务列表中*/
1872
1873 /* A higher priority task may have just been resumed. */
1874 if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
/*要恢复的任务优先级高于当前正在运行的任务优先级*/
1875 {
1876 /* This yield may not cause the task just resumed to run,
1877 * but will leave the lists in the correct state for the
1878 * next yield. */
1879 taskYIELD_IF_USING_PREEMPTION();
/*因 为 要 恢 复 的 任 务 其 优 先 级 最 高 , 所 以 需 要 调 用 函 数
*taskYIELD_IF_USING_PREEMPTION()来完成一次任务切换*/
1880 }
1881 else
1882 {
1883 mtCOVERAGE_TEST_MARKER();
1884 }
1885 }
1886 else
1887 {
1888 mtCOVERAGE_TEST_MARKER();
1889 }
1890 }
1891 taskEXIT_CRITICAL();
1892 }
1893 else
1894 {
1895 mtCOVERAGE_TEST_MARKER();
1896 }
1897 }
1898
1899 #endif /* INCLUDE_vTaskSuspend */
2.4.3挂起任务调度器vTaskSuspendAll()
函数原型:
2108 /*----------------------------------------------------------*/
2109
2110 void vTaskSuspendAll( void )
2111 {
2112 /* A critical section is not required as the variable is of type
2113 * BaseType_t. Please read Richard Barry's reply in the following link to a
2114 * post in the FreeRTOS support forum before reporting this as a bug! -
2115 * http://goo.gl/wu4acr */
2116
2117 /* portSOFRWARE_BARRIER() is only implemented for emulated/simulated ports that
2118 * do not otherwise exhibit real time behaviour. */
2119 portSOFTWARE_BARRIER();
2120
2121 /* The scheduler is suspended if uxSchedulerSuspended is non-zero. An increment
2122 * is used to allow calls to vTaskSuspendAll() to nest. */
2123 ++uxSchedulerSuspended;
2124
2125 /* Enforces ordering for ports and optimised compilers that may otherwise place
2126 * the above increment elsewhere. */
2127 portMEMORY_BARRIER();
2128 }
2129 /*----------------------------------------------------------*/
可看出,此函数只是简单的将变量 uxSchedulerSuspended 加一, uxSchedulerSuspended 是挂起嵌套计数器, 调度器挂起是支持嵌套的。 使用函数 xTaskResumeAll()可以恢复任务调度器,调用了几次 vTaskSuspendAll()挂起调度器,同样的也得调用几次 xTaskResumeAll()才会最终恢复任务调度器。
2.4.4将任务调度器从挂起壮态恢复xTaskResumeAll()
函数原型:
2192 /*----------------------------------------------------------*/
2193
2194 BaseType_t xTaskResumeAll( void )
2195 {
2196 TCB_t * pxTCB = NULL;
2197 BaseType_t xAlreadyYielded = pdFALSE;
2198
2199 /* If uxSchedulerSuspended is zero then this function does not match a
2200 * previous call to vTaskSuspendAll(). */
2201 configASSERT( uxSchedulerSuspended );
2202
2203 /* It is possible that an ISR caused a task to be removed from an event
2204 * list while the scheduler was suspended. If this was the case then the
2205 * removed task will have been added to the xPendingReadyList. Once the
2206 * scheduler has been resumed it is safe to move all the pending ready
2207 * tasks from this list into their appropriate ready list. */
2208 taskENTER_CRITICAL();
2209 {
2210 --uxSchedulerSuspended;
/*调度器挂起嵌套计数器 uxSchedulerSuspended 减一*/
2211
2212 if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
/*如果 uxSchedulerSuspended 为 0 说明所有的挂起都已经解除,调度器可以开始运行了*/
2213 {
2214 if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
2215 {
2216 /* Move any readied tasks from the pending list into the
2217 * appropriate ready list. */
2218 while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
/*while()循环处理列表 xPendingReadyList,只要列表 xPendingReadyList 不为空,说明还
*有任务挂到了列表 xPendingReadyList 上,这里需要将这些任务从列表 xPendingReadyList 上移
*除并添加到这些任务所对应的就绪列表中*/
2219 {
2220 pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as t he type of the pointer stored and retrieved is the same. */
/*遍历列表 xPendingReadyList,获取挂到列表 xPendingReadyList 上的任务对应的任务
*控制块*/
2221 ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
/*将任务从事件列表上删除*/
2222 ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
/*将任务从壮态列表上移除*/
2223 prvAddTaskToReadyList( pxTCB );
/*调用函数 prvAddTaskToReadyList()将任务添加到就绪列表中*/
2224
2225 /* If the moved task has a priority higher than the current
2226 * task then a yield must be performed. */
2227 if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
2228 {
2229 xYieldPending = pdTRUE;
/*判断任务的优先级是否高于当前正在运行的任务,如果是的话需要将 xYieldPending 标
*记为 pdTRUE,表示需要进行任务切换*/
2230 }
2231 else
2232 {
2233 mtCOVERAGE_TEST_MARKER();
2234 }
2235 }
2236
2237 if( pxTCB != NULL )
2238 {
2239 /* A task was unblocked while the scheduler was suspended,
2240 * which may have prevented the next unblock time from being
2241 * re-calculated, in which case re-calculate it now. Mainly
2242 * important for low power tickless implementations, where
2243 * this can prevent an unnecessary exit from low power
2244 * state. */
2245 prvResetNextTaskUnblockTime();
2246 }
2247
2248 /* If any ticks occurred while the scheduler was suspended then
2249 * they should be processed now. This ensures the tick count does
2250 * not slip, and that any delayed tasks are resumed at the correct
2251 * time. */
2252 {
2253 TickType_t xPendedCounts = xPendedTicks; /* Non-volatile copy. */
2254
2255 if( xPendedCounts > ( TickType_t ) 0U )
2256 {
2257 do
2258 {
2259 if( xTaskIncrementTick() != pdFALSE )
2260 {
2261 xYieldPending = pdTRUE;
2262 }
2263 else
2264 {
2265 mtCOVERAGE_TEST_MARKER();
2266 }
2267
2268 --xPendedCounts;
2269 } while( xPendedCounts > ( TickType_t ) 0U );
2270
2271 xPendedTicks = 0;
2272 }
2273 else
2274 {
2275 mtCOVERAGE_TEST_MARKER();
2276 }
2277 }
2278
2279 if( xYieldPending != pdFALSE )
/*根据上面的代码得出需要进行任务切换*/
2280 {
2281 #if ( configUSE_PREEMPTION != 0 )
2282 {
2283 xAlreadyYielded = pdTRUE;
/*标记在函数 xTaskResumeAll()中进行了任务切换,变量 xAlreadyYielded 用于标记在
*函数 xTaskResumeAll()中是否有进行任务切换*/
2284 }
2285 #endif
2286 taskYIELD_IF_USING_PREEMPTION();
/*调用函数 taskYIELD_IF_USING_PREEMPTION()进行任务切换,此函数本质上是一
*个宏,其实最终调用是通过调用函数 portYIELD()来完成任务切换的*/
2287 }
2288 else
2289 {
2290 mtCOVERAGE_TEST_MARKER();
2291 }
2292 }
2293 }
2294 else
2295 {
2296 mtCOVERAGE_TEST_MARKER();
2297 }
2298 }
2299 taskEXIT_CRITICAL();
2300
2301 return xAlreadyYielded;
/*返回变量 xAlreadyYielded,如果为 pdTRUE 的话表示在函数 xTaskResumeAll()中进
*行了任务切换,如果为 pdFALSE 的话表示没有进行任务切换*/
2302 }
2303 /*-----------------------------------------------------------*/
2.5任务延时函数
2.5.1 vTaskDelay()
函数 vTaskDelay()在文件 tasks.c 中有定义,要使用此函数的话宏 INCLUDE_vTaskDelay 必须为 1,函数代码如下:
1325 /*-----------------------------------------------------------*/
1326
1327 #if ( INCLUDE_vTaskDelay == 1 )
1328
1329 void vTaskDelay( const TickType_t xTicksToDelay )
1330 {
1331 BaseType_t xAlreadyYielded = pdFALSE;
1332
1333 /* A delay time of zero just forces a reschedule. */
1334 if( xTicksToDelay > ( TickType_t ) 0U )
/*延时时间由参数 xTicksToDelay 来确定,为要延时的时间节拍数,延时时间肯定要大
*于 0。 否则的话相当于直接调用函数 portYIELD()进行任务切换*/
1335 {
1336 configASSERT( uxSchedulerSuspended == 0 );
1337 vTaskSuspendAll();
/*调用函数 vTaskSuspendAll()挂起任务调度器*/
1338 {
1339 traceTASK_DELAY();
1340
1341 /* A task that is removed from the event list while the
1342 * scheduler is suspended will not get placed in the ready
1343 * list or removed from the blocked list until the scheduler
1344 * is resumed.
1345 *
1346 * This task cannot be in an event list as it is the currently
1347 * executing task. */
1348 prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
/*调 用 函 数 prvAddCurrentTaskToDelayedList() 将 要 延 时 的 任 务 添 加 到 延 时 列 表
*pxDelayedTaskList 或 者 pxOverflowDelayedTaskList() 中 。*/
1349 }
1350 xAlreadyYielded = xTaskResumeAll();
/*调用函数 xTaskResumeAll()恢复任务调度器*/
1351 }
1352 else
1353 {
1354 mtCOVERAGE_TEST_MARKER();
1355 }
1356
1357 /* Force a reschedule if xTaskResumeAll has not already done so, we may
1358 * have put ourselves to sleep. */
1359 if( xAlreadyYielded == pdFALSE )
/*如果函数 xTaskResumeAll()没有进行任务调度的话那么在这里就得进行任务调度*/
1360 {
1361 portYIELD_WITHIN_API();
/*调用函数 portYIELD_WITHIN_API()进行一次任务调度*/
1362 }
1363 else
1364 {
1365 mtCOVERAGE_TEST_MARKER();
1366 }
1367 }
1368
1369 #endif /* INCLUDE_vTaskDelay */
1370 /*-----------------------------------------------------------*/
2.5.2prvAddCurrentTaskToDelayedList()
函数 prvAddCurrentTaskToDelayedList()用于将当前任务添加到等待列表中,函数在文件tasks.c 中有定义
5245 /*-----------------------------------------------------------*/
5246
5247 static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait,
5248 const BaseType_t xCanBlockIndefinitely )
5249 {
5250 TickType_t xTimeToWake;
5251 const TickType_t xConstTickCount = xTickCount;
/*读取进入函数 prvAddCurrentTaskToDelayedList()的时间点并保存在 xConstTickCount 中,
*后面计算任务唤醒时间点的时候要用到。 xTickCount 是时钟节拍计数器,每个滴答定时器中断
*xTickCount 都会加一*/
5252
5253 #if ( INCLUDE_xTaskAbortDelay == 1 )
5254 {
5255 /* About to enter a delayed list, so ensure the ucDelayAborted flag is
5256 * reset to pdFALSE so it can be detected as having been set to pdTRUE
5257 * when the task leaves the Blocked state. */
5258 pxCurrentTCB->ucDelayAborted = pdFALSE;
/*如果使能函数 xTaskAbortDelay()的话复位任务控制块的 ucDelayAborted 字段为
*pdFALSE。*/
5259 }
5260 #endif
5261
5262 /* Remove the task from the ready list before adding it to the blocked list
5263 * as the same list item is used for both lists. */
5264 if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
/*要将当前正在运行的任务添加到延时列表中,肯定要先将当前任务从就绪列表中移除*/
5265 {
5266 /* The current task must be in a ready list, so there is no need to
5267 * check, and the port reset macro can be called directly. */
5268 portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); /*lint !e931 pxCurrentTCB cannot change as it is the calling task. pxCurrentTCB->uxPriority and uxTopReadyPriority cann ot change as called with scheduler suspended or in a critical section. */
/*将当前任务从就绪列表中移除以后还要取消任务在 uxTopReadyPriority 中的就绪标记。
*也就是将 uxTopReadyPriority 中对应的 bit 清零*/
5269 }
5270 else
5271 {
5272 mtCOVERAGE_TEST_MARKER();
5273 }
5274
5275 #if ( INCLUDE_vTaskSuspend == 1 )
5276 {
5277 if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
/*延 时 时 间 为 最 大 值 portMAX_DELAY , 并 且 xCanBlockIndefinitely 不 为
*pdFALSE(xCanBlockIndefinitely 不为 pdFALSE 的话表示允许阻塞任务)的话直接将当前任务添
*加到挂起列表中,任务就不用添加到延时列表中*/
5278 {
5279 /* Add the task to the suspended task list instead of a delayed task
5280 * list to ensure it is not woken by a timing event. It will block
5281 * indefinitely. */
5282 vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
/*将当前任务添加到挂起列表 xSuspendedTaskList 的末尾*/
5283 }
5284 else
5285 {
5286 /* Calculate the time at which the task should be woken if the event
5287 * does not occur. This may overflow but this doesn't matter, the
5288 * kernel will manage it correctly. */
5289 xTimeToWake = xConstTickCount + xTicksToWait;
/*计算任务唤醒时间点,也就是(1)中获取到的进入函数 prvAddCurrentTaskToDelayedList()
*的时间值 xConstTickCount 加上延时时间值 xTicksToWait*/
5290
5291 /* The list item will be inserted in wake time order. */
5292
5293
5294 if( xTimeToWake < xConstTickCount )
5295 {
5296 /* Wake time has overflowed. Place this item in the overflow
5297 * list. */
5298 vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
5299 }
5300 else
5301 {
5302 /* The wake time has not overflowed, so the current block list
5303 * is used. */
5304 vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
5305
5306 /* If the task entering the blocked state was placed at the
5307 * head of the list of blocked tasks then xNextTaskUnblockTime
5308 * needs to be updated too. */
5309 if( xTimeToWake < xNextTaskUnblockTime )
5310 {
5311 xNextTaskUnblockTime = xTimeToWake;
5312 }
5313 else
5314 {
5315 mtCOVERAGE_TEST_MARKER();
5316 }
5317 }
5318 }
5319 }
5320 #else /* INCLUDE_vTaskSuspend */
5321 {
5322 /* Calculate the time at which the task should be woken if the event
5323 * does not occur. This may overflow but this doesn't matter, the kernel
5324 * will manage it correctly. */
5325 xTimeToWake = xConstTickCount + xTicksToWait;
5326
5327 /* The list item will be inserted in wake time order. */
5328 listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
/*将计算到的任务唤醒时间点值 xTimeToWake 写入到任务列表中壮态列表项的相应字
*段中。*/
5329
5330 if( xTimeToWake < xConstTickCount )
/*计算得到的任务唤醒时间点小于 xConstTickCount, 说明发生了溢出。 全局变量
*xTickCount 是 TickType_t 类型的,这是个 32 位的数据类型,因此在用 xTickCount 计算任务唤
*醒时间点xTimeToWake的时候的肯定会出现溢出的现象。FreeRTOS针对此现象专门做了处理,
*在 FreeROTS 中定义了两个延时列表 xDelayedTaskList1 和 xDelayedTaskList2,并且也定义了两
*个指针 pxDelayedTaskList 和 pxOverflowDelayedTaskList 来访问这两个列表,在初始化列表函数
*prvInitialiseTaskLists() 中 指 针 pxDelayedTaskList 指 向 了 列 表 xDelayedTaskList1 , 指 针
*pxOverflowDelayedTaskList 指向了列表 xDelayedTaskList2。 这样发生溢出的话就将任务添加到
*pxOverflowDelayedTaskList 所指向的列表中,如果没有溢出的话就添加到 pxDelayedTaskList 所
*指向的列表中*/
5331 {
5332 /* Wake time has overflowed. Place this item in the overflow list. */
5333 vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
/*如果发生了溢出的话就将当前任务添加到pxOverflowDelayedTaskList所指向的列表中*/
5334 }
5335 else
5336 {
5337 /* The wake time has not overflowed, so the current block list is used. */
5338 vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
/*如果没有发生溢出的话就将当前任务添加到 pxDelayedTaskList 所指向的列表中*/
5339
5340 /* If the task entering the blocked state was placed at the head of the
5341 * list of blocked tasks then xNextTaskUnblockTime needs to be updated
5342 * too. */
5343 if( xTimeToWake < xNextTaskUnblockTime )
/*xNextTaskUnblockTime 是个全局变量,保存着距离下一个要取消阻塞的任务最小时
*间点值。 当 xTimeToWake 小于 xNextTaskUnblockTime 的话说明有个更小的时间点来了*/
5344 {
5345 xNextTaskUnblockTime = xTimeToWake;
/*更新 xNextTaskUnblockTime 为 xTimeToWake*/
5346 }
5347 else
5348 {
5349 mtCOVERAGE_TEST_MARKER();
5350 }
5351 }
5352
5353 /* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
5354 ( void ) xCanBlockIndefinitely;
5355 }
5356 #endif /* INCLUDE_vTaskSuspend */
5357 }
2.5.3函数 vTaskDelayUntil()
函数 vTaskDelayUntil()会阻塞任务,阻塞时间是一个绝对时间, 那些需要按照一定的频率运行的任务可以使用函数 vTaskDelayUntil()。 此函数再文件 tasks.c 中有如下定义:
1240 /*-----------------------------------------------------------*/
1241
1242 #if ( INCLUDE_vTaskDelayUntil == 1 )
1243
1244 void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
1245 const TickType_t xTimeIncrement )
1246 {
1247 TickType_t xTimeToWake;
1248 BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;
1249
1250 configASSERT( pxPreviousWakeTime );
1251 configASSERT( ( xTimeIncrement > 0U ) );
1252 configASSERT( uxSchedulerSuspended == 0 );
1253
1254 vTaskSuspendAll();
/*挂起任务调度器*/
1255 {
1256 /* Minor optimisation. The tick count cannot change in this
1257 * block. */
1258 const TickType_t xConstTickCount = xTickCount;
/*记录进入函数 vTaskDelayUntil()的时间点值,并保存在 xConstTickCount 中*/
1259
1260 /* Generate the tick time at which the task wants to wake. */
1261 xTimeToWake = *pxPreviousWakeTime + xTimeIncrement; (1)
1262
1263 if( xConstTickCount < *pxPreviousWakeTime )
/*根据图可以看出,理论上 xConstTickCount 要大于 pxPreviousWakeTime 的,但
*是也有一种情况会导致 xConstTickCount 小于 pxPreviousWakeTime,那就是 xConstTickCount 溢
*出了!*/
1264 {
1265 /* The tick count has overflowed since this function was
1266 * lasted called. In this case the only time we should ever
1267 * actually delay is if the wake time has also overflowed,
1268 * and the wake time is greater than the tick time. When this
1269 * is the case it is as if neither time had overflowed. */
1270 if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) ) (2)
1271 {
1272 xShouldDelay = pdTRUE;
/*如果满足(2)条件的话就将 pdTRUE 赋值给 xShouldDelay,标记允许延时*/
1273 }
1274 else
1275 {
1276 mtCOVERAGE_TEST_MARKER();
1277 }
1278 }
1279 else
1280 {
1281 /* The tick time has not overflowed. In this case we will
1282 * delay if either the wake time has overflowed, and/or the
1283 * tick time is less than the wake time. */
1284 if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) ) (3)
1285 {
1286 xShouldDelay = pdTRUE;
/*将 pdTRUE 赋值给 xShouldDelay,标记允许延时*/
1287 }
1288 else
1289 {
1290 mtCOVERAGE_TEST_MARKER();
1291 }
1292 }
1293
1294 /* Update the wake time ready for the next call. */
1295 *pxPreviousWakeTime = xTimeToWake;
/*更新 pxPreviousWakeTime 的值,更新为 xTimeToWake,为本函数的下一次执行做准备*/
1296
1297 if( xShouldDelay != pdFALSE )
/*经过前面的判断,允许进行任务延时*/
1298 {
1299 traceTASK_DELAY_UNTIL( xTimeToWake );
1300
1301 /* prvAddCurrentTaskToDelayedList() needs the block time, not
1302 * the time to wake, so subtract the current tick count. */
1303 prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
/*调用函数 prvAddCurrentTaskToDelayedList()进行延时。 函数的第一个参数是设置任务
*的阻塞时间,前面我们已经计算出了任务下一次唤醒时间点了,那么任务还需要阻塞的时间就
*是下一次唤醒时间点 xTimeToWake 减去当前的时间 xConstTickCount。而在函数 vTaskDelay()中
*只是简单的将这参数设置为 xTicksToDelay。*/
1304 }
1305 else
1306 {
1307 mtCOVERAGE_TEST_MARKER();
1308 }
1309 }
1310 xAlreadyYielded = xTaskResumeAll();
/*调用函数 xTaskResumeAll()恢复任务调度器*/
1311
1312 /* Force a reschedule if xTaskResumeAll has not already done so, we may
1313 * have put ourselves to sleep. */
1314 if( xAlreadyYielded == pdFALSE )
1315 {
1316 portYIELD_WITHIN_API();
1317 }
1318 else
1319 {
1320 mtCOVERAGE_TEST_MARKER();
1321 }
1322 }
1323
1324 #endif /* INCLUDE_vTaskDelayUntil */
1325 /*-----------------------------------------------------------*/
参数:
pxPreviousWakeTime: 上一次任务延时结束被唤醒的时间点,任务中第一次调用函数vTaskDelayUntil 的话需要将 pxPreviousWakeTime 初始化进入任务的 while()循环体的时间点值。在以后的运行中函数vTaskDelayUntil()会自动更新 pxPreviousWakeTime。
xTimeIncrement: 任务需要延时的时间节拍数(相对于 pxPreviousWakeTime 本次延时的节
拍数)。
(1)根据延时时间 xTimeIncrement 来计算任务下一次要唤醒的时间点, 并保存在xTimeToWake 中。 可以看出这个延时时间是相对于 pxPreviousWakeTime 的,也就是上一次任务被唤醒的时间点。 pxPreviousWakeTime、 xTimeToWake、 xTimeIncrement 和 xConstTickCount 的关系如图所示。
如图中 (1)为任务主体,也就是任务真正要做的工作, (2)是任务函数中调用vTaskDelayUntil()对任务进行延时, (3)为其他任务在运行。任务的延时时间是 xTimeIncrement,这个延时时间是相对于 pxPreviousWakeTime 的,可以看出任务总的执行时间一定要小于任务的延时时间 xTimeIncrement!也就是说如果使用 vTaskDelayUntil()的话任务相当于任务的执行周期永远都是 xTimeIncrement,而任务一定要在这个时间内执行完成。这样就保证了任务永远按照一定的频率运行了,这个延时值就是绝对延时时间,因此函数 vTaskDelayUntil()也叫做绝对延时函数。
(2)既然 xConstTickCount 都溢出了,那么计算得到的任务唤醒时间点肯定也是要溢出的,并且 xTimeToWake 肯定也是要大于 xConstTickCount 的。 这种情况如图所示:
(3)还有其他两种情况,一:只有 xTimeToWake 溢出,二:都没有溢出。只有 xTimeToWake
溢出的话如图所示:
3.任务调度器
3.1.vTaskStartScheduler()
这个函数的功能就是开启任务调度器的,这个函数在文件 tasks.c
中有定义
1973 /*-----------------------------------------------------------*/
1974
1975 void vTaskStartScheduler( void )
1976 {
1977 BaseType_t xReturn;
1978
1979 /* Add the idle task at the lowest priority. */
/*创建空闲任务,如果使用静态内存的话使用函数 xTaskCreateStatic()来创建空闲任务,
*优先级为 tskIDLE_PRIORITY,宏 tskIDLE_PRIORITY 为 0,也就是说空闲任务的优先级为最
*低。*/
1980 #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
1981 {
1982 StaticTask_t * pxIdleTaskTCBBuffer = NULL;
1983 StackType_t * pxIdleTaskStackBuffer = NULL;
1984 uint32_t ulIdleTaskStackSize;
1985
1986 /* The Idle task is created using user provided RAM - obtain the
1987 * address of the RAM then create the idle task. */
1988 vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
1989 xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
1990 configIDLE_TASK_NAME,
1991 ulIdleTaskStackSize,
1992 ( void * ) NULL, /*lint !e961. The cast is not redundant for all compilers. */
1993 portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
1994 pxIdleTaskStackBuffer,
1995 pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
1996
1997 if( xIdleTaskHandle != NULL )
1998 {
1999 xReturn = pdPASS;
2000 }
2001 else
2002 {
2003 xReturn = pdFAIL;
2004 }
2005 }
2006 #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
2007 {
2008 /* The Idle task is being created using dynamically allocated RAM. */
2009 xReturn = xTaskCreate( prvIdleTask,
2010 configIDLE_TASK_NAME,
2011 configMINIMAL_STACK_SIZE,
2012 ( void * ) NULL,
2013 portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
2014 &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
2015 }
2016 #endif /* configSUPPORT_STATIC_ALLOCATION */
2017
2018 #if ( configUSE_TIMERS == 1 )
2019 {
2020 if( xReturn == pdPASS )
2021 {
2022 xReturn = xTimerCreateTimerTask();
/*如果使用软件定时器的话还需要通过函数 xTimerCreateTimerTask()来创建定时器服务
*任务。定时器服务任务的具体创建过程是在函数 xTimerCreateTimerTask()中完成的*/
2023 }
2024 else
2025 {
2026 mtCOVERAGE_TEST_MARKER();
2027 }
2028 }
2029 #endif /* configUSE_TIMERS */
2030
2031 if( xReturn == pdPASS )
2032 {
2033 /* freertos_tasks_c_additions_init() should only be called if the user
2034 * definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
2035 * the only macro called by the function. */
2036 #ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
2037 {
2038 freertos_tasks_c_additions_init();
2039 }
2040 #endif
2041
2042 /* Interrupts are turned off here, to ensure a tick does not occur
2043 * before or during the call to xPortStartScheduler(). The stacks of
2044 * the created tasks contain a status word with interrupts switched on
2045 * so interrupts will automatically get re-enabled when the first task
2046 * starts to run. */
2047 portDISABLE_INTERRUPTS();
/*关闭中断*/
2048
2049 #if ( configUSE_NEWLIB_REENTRANT == 1 )
2050 {
2051 /* Switch Newlib's _impure_ptr variable to point to the _reent
2052 * structure specific to the task that will run first.
2053 * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
2054 * for additional information. */
2055 _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
2056 }
2057 #endif /* configUSE_NEWLIB_REENTRANT */
2058
2059 xNextTaskUnblockTime = portMAX_DELAY;
2060 xSchedulerRunning = pdTRUE;
/*变量 xSchedulerRunning 设置为 pdTRUE,表示调度器开始运行*/
2061 xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;
2062
2063 /* If configGENERATE_RUN_TIME_STATS is defined then the following
2064 * macro must be defined to configure the timer/counter used to generate
2065 * the run time counter time base. NOTE: If configGENERATE_RUN_TIME_STATS
2066 * is set to 0 and the following line fails to build then ensure you do not
2067 * have portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() defined in your
2068 * FreeRTOSConfig.h file. */
2069 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
/*当宏 configGENERATE_RUN_TIME_STATS 为 1 的时候说明使能时间统计功能,此时
*需要用户实现宏 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS,此宏用来配置一个定时器/计数器*/
2070
2071 traceTASK_SWITCHED_IN();
2072
2073 /* Setting up the timer tick is hardware specific and thus in the
2074 * portable interface. */
2075 if( xPortStartScheduler() != pdFALSE )
/*调用函数 xPortStartScheduler()来初始化跟调度器启动有关的硬件,比如滴答定时器、
*FPU 单元和 PendSV 中断等等*/
2076 {
2077 /* Should not reach here as if the scheduler is running the
2078 * function will not return. */
2079 }
2080 else
2081 {
2082 /* Should only reach here if a task calls xTaskEndScheduler(). */
2083 }
2084 }
2085 else
2086 {
2087 /* This line will only be reached if the kernel could not be started,
2088 * because there was not enough FreeRTOS heap to create the idle task
2089 * or the timer task. */
2090 configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
2091 }
2092
2093 /* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
2094 * meaning xIdleTaskHandle is not used anywhere else. */
2095 ( void ) xIdleTaskHandle;
2096 }
2097 /*-----------------------------------------------------------*/
3.2 xPortStartScheduler()
FreeRTOS 系统时钟是由滴答定时器来提供的,而且任务切换也会用到 PendSV 中断,这些硬件的初始化由函数 xPortStartScheduler()来完成.
3.3 prvEnableVFP()
在函数 xPortStartScheduler()中会通过调用 prvEnableVFP()来使能 FPU,这个函数是汇编形
式的,在文件 port.c 中有定义。
3.4 prvStartFirstTask()
启动第一个任务