前言

后面都是已动态内存任务为例来分析。

注意:

  • 由于当前学习是在linux上跑的freertos,对于freertos底层相关接口,从demo工程来看,都是posix标准相关。

  • 鉴于freertos多用于ARM架构,本教程涉及到硬件接口,作者会分两条路线讲解:

    • posix标准接口。
    • cortex m3/4架构相关接口。

参考:

本文默认按堆栈向下生长方式讲解。

4.1 任务控制块

/* 任务控制块 */
typedef struct tskTaskControlBlock
{
volatile StackType_t * pxTopOfStack; /* 指向放在任务堆栈上的最后一项的位置。这必须是TCB结构体的第一个成员。 */ #if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /* MPU设置被定义为端口层的一部分。这必须是TCB结构体的第二个成员。 */
#endif ListItem_t xStateListItem; /* 任务的状态列表项引用的列表表示该任务的状态(就绪、阻塞、挂起)。 */
ListItem_t xEventListItem; /* 用于从事件列表中引用任务 */
UBaseType_t uxPriority; /* 任务优先级 */
StackType_t * pxStack; /* 任务栈其实地址指针 */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* 创建时为任务指定的描述性名称。便于调试。非限定的char类型只允许用于字符串和单个字符。 */ #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t * pxEndOfStack; /* 指向任务栈末 */
#endif #if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /* 自己维护临界嵌套深度,不用在端口层维护。 */
#endif #if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /* 标记当前任务控制块序号,由内核决定,每个任务不同。 */
UBaseType_t uxTaskNumber; /* 标记当前任务序号,但不是有内核决定,而是通过API函数`vTaskSetTaskNumber()`来设置的。 */
#endif #if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /* 基优先级,用于优先级继承时使用 */
UBaseType_t uxMutexesHeld; /* 当前任务获取到的互斥量个数 */
#endif #if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag; /* 任务标签 */
#endif #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ]; /* 本地内存指针数组 */
#endif #if ( configGENERATE_RUN_TIME_STATS == 1 )
configRUN_TIME_COUNTER_TYPE ulRunTimeCounter; /* 存储任务处于运行状态所花费的时间 */
#endif #if ( configUSE_NEWLIB_REENTRANT == 1 ) /* “没有用过” */ struct _reent xNewLib_reent;
#endif #if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; /* 任务通知值数组 */
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; /* 任务通状态数组 */
#endif #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated; /* 标记任务是动态内存创建还是静态内存创建 */
#endif #if ( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted; /* 解除阻塞标记 */
#endif #if ( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno; /* 当前任务的错误码 */
#endif
} tskTCB;

详细说明各成员:

pxTopOfStack

  • 任务栈顶指针,必须放在任务控制块首位,指向任务当前堆栈的栈顶,且总是指向最后一个压栈的项目。
  • 该值在任务切换时才会更新。

xMPUSettings

  • 如果使用MPU,xMPUSettings必须位于结构体的第二项,用于MPU设置。

xStateListItemxEventListItem

  • 状态链表节点和事件链表节点。
  • 这些链表主要被OS调度器使用,用于跟踪、处理任务。
  • 对于链表的学习可以百度搜索下:李柱明 链表

uxPriority

  • 任务优先级,freertos是0为最低优先级。
  • 一般在创建任务时配置,也可以动态修改。对于动态修改,后面任务控制章节会讲述。

pxStack

  • 任务栈底指针,指向任务堆栈的起始位置。
  • 在任务创建时就被赋值了。
  • 栈底指针pxStack被赋值后就不会改变的,而栈顶指针pxTopOfStack是会随着出入栈变化的。
  • 对于向下生长的栈,该值可用于任务栈溢出监测。在任务栈初始化时,会初始化为也给固定值,如0xA5,在切换任务时,检查该任务的栈底的几个值是否是0xA5,如果是,则可粗略判断为任务栈未溢出,如果不是,则可肯定任务栈一定异常。被踩,或溢出。

pcTaskName

  • 任务的描述或名字,任务创建时赋值。
  • 主要用于调试分析。
  • 名字的长度由宏configMAX_TASK_NAME_LEN(位于FreeRTOSConfig.h中)指定,包含字符串结束标志。

pxEndOfStack

  • 指向任务栈的尾部。
  • 该值在堆栈向上生长portSTACK_GROWTH > 0,或者开启记录堆栈高地址configRECORD_STACK_HIGH_ADDRESS == 1时有效。
  • 也是用任务栈溢出检测。

uxCriticalNesting

  • 临界区嵌套深度记录值,初始为0。

uxTCBNumber

  • 标记当前任务控制块序号,由内核决定,每个任务不同。
  • 主要用于调试。

uxTaskNumber

  • 标记当前任务序号,但不是有内核决定,而是通过API函数vTaskSetTaskNumber()来设置的。
  • 主要用于调试。

uxBasePriority

  • 保存任务原来的优先级。
  • 主要用于优先级继承机制。如互斥量。

uxMutexesHeld

  • 当前任务获取到的互斥量个数。
  • 获取到一个互斥量,该值+1;释放一个互斥量,该值-1;为 0 时,优先级恢复基优先级。

pxTaskTag

  • 任务标签。
  • 内核不使用。
  • 类型是任务钩子函数指针,主要供给用户使用。

pvThreadLocalStoragePointers

  • 本地内存指针。
  • 其实就是在自己的任务栈里占用部分内存,并通过接口把这部分内存开放出去,让其它任务也可以使用。
  • 参考:官网

ulRunTimeCounter:

  • 记录任务在运行状态下执行的总时间。
  • 单位:tickle。

ulNotifiedValue

  • 任务通知值数组。

ucNotifyState:

  • 任务通知状态数组。

xNewLib_reent

  • 还没研究这有啥用。

ucStaticallyAllocated

  • 标记任务是动态内存创建还是静态内存创建。
  • 静态标记为pdTURE。
  • 提供给任务回收时使用。

ucDelayAborted

  • 打断延时标记。
  • 解除挂起时被标记为 pdTURE。

iTaskErrno

  • 当前任务的错误码。

4.2 创建任务源码主要内容

