开局一张图。一步一步分析就好。

(一)什么是任务?

  在多任务系统中,我们按照功能不同,把整个系统分割成一个个独立的,且无法返回的函数,这个函数我们称为任务;任务包含几个属性:任务堆栈,任务函数、任务控制块、任务优先级;下面主要介绍一下任务控制块,其他都比较容易理解。

(二)什么是任务控制块?

  任务控制块内包含了该任务的全部信息,任务的执行需要通过任务调度器来控制,那么任务调度器怎么“控制”任务实体的呢?就要抓住任务的小辫子---“任务控制块”,系统对任务的全部操作都可以通过任务控制块来实现!它是一种特别的数据结构。

  在任务创建函数xTaskCreat()创建任务的时候就会自动给每个任务分配一个任务控制块。

typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /*任务堆栈栈顶指针*/ #if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*MPU相关设置*/
#endif ListItem_t xStateListItem; /*状态列表项,这是一个内置在TCB控制块中的一个链表节点,通过这个节点,将任务挂到其他链表中
比如就绪列表,阻塞列表,挂起列表等*/

ListItem_t xEventListItem; /*事件列表项,用于引用事件列表中的任务*/
UBaseType_t uxPriority; /*任务优先级*/
StackType_t *pxStack; /*任务堆栈起始地址,是一个栈底*/
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*任务名字*/ #if ( portSTACK_GROWTH > 0 )
StackType_t *pxEndOfStack; /*任务堆栈栈底*/
#endif #if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*临界区嵌套深度*/
#endif #if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*debug的时候用到*/
UBaseType_t uxTaskNumber; /*trace的时候用到*/
#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 )
uint32_t ulRunTimeCounter; /*用来记录任务运行总时间*/
#endif #if ( configUSE_NEWLIB_REENTRANT == 1 )
struct _reent xNewLib_reent; /*定义一个newlib结构体变量*/
#endif #if( configUSE_TASK_NOTIFICATIONS == 1 ) /*任务通知相关变量*/
volatile uint32_t ulNotifiedValue; /*任务通知值*/
volatile uint8_t ucNotifyState; /*任务通知状态*/
#endif /* 用来标记任务是动态创建还是静态创建*/
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated; /*静态创建此变量为pdTURE;动态创建此变量为pdFALSE*/
#endif #if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif } tskTCB;

注:#if 开头的都是条件编译,咱们可以先不用理解。基本结构如下:

  

   指针pxStack指向堆栈的起始位置,任务创建时会分配指定数目的任务堆栈,申请堆栈内存函数返回的指针就被赋给该变量。

   很多刚接触FreeRTOS的人会分不清指针pxTopOfStack和pxStack的区别,这里简单说一下:pxTopOfStack指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack指向的位置是会变化的;pxStack指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。那么为什么需要pxStack变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;如果在堆栈向上增长的系统中,要想确定堆栈是否溢出,还需要另外一个变量pxEndOfStack来辅助诊断是否堆栈溢出。

(三)任务是怎么创建出来的?

  任务有两种创建方式,动态创建静态创建,两者的区别就是: 静态创建时候任务控制块和任务堆栈的内存是由用户自己定义的,任务删除的时候,内存不能自动释放。动态创建,任务堆栈和任务控制块的内存是由系统自动创建的,自动释放的。

  动态创建任务的函数为 xTaskCreate();

