FreeRTOS Task

1.多任务系统

1.FreeRTOS 是一个抢占式的实时多任务系统, 那么其任务调度器也是抢占式的,运行过程如下图所示:


图1:多任务抢占系统

1.1任务(Task)的特性

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

1.2任务状态

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

1.3任务状态迁移

图2:任务状态迁移图

图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
溢出的话如图所示:
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()

启动第一个任务

4.任务切换

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351