主要内容:

  1. 初始化任务控制块。
  2. 初始化任务栈。与主控架构有关。就是把重要数据压栈,主要是伪造CPU寄存器组压栈现场。或者说只是伪造上文保护现场,让下次调用时恢复下文使用。
  3. 把当前任务插入就绪链表。

参考:查看源码附加部分注释

4.3 内存申请

一个任务主要由三部分组成:

  1. 任务主体程序。
  2. 任务控制块。
  3. 任务栈。

任务主体程序一般存在代码区中。

任务控制块和任务栈需要的空间有两种方式申请:

  1. 静态申请:定义的方式,占用的是工程栈,有编译器决定。
  2. 动态申请:malloc的方式,占用的是对应的系统堆空间。

本次讲解的函数是动态内存创建任务。

对于任务控制块和任务栈的空间位置顺序也是有讲究的,建议是按堆栈增长方向顺序,任务控制块在先,任务栈在后。

这样做的目的是为了栈溢出时不会踩到任务控制块:

  • 如果堆栈向上生长,先申请任务控制块空间,再申请任务栈空间。
  • 如果堆栈向下生长,先申请任务栈空间,再申请任务控制块空间。

申请任务控制块空间:

pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); // 申请任务控制块空间

申请任务栈空间:

pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );

需要注意,如果申请失败,已申请部分需要释放空间再退出。

4.4 任务控制块初始化

任务控制块和任务栈都获得了合法空间,即可开始初始化。

初始化任务控制块,按照任务控制块成员进行初始化即可。

主要是调用prvInitialiseNewTask()API来完成任务初始化。

4.4.1 任务栈地址保存

任务栈地址保存到任务控制块:(这个在申请空间时实现)

pxNewTCB->pxStack = pxStack;

4.4.2 栈顶对齐纠正

先获取对齐前的栈顶地址。

再纠正栈顶地址pxTopOfStack,等等初始化任务栈伪造任务上文现场时就从这个栈顶变量pxTopOfStack指向的地址开始。

/* 下面两行用于栈顶地址对齐 */
pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /* 检查栈顶地址堆栈对齐方式是否正确。 */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );

4.4.3 保存任务名称

保存任务名称到任务控制块,长度受限于宏configMAX_TASK_NAME_LEN

保存时遇到0x00结束符结束或受限长度结束,并且会在受限长度末强制加上0x00结束符。

/* 将任务名称存储在TCB中 */
if( pcName != NULL )
{
/* 这个for循环用于逐个字符地保存任务名,直到超出限长或遇到结束符为止。 */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
/* 遇到结束符,保存并结束 */
if( pcName[ x ] == ( char ) 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 字段最后一个字符默认设置为结束符 */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
}
else
{
/* 任务名传入NULL,则全字段设置为0x00 */
pxNewTCB->pcTaskName[ 0 ] = 0x00;
}

4.4.4 任务优先级保存

任务优先级会实现断言式校验,不能大于等于系统配置的优先级限定值configMAX_PRIORITIES

如果优先级超出配置范围,且没有开启断言式校验,便会纠正任务优先级值,因为不纠正会存在越界访问。(就绪表是二级线性表,用数组记录各个优先级就绪链表,优先级会作为数组下标访问对应就绪链表,所以不能让优先级越界。)

/* 优先级校验 */
configASSERT( uxPriority < configMAX_PRIORITIES );
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
/* 若到这里,优先级超范围,会重置为最大优先级,防止越界访问 */
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}

优先级校验正确,纠正后,保存到任务控制块,如果开启了互斥量功能,即是系统当前配置支持了优先级继承机制,为了实现该机制,任务控制块会有两个优先级相关的变量:

  1. pxNewTCB->uxBasePriority:任务基优先级,优先级继承机制使用。在优先级继承状态时,该值用于保存任务原有优先级。
  2. pxNewTCB->uxPriority:任务在用优先级,实时使用。这个就是任务当前状态的优先级,是根据这个优先级插入对应就绪链表进行抢占调度的。
/* 确定最终的基优先级,赋值给TCB */
pxNewTCB->uxPriority = uxPriority;
#if ( configUSE_MUTEXES == 1 )
{
/* 使用了互斥量,则会有优先级继承机制。 */
pxNewTCB->uxBasePriority = uxPriority; /* 优先级继承 */
pxNewTCB->uxMutexesHeld = 0; /* 当前任务占用的互斥量 */
}
#endif /* configUSE_MUTEXES */

4.4.5 任务状态节点

先初始化任务状态节点。后面完成任务初始前,会把当前任务,即任务状态节点插入就绪链表。

需要设置节点归属,这样才能通过状态节点找到任务控制块。

还需要设置任务状态节点值,就是按这个值排序的,参考任务优先级来配置该值。

  • 使用倒叙onfigMAX_PRIORITIES - uxPriority是因为链表排序采用小在前,而任务优先级采用大优先。
/* 初始化任务状态链表节点 */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
/* 设置任务状态链表的当前节点归属 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/* 根据任务优先级设置事件节点序号 */
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );

任务状态节点在系统中被插入到不同链表而呈现不同的任务状态:

  1. 就绪链表。就绪态。(在跑就是运行态)
  2. 延时链表。阻塞态。
  3. 挂起链表。阻塞态或者挂起态。

4.4.6 任务事件节点

初始化任务状态节点,就只是初始化节点而已。还需要设置节点归属,这样才能通过事件节点找到任务控制块。

/* 初始化时间链表节点 */
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
/* 设置事件链表的当前节点归属 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );

事件节点用于把任务记录到各种事件链表中,消息队列阻塞、事件组等等。

4.4.7 任务本地开放内存配置

任务本地开放内存,其实就是在任务栈中取一部分空间出来,通过接口vTaskSetThreadLocalStoragePointer()pvTaskGetThreadLocalStoragePointer()开放给外部使用。

#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
{
/* 初始化本地存储空间 */
memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );
}
#endif

4.4.8 其它值初始化

参考下源码即可:

#if ( portCRITICAL_NESTING_IN_TCB == 1 )
{
/* 临界嵌套记录初始化 */
pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif /* portCRITICAL_NESTING_IN_TCB */ #if ( configUSE_APPLICATION_TASK_TAG == 1 )
{
/* 任务标签初始化 */
pxNewTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */ #if ( configGENERATE_RUN_TIME_STATS == 1 )
{
/* 任务占用CPU总时间值初始化 */
pxNewTCB->ulRunTimeCounter = ( configRUN_TIME_COUNTER_TYPE ) 0;
}
#endif /* configGENERATE_RUN_TIME_STATS */ #if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
/* 初始化任务通知值空间和任务通知状态空间 */
memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );
memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );
}
#endif #if ( INCLUDE_xTaskAbortDelay == 1 )
{
/* 当前任务先标记为没有被打断延迟 */
pxNewTCB->ucDelayAborted = pdFALSE;
}
#endif

4.5 任务栈初始化

任务栈初始化主要有以下内容:

  1. 主要的就是未在上文现场。在调用时能正常恢复出来执行。
  2. 个人习惯配置。如有些系统喜欢在栈前标记特殊的值,用于dump时判断任务栈是否正常。

任务栈初始化主要是伪造上文现场,与主控硬件架构有关,调用pxPortInitialiseStack()来实现,该函数返回初始化后的栈顶地址。

先把整个任务栈初始化为固定的tskSTACK_FILL_BYTE值,方便调试和任务栈溢出和踩栈检查。

( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );

后面读者自选posix或cortex m3其一学习即可。

4.5.1 posix标准任务栈初始化

因为posix标准下的freertos任务实质是线程,通过posix标准接口实现任务切换。

所以任务栈大概内容就是创建线程,初始化线程管理数据块,指定任务栈等等。

把线程管理数据结构Thread_t *thread;固定到栈顶,用于管理实现线程启停从而实现上层任务切换使用:

Thread_t *thread;
thread = (Thread_t *)(pxTopOfStack + 1) - 1;

初始化线程管理数据结构:

/* 保存任务参数,如任务回调函数及其参数等 */
thread->pxCode = pxCode;
thread->pvParams = pvParameters;
thread->xDying = pdFALSE;
/* 创建一个事件,在任务切换时使用 */
thread->ev = event_create();

初始化线程,指定线程栈:

/* 初始化线程属性结构体 */
pthread_attr_init( &xThreadAttributes );
/* 指定线程栈 */
pthread_attr_setstack( &xThreadAttributes, pxEndOfStack, ulStackSize );

按照前面配置,创建线程:

/* 进入临界 */
vPortEnterCritical();
/* 创建线程。posix标准下的freertos模拟器就是使用线程实现task的。 */
iRet = pthread_create( &thread->pthread, &xThreadAttributes, prvWaitForStart, thread );
/* 退出临界 */
vPortExitCritical();

返回当前栈顶地址:

return pxTopOfStack;

4.5.2 cortex m3任务栈现场伪造

前面章节已经了解了cortex m3内核架构进出异常的知识了,所以伪造现场前段按照异常压栈部分伪造,当然,对于系统任务切换来说,异常压栈的那些CPU寄存器组还不完整,需要手动完成其余CPU寄存器组压栈。

前段栈使用:

在伪造现场前,先安排好前面栈的用途,然后再开始伪造。

比如我把当前栈顶的前10个字节初始化为0x55,在调试时就可以方便看到自己的任务栈尾位置;

又比如,像posix标准一样,把前段栈用于数据管理。

伪造现场,顺序不能随意,需要参考cortex m异常时压栈处理:

  • 硬件压栈部分:xPSR、PC、LR、R12、R3、R2、R1、R0
  • 软件压栈部分:R11、R10、R9、R8、R7、R6、R5、R4
StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
TaskFunction_t pxCode,
void * pvParameters )
{
/* 伪造栈现场 */
pxTopOfStack--; /* 添加的偏移量,用于解释MCU在进入/退出中断时使用堆栈的方式 */
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */ pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */ return pxTopOfStack;
}

初始化后的栈情况参考图(图片源自野火):

4.6 新任务插入就绪表处理

初始化任务控制块和任务栈后,便可插入就绪链表,待调度器调度运行。

调用prvAddNewTaskToReadyList()实现插入就绪链表。

4.6.1 就绪表

freertos就绪表是一个二级线性表,由数组+链表组成。

如图:

各级就绪链表都寄存在pxReadyTasksLists数组中,调度器检索就绪任务就是从pxReadyTasksLists数组中,从高优先级开始检索就绪任务。

另外还有一个变量可以辅助快速检索就绪任务,uxTopReadyPriority,就是就绪任务优先级位图表。

当某个优先级下存在任务就绪,这个值对应bit就会值一,开启该功能需要限制优先级最大值。cortex m架构的可以了解下前导零指令。

为啥要使用数组+链表的方式?本人的认为

  • 数组寻址时间复杂度可以达到O(1),但是会浪费空间,但是对于优先级个数,占用的不多,有效控制好最大优先级即可。
  • 而二级使用链表是因为,任务数量不定,想像管理优先级一样管理任务,非常浪费空间,所以链表更加适合。

下面处理都进入临界


4.6.2 就绪表初始化

如果当前新建的任务时第一个,需要初始化就绪表和赋值当前在跑任务全局变量pxCurrentTCB

if( pxCurrentTCB == NULL ) /* 判断创建第一个任务的条件 */
{
/* 把现在需要插入就绪链表的任务赋值给整个全局变量吧。pxCurrentTCB表示当前占用CPU的任务。 */
pxCurrentTCB = pxNewTCB; if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) // 才创建第一个任务
{
/* 初始化任务链表 */
prvInitialiseTaskLists();
}
}

4.6.3 切换在跑任务

新建的任务如果优先级比当前标记任务更高,而且调度器没有启动,可以立即更新该值:

if( pxCurrentTCB != NULL )
{
/* 调度器没有开启 */
if( xSchedulerRunning == pdFALSE )
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
/* 新插入就绪链表的任务优先级大于等于当前占用CPU的任务,切换它 */
pxCurrentTCB = pxNewTCB;
}
}
}

如果调度器已经启动了,那切换在跑任务的处理就应该交给调度器处理,所以先插入就绪表,退出临界再触发任务调度,触发任务调度实现如下:

/* 如果调度器已经开启 */
if( xSchedulerRunning != pdFALSE )
{
/* 新插入就绪链表的任务优先级比当前占用CPU的任务优先级高才会切换。 */
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
/* 触发异常,进行任务切换 */
taskYIELD_IF_USING_PREEMPTION();
}
}

4.6.4 插入就绪表

插入就绪链表:

/*
* Place the task represented by pxTCB into the appropriate ready list for the task. It is inserted at the end of the list.
*/
#define prvAddTaskToReadyList( pxTCB ) \
traceMOVED_TASK_TO_READY_STATE( pxTCB ); \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
listINSERT_END( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB

更新就绪链表最高优先级图位:

/* uxTopReadyPriority holds the priority of the highest priority ready state task. */
#define taskRECORD_READY_PRIORITY( uxPriority ) \
{ \
if( ( uxPriority ) > uxTopReadyPriority ) \
{ \
uxTopReadyPriority = ( uxPriority ); \
} \
} /* taskRECORD_READY_PRIORITY */

插入对应就绪链表尾:

  • 这个函数只是一个简单的插入链表的API,数据结构的基础。但是这里的重点不是这个API,而是这个API的参数。

  • listINSERT_END( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );

    • 就绪链表不是一个循环双向链表,freertos的就绪链表是一个二级线性表。由数组+链表组成。
    • 由一个数组管理各级就绪链表。
/* 这只是一个数据结构-链表相关的API */
#define listINSERT_END( pxList, pxNewListItem ) \
{ \
ListItem_t * const pxIndex = ( pxList )->pxIndex; \
\
/* Only effective when configASSERT() is also defined, these tests may catch \
* the list data structures being overwritten in memory. They will not catch \
* data errors caused by incorrect configuration or use of FreeRTOS. */ \
listTEST_LIST_INTEGRITY( ( pxList ) ); \
listTEST_LIST_ITEM_INTEGRITY( ( pxNewListItem ) ); \
\
/* Insert a new list item into ( pxList ), but rather than sort the list, \
* makes the new list item the last item to be removed by a call to \
* listGET_OWNER_OF_NEXT_ENTRY(). */ \
( pxNewListItem )->pxNext = pxIndex; \
( pxNewListItem )->pxPrevious = pxIndex->pxPrevious; \
\
pxIndex->pxPrevious->pxNext = ( pxNewListItem ); \
pxIndex->pxPrevious = ( pxNewListItem ); \
\
/* Remember which list the item is in. */ \
( pxNewListItem )->pxContainer = ( pxList ); \
\
( ( pxList )->uxNumberOfItems )++; \
}

4.7 删除任务源码

主要是释放资源。

如果是删除自己的话,就插入到结束链表xTasksWaitingTermination

  • 因为任务调度时需要上下文切换,所以为了保证调度器能顺利切换到下一个任务,便把释放资源,删除任务的内容交给空闲任务处理。

如果不是删除本身,立即就删除,无需经过空闲任务处理。

处理需要进入临界处理。

4.7.1 相关变量

uxDeletedTasksWaitingCleanUp:这个值表示当前有多少人任务需要释放。空闲任务会检查这个值。

xTasksWaitingTermination:结束链表。空闲任务调用 prvCheckTasksWaitingTermination()函数来检查该链表并释放资源。

4.7.2 解除任务所有状态

通过任务句柄获取任务控制块:

/* 获取任务控制块。若传入任务句柄为空,则返回当前运行的任务的任务控制块 */
pxTCB = prvGetTCBFromHandle( xTaskToDelete );

解除任务所有状态,即是从相关状态链表中移除当前任务:

/* 把任务从状态链表(就绪链表、延时链表这些)中移除。 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}

解除事件阻塞:

/* 如果任务在等待某个事件,也把任务从该事件链表中移除。 */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}

4.7.3 删除本身

传入任务句柄为NULL,表示删除本身,但是任务调度时需要上下文切换,所以为了保证调度器能顺利切换到下一个任务,便把释放资源,删除任务的内容交给空闲任务处理。

先把当前任务插入到结束链表xTasksWaitingTermination,更新uxDeletedTasksWaitingCleanUp,让空闲任务知道有多少个已删除的任务需要进行内存释放:

/* 要是删除自己的话 */
if( pxTCB == pxCurrentTCB )
{
/* 删除自己任务函数不能在任务本身内完成,因为需要上下文切换到另一个任务。
所以需要将任务放在结束列表中(xTasksWaitingTermination);
空闲任务会检查结束列表并在空闲任务中释放删除任务的控制块和已删除任务的堆栈内存。 */
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) ); /* 增加 uxDeletedTasksWaitingCleanUp 变量的值,
该变量用于记录有多少个任务需要释放内存,以便空闲任务知道有多少个已删除的任务需要进行内存释放。 */
++uxDeletedTasksWaitingCleanUp; /* 删除任务钩子函数 */
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
}

4.7.4 删除其它任务

删除的任务非当前在跑任务。可以在这里就做删除处理,释放资源。

当前有效任务统计uxCurrentNumberOfTasks减一,还要重置下一个预期的解锁时间,以防它被引用被删除的任务:

  • prvResetNextTaskUnblockTime()需要在临界内处理,因为内部涉及到延时机制组件的处理,如延时链表pxDelayedTaskList、未来最近唤醒时间变量xNextTaskUnblockTime的处理,这些变量在系统节拍中断回调中用到。
taskENTER_CRITICAL();
if( pxTCB != pxCurrentTCB )
{
/* 当前任务数量减一 */
--uxCurrentNumberOfTasks; /* 重置下一个预期的解锁时间,以防它被引用被删除的任务。 */
prvResetNextTaskUnblockTime();
}
taskEXIT_CRITICAL();

然后调用prvDeleteTCB()释放资源

if( pxTCB != pxCurrentTCB )
{
/* 释放资源 */
prvDeleteTCB( pxTCB );
}

4.7.5 触发任务调度

如果调度器没有关闭,且删除了本身,那需要触发任务调度,切换到其它有效任务:

/* 如果调度器没有关闭 */
if( xSchedulerRunning != pdFALSE )
{
if( pxTCB == pxCurrentTCB )
{
/* 自删除要触发异常,进行任务调度 */
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
}

4.7.6 空闲任务释放被删除任务资源

在空闲任务中调用prvCheckTasksWaitingTermination()来处理在结束链表xTasksWaitingTermination中的任务。

需要注意的是,在系统中,需要留点CPU时间给空闲任务,要不然删除本身的任务资源久久得不到释放。

static void prvCheckTasksWaitingTermination( void )
{
#if ( INCLUDE_vTaskDelete == 1 )
{
TCB_t * pxTCB; /* 一直删除到没有删除任务为止 */
while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )
{
/* 进入临界 */
taskENTER_CRITICAL();
{
/* 检查结束列表中的任务 */
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) );
/* 将任务从状态列表中删除 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
--uxCurrentNumberOfTasks;
--uxDeletedTasksWaitingCleanUp;
}
/* 退出临界 */
taskEXIT_CRITICAL();
/* 删除任务控制块与堆栈 */
prvDeleteTCB( pxTCB );
}
}
#endif /* INCLUDE_vTaskDelete */
}

4.7.7 释放任务空间函数prvDeleteTCB()

不管在哪里释放资源,最终都是调用prvDeleteTCB()API来实现。

释放资源主要是任务控制块空间和任务栈空间,前期需要先判断是否是动态分配,动态分配才能动态释放。

先了解几个参数或宏:

  • configSUPPORT_DYNAMIC_ALLOCATION:动态分配内存宏

    • 定义为 1 :在创建 FreeRTOS的内核对象时候 所需要的 RAM 就会从 FreeRTOS 的堆中动态的获取内存。
    • 定义为 0:需要用户自行提供。
    • 默认为1。
  • configSUPPORT_STATIC_ALLOCATION:静态分配内存宏

    • 定义为1:允许静态创建任务。
    • 定义为0:不允许静态创建任务。
  • pxTCB->ucStaticallyAllocated:任务分配内存记录

    • tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB:动态分配任务控制块和任务栈。
    • tskSTATICALLY_ALLOCATED_STACK_ONLY:只是静态分配了任务栈。任务控制块是动态分配的。
    • tskSTATICALLY_ALLOCATED_STACK_AND_TCB:静态分配任务控制块和任务栈。

根据上述参数可以了解到当前任务的任务栈和任务控制块是如何分配的,把动态分配的动态释放即可。

static void prvDeleteTCB( TCB_t * pxTCB )
{
/* 这个调用特别需要TriCore端口。它必须位于vPortFree()调用的上方。这个调用也被那些想要静态分配和清理RAM的端口/演示程序所使用。 */
portCLEAN_UP_TCB( pxTCB );
#if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) && ( portUSING_MPU_WRAPPERS == 0 ) )
{
/* 释放动态分配的任务控制块和任务栈空间 */
vPortFreeStack( pxTCB->pxStack );
vPortFree( pxTCB );
}
#elif ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
/* 开启了静态分配功能,就需要检查任务控制块和任务栈空间是静态还是动态分配的 */
if( pxTCB->ucStaticallyAllocated == tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB )
{
/* 释放动态分配的任务控制块和任务栈空间 */
vPortFreeStack( pxTCB->pxStack );
vPortFree( pxTCB );
}
else if( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_ONLY )
{
/* 只有任务栈是静态分配的,那就只释放TCB的内存 */
vPortFree( pxTCB );
}
else
{
/* 堆栈和TCB都不是动态分配的,因此不需要释放任何东西 */
configASSERT( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_AND_TCB );
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}

附件

xTaskCreate():创建任务源码

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
{
TCB_t * pxNewTCB;
BaseType_t xReturn; /* 根据堆栈生长方式不同,申请任务控制块和任务栈的顺序不同,保证任务栈溢出不会踩到任务控制块。*/
#if ( portSTACK_GROWTH > 0 ) // 堆栈向上生长
{ pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); // 申请任务控制块空间 if( pxNewTCB != NULL )
{
/* 继续申请任务堆栈空间 */
pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); if( pxNewTCB->pxStack == NULL )
{
/* 无法分配堆栈。删除已分配的TCB */
vPortFree( pxNewTCB );
pxNewTCB = NULL;
}
}
}
#else /* portSTACK_GROWTH */ // 堆栈向下生长
{
StackType_t * pxStack; /* 先申请任务栈空间 */
pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); if( pxStack != NULL )
{
/* 申请任务控制块空间 */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); if( pxNewTCB != NULL )
{
/* 保存任务栈地址到任务控制块 */
pxNewTCB->pxStack = pxStack;
}
else
{
/* The stack cannot be used as the TCB was not created. Free
* it again. */
vPortFreeStack( pxStack );
}
}
else
{
pxNewTCB = NULL;
}
}
#endif /* portSTACK_GROWTH */ if( pxNewTCB != NULL )
{
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
{
/* Tasks can be created statically or dynamically, so note this
* task was created dynamically in case it is later deleted. */
pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB; // 标记任务创建的方式
}
#endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */ /* 初始化任务栈 */
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
/* 把当前任务插入就绪链表 */
prvAddNewTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
}
else
{
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
} return xReturn;
}

prvInitialiseNewTask():任务初始化函数