BaseType_t xTaskCreate(  TaskFunction_t    pxTaskCode,        //任务函数的名称
const char * const pcName, //任务的名称
const uint16_t usStackDepth, //任务堆栈大小
void * const pvParameters, //任务的形参
UBaseType_t uxPriority, //任务优先级
TaskHandle_t * const pxCreatedTask ) // 用于传回一个任务句柄,创建任务后使用这个句柄引用(控制)任务。本质上是一个空指针。
{
TCB_t *pxNewTCB;
BaseType_t xReturn; #define portSTACK_GROWTH //-1表示满减栈
#if( portSTACK_GROWTH > 0 ){
}
#else{ /* portSTACK_GROWTH<0 代表堆栈向下增长 */
StackType_t *pxStack;
/* 任务栈内存分配,stm32是向下增长的堆栈,获取到的pxStack 是一个栈底的指针*/
pxStack = ( StackType_t *) pvPortMalloc(((( size_t) usStackDepth ) * sizeof( StackType_t)));
if( pxStack != NULL ){
/* 任务控制块内存分配 */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL ){
/* 赋值栈地址 */
pxNewTCB->pxStack = pxStack;
}
else{
/* 释放栈空间 */
vPortFree( pxStack );
}
}
else{
/* 没有分配成功 */
pxNewTCB = NULL;
}
}
#endif /* portSTACK_GROWTH */ if( pxNewTCB != NULL )
{
/* 新建任务初始化 */
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,
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( portSTACK_GROWTH < 0 ){
/* 把栈空间的高地址分配给栈顶 */
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
/* 栈对齐----栈要8字节对齐 */
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));
}
#else /* portSTACK_GROWTH */
{
}
#endif /* portSTACK_GROWTH */ /* 存储任务名称 */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ){
pxNewTCB->pcTaskName[ x ] = pcName[ x ]; if( pcName[ x ] == 0x00 ){
break;
}
else{
mtCOVERAGE_TEST_MARKER();
}
} /* \0补齐字符串 */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
/* 判断任务分配的优先级,是否大于最大值 如果超过最大值,赋值最大值 */
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ){
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else{
mtCOVERAGE_TEST_MARKER();
}
/* 赋值任务优先级到任务控制块 */
pxNewTCB->uxPriority = uxPriority;
/* 任务状态表 事件表初始化 */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
/* 设置任务控制块中的状态列表项的成员变量ower ,是属于PxNewTCB(拥有该结点的内核对象) */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/*更改事件列表项中的成员变量xItemValue的值,目的是列表在排列的时候,是按照优先级由大到小排列 */
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
/*设置任务控制块中事件列表项的成员变量ower,同上*/
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB ); #if( portUSING_MPU_WRAPPERS == 1 ){ }
#else{ /* portUSING_MPU_WRAPPERS */
/* 初始化任务堆栈,之后返回任务栈顶 */
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
}
#endif /* portUSING_MPU_WRAPPERS */ if( ( void * ) pxCreatedTask != NULL ){
/* 任务句柄指向任务控制块 */
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
else{
mtCOVERAGE_TEST_MARKER();
}
}

  prvInitialiseNewTask()函数的形参,出来xTaskCreat()的形参之外,又多出来pxNewTCB和xRegions两个形参;

