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

(一)什么是任务?

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

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

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

  在任务创建函数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. 跨平台书签同步-Xmarks

    原文链接 Xmarks简介 Xmarks 是一款浏览器书签同步工具,它可以实现不同设备,不同浏览器之间的书签同步,并且是免费的. 有人会说,我们为什么需要一款专门的书签同步工具呢?Safari 自带的 ...

  2. 一比一还原axios源码(三)—— 错误处理

    前面的章节我们已经可以正确的处理正确的请求,并且通过处理header.body,以及加入了promise,让我们的代码更像axios了.这一章我们一起来处理ajax请求中的错误. 一.错误处理 首先我 ...

  3. LGP3346题解

    广义 SAM 比较简单的题/fad 题意:树上所有路径一共能够组成多少个本质不同子串? 并且数据保证最多只有20个叶子节点. 我们先来考虑一下一种特殊情况: 对于路径 \([u,v]\),\(u\) ...

  4. java 实现装饰器设计模式

    package com.gylhaut.base; /** * 装饰器 * 类与类之间的关系 * 1.依赖:形参(局部变量) * 2.关联:属性 * 聚合 属性 整体和部分 不一致的生命周期 人和手 ...

  5. Nacos+OpenFegin正确调用服务的姿势!

    Nacos 支持两种 HTTP 服务请求,一个是 REST Template,另一个是 Feign Client.之前的文章咱们介绍过 Rest Template 的调用方式,主要是通过 Ribbon ...

  6. 【SVN】Please execute the 'Cleanup' command.

    背景 项目有个新的bug,我需要提取一个新的分支,但是提取之后,更新分支出现了这个问题 Please execute the 'Cleanup' command. 原因 由于使用SVN更新文件出错,导 ...

  7. source insight新建工程

    1.打开Source Insight.如果已经打开过项目,则选择Project->Close Project.然后点击Project->New Project: 2.在弹出界面填入项目名 ...

  8. 查找goog13的ip

    C:\Users\Deen>ping 172.217.24.14 Pinging 172.217.24.14 with 32 bytes of data: Reply from 172.217. ...

  9. SMB共享配置

                                                                   SMB 使用命令挂载和卸载SMB文件系统 自动挂载SMB文件系统 红帽企业 ...

  10. Nacos 使用

    Nacos(一)-下载安装 https://blog.csdn.net/qq_21067307/article/details/103895607  转载                        ...