static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
TCB_t * pxNewTCB,
const MemoryRegion_t * const xRegions )
{
StackType_t * pxTopOfStack;
UBaseType_t x; #if ( portUSING_MPU_WRAPPERS == 1 ) // 不使用,略
/* Should the task be created in privileged mode? */
BaseType_t xRunPrivileged; if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
{
xRunPrivileged = pdTRUE;
}
else
{
xRunPrivileged = pdFALSE;
}
uxPriority &= ~portPRIVILEGE_BIT;
#endif /* portUSING_MPU_WRAPPERS == 1 */ #if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
{
/* 意思是把整个任务栈初始化为固定的tskSTACK_FILL_BYTE值。这操作主要用于调试和任务栈溢出检查。 */
( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
}
#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */ #if ( portSTACK_GROWTH < 0 ) // 堆栈向下生长
{
/* 下面两行用于栈顶地址对齐 */
pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /* 检查栈顶地址堆栈对齐方式是否正确。 */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) ); #if ( configRECORD_STACK_HIGH_ADDRESS == 1 )
{
/* 记录栈尾地址 */
pxNewTCB->pxEndOfStack = pxTopOfStack;
}
#endif /* configRECORD_STACK_HIGH_ADDRESS */
}
#else /* portSTACK_GROWTH */ // 堆栈向上生长,参考向下分析即可。略
{
pxTopOfStack = pxNewTCB->pxStack; /* Check the alignment of the stack buffer is correct. */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) ); /* The other extreme of the stack space is required if stack checking is
* performed. */
pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
}
#endif /* portSTACK_GROWTH */ /* 将任务名称存储在TCB中 */
if( pcName != NULL )
{
/* 这个for循环用于逐个字符地保存任务名,直到超出限长或遇到结束符为止。 */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
/* 遇到结束符,保存并结束 */
if( pcName[ x ] == ( char ) 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 字段最后一个字符默认设置为结束符 */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
}
else
{
/* 任务名传入NULL,则全字段设置为0x00 */
pxNewTCB->pcTaskName[ 0 ] = 0x00;
} /* This is used as an array index so must ensure it's not too large. */
/* 优先级校验 */
configASSERT( uxPriority < configMAX_PRIORITIES ); if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
/* 若到这里,优先级超范围,会重置为最大优先级 */
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
/* 调试用的测试回调函数 */
mtCOVERAGE_TEST_MARKER();
} /* 确定最终的基优先级,赋值给TCB */
pxNewTCB->uxPriority = uxPriority;
#if ( configUSE_MUTEXES == 1 )
{
/* 使用了互斥量,则会有优先级继承机制。 */
pxNewTCB->uxBasePriority = uxPriority; /* 优先级继承 */
pxNewTCB->uxMutexesHeld = 0; /* 当前任务占用的互斥量 */
}
#endif /* configUSE_MUTEXES */ vListInitialiseItem( &( pxNewTCB->xStateListItem ) ); /* 初始化任务状态链表节点 */
vListInitialiseItem( &( pxNewTCB->xEventListItem ) ); /* 初始化时间链表节点 */ /* 设置任务状态链表的当前节点归属 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB ); /* 根据任务优先级设置事件节点序号 */
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
/* 设置事件链表的当前节点归属 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB ); #if ( portCRITICAL_NESTING_IN_TCB == 1 )
{
/* 临界嵌套记录初始化 */
pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif /* portCRITICAL_NESTING_IN_TCB */ #if ( configUSE_APPLICATION_TASK_TAG == 1 )
{
/* 任务标签初始化 */
pxNewTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */ #if ( configGENERATE_RUN_TIME_STATS == 1 )
{
/* 任务占用CPU总时间值初始化 */
pxNewTCB->ulRunTimeCounter = ( configRUN_TIME_COUNTER_TYPE ) 0;
}
#endif /* configGENERATE_RUN_TIME_STATS */ #if ( portUSING_MPU_WRAPPERS == 1 ) // 略
{
vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
}
#else
{
/* Avoid compiler warning about unreferenced parameter. */
( void ) xRegions;
}
#endif #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
{
/* 初始化本地存储空间 */
memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );
}
#endif #if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
/* 初始化任务通知值空间和任务通知状态空间 */
memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );
memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );
}
#endif #if ( configUSE_NEWLIB_REENTRANT == 1 ) // 略
{
/* Initialise this task's Newlib reent structure.
* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
* for additional information. */
_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
}
#endif #if ( INCLUDE_xTaskAbortDelay == 1 )
{
/* 当前任务先标记为没有被打断延迟 */
pxNewTCB->ucDelayAborted = pdFALSE;
}
#endif #if ( portUSING_MPU_WRAPPERS == 1 ) // 略
{
/* If the port has capability to detect stack overflow,
* pass the stack end address to the stack initialization
* function as well. */
#if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
{
#if ( portSTACK_GROWTH < 0 )
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );
}
#else /* portSTACK_GROWTH */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );
}
#endif /* portSTACK_GROWTH */
}
#else /* portHAS_STACK_OVERFLOW_CHECKING */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
}
#endif /* portHAS_STACK_OVERFLOW_CHECKING */
}
#else /* portUSING_MPU_WRAPPERS */
{
#if ( portHAS_STACK_OVERFLOW_CHECKING == 1 ) // 打开栈溢监测出功能
{
#if ( portSTACK_GROWTH < 0 ) // 堆栈向下生长
{
/* 初始化任务栈:伪造CPU异常上文保护现场。与主控硬件架构有关 */
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
}
#else /* portSTACK_GROWTH */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
}
#endif /* portSTACK_GROWTH */
}
#else /* portHAS_STACK_OVERFLOW_CHECKING */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
}
#endif /* portHAS_STACK_OVERFLOW_CHECKING */
}
#endif /* portUSING_MPU_WRAPPERS */ if( pxCreatedTask != NULL )
{
/* 让任务句柄指向任务控制块 */
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}

pxPortInitialiseStack():POSIX标准任务栈初始化函数

portSTACK_TYPE *pxPortInitialiseStack( portSTACK_TYPE *pxTopOfStack,
portSTACK_TYPE *pxEndOfStack,
pdTASK_CODE pxCode, void *pvParameters )
{
Thread_t *thread;
pthread_attr_t xThreadAttributes;
size_t ulStackSize;
int iRet; /* 配置整个系统中,在某个线程只执行一次prvSetupSignalsAndSchedulerPolicy() */
(void)pthread_once( &hSigSetupThread, prvSetupSignalsAndSchedulerPolicy ); /* 将额外的线程数据存储在堆栈的开头 */
thread = (Thread_t *)(pxTopOfStack + 1) - 1; // 把栈顶指针,赋值给线程管理结构体指针。意思是把线程管理结构体的数据在任务栈初始栈顶上固定使用。
pxTopOfStack = (portSTACK_TYPE *)thread - 1; // 重新赋值栈顶指针。
ulStackSize = (pxTopOfStack + 1 - pxEndOfStack) * sizeof(*pxTopOfStack); // 计算剩下的任务栈大小,在后面配置为线程栈。 /* 保存任务参数,如任务回调函数及其参数等 */
thread->pxCode = pxCode;
thread->pvParams = pvParameters;
thread->xDying = pdFALSE; /* 初始化线程属性结构体 */
pthread_attr_init( &xThreadAttributes );
/* 指定线程栈 */
pthread_attr_setstack( &xThreadAttributes, pxEndOfStack, ulStackSize ); /* 创建一个事件,在任务切换时使用 */
thread->ev = event_create(); /* 进入临界 */
vPortEnterCritical(); /* 创建线程。posix标准下的freertos模拟器就是使用线程实现task的。 */
iRet = pthread_create( &thread->pthread, &xThreadAttributes,
prvWaitForStart, thread );
if ( iRet )
{
prvFatalError( "pthread_create", iRet );
} /* 退出临界 */
vPortExitCritical(); return pxTopOfStack;
}