后面又调用了 pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters)
来初始化任务堆栈。
StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters){
pxTopOfStack--; /* 入栈程序状态寄存器 */
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */ pxTopOfStack--; /* 入栈PC指针 */
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */ pxTopOfStack--; /* 入栈LR链接寄存器 */
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */ pxTopOfStack -= 5; /* 跳过R12, R3, R2 and R1这四个寄存器,不初始化 */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0作为传参入栈 */ pxTopOfStack--; /* 保存EXC_RETURN的值,用于退出SVC或PendSV中断时候,处理器处于什么状态*/
*pxTopOfStack = portINITIAL_EXEC_RETURN; pxTopOfStack -= 8; /* 跳过R11, R10, R9, R8, R7, R6, R5 and R4这8个寄存器,不初始化 */
return pxTopOfStack; /*最终返回栈顶*/

初始化堆栈完成之后堆栈如下图:

层层深入完毕,现在我们返回到xTaskCreat()函数后面,看看  prvAddNewTaskToReadyList( pxNewTCB ); 函数是怎么把任务添加到就绪列表中!

static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{ taskENTER_CRITICAL();
{
uxCurrentNumberOfTasks++;
if( pxCurrentTCB == NULL ) //正在运行的任务块为NULL,没有任务运行;
{
pxCurrentTCB = pxNewTCB; //将新任务控制块赋值给pxCurrentTCB if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) //为1说明正在创建的任务是第一个任务。
{ prvInitialiseTaskLists(); //初始化列表,就绪列表、阻塞列表等等
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{ if( xSchedulerRunning == pdFALSE ) //判断任务调度器是否运行,pdfalse代表没有运行
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;// 将新创建的任务控制块赋值给当前任务控制块
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
} uxTaskNumber++; // 用于任务控制块编号 #if ( configUSE_TRACE_FACILITY == 1 )
{
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif /* configUSE_TRACE_FACILITY */
traceTASK_CREATE( pxNewTCB ); prvAddTaskToReadyList( pxNewTCB ); //将任务添加到就绪列表 portSETUP_TCB( pxNewTCB );
}
taskEXIT_CRITICAL(); if( xSchedulerRunning != pdFALSE ) //如果任务调调度器在运行,新任务优先级比正在运行的优先级高
{ if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION(); //调用此函数完成一次任务切换
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}

  一定要耐心分析,别无他法,加油!不难。

FreeRTOS学习记录--任务创建函数详解的更多相关文章

  1. Python学习记录3-函数参数详解

    参数详解 参数分类 普通参数 默认参数 关键字参数 收集参数 普通参数 定义时直接定义变量名 调用的时候直接把变量或者值放入指定位置 def 函数名 (参数1, 参数2, ....): 函数体 # 调 ...

  2. Python学习之模块进程函数详解

    今天在看<Beginning Linux Programming>中的进程相关部分,讲到Linux几个进程相关的系统函数: system , exec , fork ,wait . Pyt ...

  3. opencv学习笔记——cv::CommandLineParser函数详解

    命令行解析类CommandLineParser 该类的作用主要用于命令行的解析,也就是分解命令行的作用.以前版本没这个类时,如果要运行带参数的.exe,必须在命令行中输入文件路径以及各种参数,并且输入 ...

  4. opencv学习笔记——cv::mean()函数详解

    opencv中封装了一个专门用于求解cv::Mat均值的函数,即cv::mean(&cv::Mat),该函数会得到Mat中各个通道的均值,若要获取指定通道的均值,做进一步解析即可. 具体使用方 ...

  5. 【原创】go语言学习(六)函数详解2

    目录 变量作用域和可见性 匿名函数 闭包 课后练习 变量作用域和可见性 1.全局变量量,在程序整个生命周期有效. var a int = 10 2.局部变量量,分为两种: 1)函数内定义, 2)语句句 ...

  6. 【原创】go语言学习(五)函数详解1

    目录 1.函数介绍 2.多返回值和可变参数 3.defer语句 4.内置函数介绍 1.函数介绍 1.1定义: 有输⼊入.有输出,⽤用来执⾏行行⼀一个指定任务的代码块. func functionnam ...

  7. opencv学习笔记——cv::line函数详解

    void cvLine( CvArr* img, CvPoint pt1, CvPoint pt2, CvScalar color, int thickness=1, int line_type=8, ...

  8. 【转】linux 中fork()函数详解

    在看多线程的时候看到了这个函数,于是学习了下,下面文章写的通俗易懂,于是就开心的看完了,最后还是很愉快的算出了他最后一个问题. linux 中fork()函数详解 一.fork入门知识 一个进程,包括 ...

  9. fork()函数详解

    linux中fork()函数详解(原创!!实例讲解) (转载)    一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程 ...

随机推荐

  1. tensorflow源码解析之common_runtime-direct_session

    目录 核心概念 direct_session direct_session.h direct_session.cc 1. 核心概念 读过之前文章的读者应该还记得,session是一个执行代理.我们把计 ...

  2. ArcGIS提取水系并进行生态敏感性分析

    1.前言 此前已经发表过一篇名为<ENVI提取水系并进行生态敏感性分析>的随笔,这篇是用ArcGIS进行水系提取,与前者的区别是上篇一般是对遥感影像进行处理,准确性较高:这篇是讲在没有遥感 ...

  3. 面试突击32:为什么创建线程池一定要用ThreadPoolExecutor?

    在 Java 语言中,并发编程都是依靠线程池完成的,而线程池的创建方式又有很多,但从大的分类来说,线程池的创建总共分为两大类:手动方式使用 ThreadPoolExecutor 创建线程池和使用 Ex ...

  4. Windows10 1809版本Windows自动更新服务无法禁用问题解决方案

    症状 Windows Update服务已经在服务管理器中禁用,但是莫名奇妙的会被自动设置为手动,并会自动下载补丁.原因 微软加强了系统更新服务的保护措施,导致按照原有的禁用服务方法,能够随时被恢复.解 ...

  5. AQS 详解之共享锁模式

    概括 AQS框架数据结构是一个先进先出的双向队列,当多个线程进行竞争资源时,那些竞争失败的线程会加入到队列中.他向上层提供了很多接口,其中一个是acquireShared获取共享模式的接口.本文将会根 ...

  6. ArcMap连接oracle、oracle配置

    服务器:Oracle 11g 客户端:arcgis desktop 10.4.1.oracle 11g 32位客户端 客户端:arcgis server 10.4.1.oracle 11g 64位客户 ...

  7. 航模电池平衡头接线,1S-6S原理图

    1-4S平衡头接线 S数代表几级锂电池串联,比如3S代表串联了3级,所以总电压为3*3.7V=11.4V.(每一级可能是多个电芯并联) 图源:百度贴吧 图源:5imx论坛 3S电池示例 B6充电器

  8. mq 的缺点 ?

    系统可用性降低 系统引入的外部依赖越多,越容易挂掉,本来你就是 A 系统调用 BCD 三个系统的 接口就好了,人 ABCD 四个系统好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整?MQ ...

  9. Oracle入门基础(十二)一一储存过程及触发器

    1.第一个存储过程 打印Hello World 调用存储过程: 1.exec sayhelloworld(); 2.begin sayhelloworld(); sayhelloworld(); en ...

  10. 哪一个List实现了最快插入?

    LinkedList和ArrayList是另个不同变量列表的实现.ArrayList的优势在于动态的增长数组,非常适合初始时总长度未知的情况下使用.LinkedList的优势在于在中间位置插入和删除操 ...