pxPortInitialiseStack():cortex m3/m4任务栈现场伪造

StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
TaskFunction_t pxCode,
void * pvParameters )
{
/* 伪造栈现场 */
pxTopOfStack--; /* 添加的偏移量,用于解释MCU在进入/退出中断时使用堆栈的方式 */
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */ pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */ return pxTopOfStack;
}

prvAddNewTaskToReadyList():插入任务就绪链表函数

static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
{
/* Ensure interrupts don't access the task lists while the lists are being
* updated. */
/* 进入临界 */
taskENTER_CRITICAL();
{
uxCurrentNumberOfTasks++; // 全局变量,用于任务计数。 if( pxCurrentTCB == NULL )
{
/* There are no other tasks, or all the other tasks are in
* the suspended state - make this the current task. */
/* 把现在需要插入就绪链表的任务赋值给整个全局变量吧。pxCurrentTCB表示当前占用CPU的任务。 */
pxCurrentTCB = pxNewTCB; if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) // 才创建第一个任务
{
/* This is the first task to be created so do the preliminary
* initialisation required. We will not recover if this call
* fails, but we will report the failure. */
/* 初始化任务链表 */
prvInitialiseTaskLists();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* If the scheduler is not already running, make this task the
* current task if it is the highest priority task to be created
* so far. */
/* 调度器没有开启 */
if( xSchedulerRunning == pdFALSE )
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
/* 新插入就绪链表的任务优先级大于等于当前占用CPU的任务,切换它 */
pxCurrentTCB = pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
} uxTaskNumber++; #if ( configUSE_TRACE_FACILITY == 1 )
{
/* Add a counter into the TCB for tracing only. */
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif /* configUSE_TRACE_FACILITY */
traceTASK_CREATE( pxNewTCB ); /* 插入就绪链表 */
prvAddTaskToReadyList( pxNewTCB ); portSETUP_TCB( pxNewTCB );
}
/* 退出临界 */
taskEXIT_CRITICAL(); /* 如果调度器已经开启 */
if( xSchedulerRunning != pdFALSE )
{
/* If the created task is of a higher priority than the current task
* then it should run now. */
/* 新插入就绪链表的任务优先级比当前占用CPU的任务优先级高才会切换。 */
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
/* 触发异常,进行任务切换 */
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}

vTaskDelete():删除任务源码

void vTaskDelete( TaskHandle_t xTaskToDelete )
{
TCB_t * pxTCB; /* 进入临界 */
taskENTER_CRITICAL();
{
/* 获取任务控制块。若传入任务句柄为空,则返回当前运行的任务的任务控制块 */
pxTCB = prvGetTCBFromHandle( xTaskToDelete ); /* 把任务从状态链表(就绪链表、延时链表这些)中移除。 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
} /* 如果任务在等待某个事件,也把任务从该事件链表中移除。 */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
} uxTaskNumber++; /* 要是删除自己的话 */
if( pxTCB == pxCurrentTCB )
{
/* 删除自己任务函数不能在任务本身内完成,因为需要上下文切换到另一个任务。
所以需要将任务放在结束列表中(xTasksWaitingTermination);
空闲任务会检查结束列表并在空闲任务中释放删除任务的控制块和已删除任务的堆栈内存。 */
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) ); /* 增加 uxDeletedTasksWaitingCleanUp 变量的值,
该变量用于记录有多少个任务需要释放内存,以便空闲任务知道有多少个已删除的任务需要进行内存释放。 */
++uxDeletedTasksWaitingCleanUp; traceTASK_DELETE( pxTCB ); /* 删除任务钩子函数 */
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
}
else
{
/* 当前任务数量减一 */
--uxCurrentNumberOfTasks;
traceTASK_DELETE( pxTCB ); /* 重置下一个预期的解锁时间,以防它被引用被删除的任务。 */
prvResetNextTaskUnblockTime();
}
}
taskEXIT_CRITICAL(); /* 如果不是自删除,则直接删除任务控制块 */
if( pxTCB != pxCurrentTCB )
{
prvDeleteTCB( pxTCB );
} /* 如果调度器没有关闭 */
if( xSchedulerRunning != pdFALSE )
{
if( pxTCB == pxCurrentTCB )
{
/* 自删除要触发异常,进行任务调度 */
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}

prvCheckTasksWaitingTermination():空闲任务检索结束链表释放资源

static void prvCheckTasksWaitingTermination( void )
{
#if ( INCLUDE_vTaskDelete == 1 )
{
TCB_t * pxTCB; /* 一直删除到没有删除任务为止 */
while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )
{
/* 进入临界 */
taskENTER_CRITICAL();
{
/* 检查结束列表中的任务 */
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) );
/* 将任务从状态列表中删除 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
--uxCurrentNumberOfTasks;
--uxDeletedTasksWaitingCleanUp;
}
/* 退出临界 */
taskEXIT_CRITICAL();
/* 删除任务控制块与堆栈 */
prvDeleteTCB( pxTCB );
}
}
#endif /* INCLUDE_vTaskDelete */
}

prvDeleteTCB():删除任务控制块和任务堆栈

static void prvDeleteTCB( TCB_t * pxTCB )
{
/* 这个调用特别需要TriCore端口。它必须位于vPortFree()调用的上方。这个调用也被那些想要静态分配和清理RAM的端口/演示程序所使用。 */
portCLEAN_UP_TCB( pxTCB ); #if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* 没有用过,还不晓得咋用 */
_reclaim_reent( &( pxTCB->xNewLib_reent ) );
}
#endif /* configUSE_NEWLIB_REENTRANT */ #if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) && ( portUSING_MPU_WRAPPERS == 0 ) )
{
/* 释放动态分配的任务控制块和任务栈空间 */
vPortFreeStack( pxTCB->pxStack );
vPortFree( pxTCB );
}
#elif ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
/* 开启了静态分配功能,就需要检查任务控制块和任务栈空间是静态还是动态分配的 */
if( pxTCB->ucStaticallyAllocated == tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB )
{
/* 释放动态分配的任务控制块和任务栈空间 */
vPortFreeStack( pxTCB->pxStack );
vPortFree( pxTCB );
}
else if( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_ONLY )
{
/* 只有堆栈是静态分配的,那就只释放TCB的内存 */
vPortFree( pxTCB );
}
else
{
/* 堆栈和TCB都不是动态分配的,因此不需要释放任何东西 */
configASSERT( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_AND_TCB );
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}

【freertos】004-任务创建与删除及其实现细节的更多相关文章

  1. FreeRTOS-01移植及任务创建和删除

    根据正点原子FreeRTOS视频整理 单片机:STM32F207VC FreeRTOS源码版本:v10.0.1 任务创建和删除API函数: 工程列表: 1. main.c /**/ #include ...

  2. linux命令 - ln - 创建和删除软、硬链接

    linux命令 - ln - 创建和删除软.硬链接 在Linux系统中,内核为每一个新创建的文件分配一个Inode(索引结点),每个文件都有一个惟一的inode号.文件属性保存在索引结点里,在访问文件 ...

  3. 各种隐藏 WebShell、创建、删除畸形目录、特殊文件名、黑帽SEO作弊(转自核大大)

    其实这个问题,经常有朋友问我,我也都帮大家解决了…… 但是现在这些现象越来越严重,而且手法毒辣.隐蔽.变态,清除了又来了,删掉了又恢复了,最后直接找不到文件了,但是访问网站还在,急的各大管理员.站长抓 ...

  4. DOM创建和删除节点、HTML DOM常用对象[转]

    创建和删除节点:——核心DOM   1. 创建单个元素节点:3步:      1. 创建空元素节点对象:         var elem=document.createElement("标 ...

  5. MySQL索引的创建、删除和查看

    MySQL索引的创建.删除和查看 此文转自http://blogold.chinaunix.net/u3/93470/showart_2001536.html 1.索引作用 在索引列上,除了上面提到的 ...

  6. linux下创建和删除软、硬链接

    linux下创建和删除软.硬链接 在Linux系统中,内核为每一个新创建的文件分配一个Inode(索引结点),每个文件都有一个惟一的inode号.文件属性保存在索引结点里,在访问文件时,索引结点被复制 ...

  7. git创建和删除远程分支

    问题描述:           使用git创建和删除远程分支 问题解决:              (1)git创建本地分支 注:            如上所示,使用命令 git branch -a ...

  8. oracl使用DataBase Configuration Assistant创建、删除数据库

    原文:oracl使用DataBase Configuration Assistant创建.删除数据库 可以使用DataBase Configuration Assistant来创建一个心得数据库.Da ...

  9. Java文件File操作一:文件的创建和删除

    一.简述 File 文件类,主要对文件进行相关操作.常用的File操作有:文件(夹)的创建.文件(夹)的删除,文件的读入和下载(复制)等: 二.文件(夹)的创建和删除 1.创建过程 实例: //cre ...

随机推荐

  1. 五、MyBatis缓存初体验

    缓存就是内存中的数据,常常来自对数据库查询结果的保存,使用缓存, 我们可以避免频繁的与数据库进行交互, 进而提高响应速度. 一级缓存初体验(session,默认打开) 同一查询执行两次以上:selec ...

  2. VMware vSphere,ESXi和vCenter的关系和区别

    VMware Inc.是一家软件公司.它开发了很多产品,尤其是各种云解决方案 .他的云解决方案包括云产品,数据中心产品和桌面产品等. vSphere是在数据中心产品下的一套软件.vSphere类似微软 ...

  3. OC之判断数组最大最小值

    超简单的筛选方法 array为你筛选的数组 NSNumber * max = [Array valueForKeyPath:@"@max.floatValue"]; NSNumbe ...

  4. 云原生 PostgreSQL 集群 - PGO:5分钟快速上手

    前提条件 请确保您的主机上安装了以下实用程序: kubectl git 安装 第 1 步:下载示例 首先,转到 GitHub 并 fork Postgres Operator 示例存储库: https ...

  5. jemter参数化是如何取值的?(数据分配)

    参数化文件数据 脚本设置 ${__threadNum}是线程号,${n}是取值 测试结果 第一次 线程1取值:1,4,7,10,12 线程2取值:2,3,6,11,16 线程3取值:5,9,15,17 ...

  6. BI工具是什么,有什么用,怎么用?

    BI工具是什么,有什么用,怎么用?这应该是很多刚刚接触BI工具的读者最开始想了解的问题,经常在看到数据可视化.数据分析.数据仓库和大数据等词汇时会有的摸不着头脑,本次小编就围绕BI工具是什么,有什么用 ...

  7. jprofiler 查看程序内存泄露

    在最近的工作中,通过JProfiler解决了一个内存泄漏的问题,现将检测的步骤和一些分析记录下来,已备今后遇到相似问题时可以作为参考. 运行环境: Tomcat6,jdk6,JProfiler8 内存 ...

  8. mataplotlib篇(开篇)

    今日内容概要 matplotlib画各种图形 数据操作补充 数据清洗 网络爬虫 今日内容详细 matplotlib画各种图形 # 首先导入模块 import numpy as np import pa ...

  9. Python 中的深浅拷贝

    Python 中的深浅拷贝 参考文献:https://blog.csdn.net/lnotime/article/details/81194633 参考文献:https://blog.csdn.net ...

  10. 用Assert(断言)封装异常,让代码更优雅(附项目源码)

    有关Assert断言大家并不陌生,我们在做单元测试的时候,看业务事务复合预期,我们可以通过断言来校验,断言常用的方法如下: public class Assert { /** * 结果 = 预期 则正 ...