FreeRTOS学习(二)

前言

前面已经大致了解了FreeRTOS的工作机制以及一些常用API的大致内容,接下来我打算从底层层面具体介绍一下任务创建和删除的API是如何实现的

创建任务

在FreeRTOS中每一个已创建的任务都包含了一个任务控制块,这是一个用于存储任务属性的结构体变量

TCB代码

结构体部分

条件编译的内容先略过,一个TCB的组成大致如下

typedef struct tskTaskControlBlock  
{  
 volatile StackType_t *pxTopOfStack; /*< 任务栈栈顶,必须为TCB的第一个成员 */  
 #if ( portUSING_MPU_WRAPPERS == 1 ) //是否使用MPU(微型处理器)  
  xMPU_SETTINGS  xMPUSettings;   /*< The MPU settings are defined as part of the port layer.  THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */  
 #endif  
  
 ListItem_t   xStateListItem;  /*< 任务状态列表 */ ListItem_t   xEventListItem;   /*< 任务事件列表 */ UBaseType_t    uxPriority;    /*< 任务优先级 */ StackType_t    *pxStack;    /*< 任务栈起始地址 */ char    pcTaskName[ configMAX_TASK_NAME_LEN ];/*< 任务栈名字 */  /* 判断堆栈的生长方向,在STM32中栈的地址由高开始向低生长,而堆由低向高生长,因此这里用到的是堆 */ #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )  
  StackType_t   *pxEndOfStack;  /*< Points to the highest valid address for the stack. */  
 #endif  
  
 #if ( portCRITICAL_NESTING_IN_TCB == 1 )  
  UBaseType_t   uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */  
 #endif  
  
 #if ( configUSE_TRACE_FACILITY == 1 )  
  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. */  
  UBaseType_t   uxTaskNumber;   /*< Stores a number specifically for use by third party trace code. */  
 #endif  
  
 #if ( configUSE_MUTEXES == 1 )  
  UBaseType_t   uxBasePriority;   /*< The priority last assigned to the task - used by the priority inheritance mechanism. */  
  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;  /*< Stores the amount of time the task has spent in the Running state. */  
 #endif  
  
 #if ( configUSE_NEWLIB_REENTRANT == 1 )  
  /* Allocate a Newlib reent structure that is specific to this task.  
  Note Newlib support has been included by popular demand, but is not  used by the FreeRTOS maintainers themselves.  FreeRTOS is not  responsible for resulting newlib operation.  User must be familiar with  newlib and must provide system-wide implementations of the necessary  stubs. Be warned that (at the time of writing) the current newlib design  implements a system-wide malloc() that must be provided with locks. */  struct _reent xNewLib_reent; #endif  
  
 #if( configUSE_TASK_NOTIFICATIONS == 1 )  
  volatile uint32_t ulNotifiedValue;  
  volatile uint8_t ucNotifyState;  
 #endif  
  
 /* See the comments above the definition of  
 tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */ #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 Macro has been consolidated for readability reasons. */  
  uint8_t  ucStaticallyAllocated;    /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */  
 #endif  
  
 #if( INCLUDE_xTaskAbortDelay == 1 )  
  uint8_t ucDelayAborted;  
 #endif  
  
} tskTCB;

当我们启动动态分配内存操作时系统会通过一系列预编译以及条件判断来为我们自动分配对应的堆栈空间

动态分配内存

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )  
  
 BaseType_t xTaskCreate(  TaskFunction_t pxTaskCode,  
       const char * const pcName,  /*lint !e971 Unqualified char types are allowed for strings and single characters only. */  
       const configSTACK_DEPTH_TYPE usStackDepth,  //任务堆栈大小  
       void * const pvParameters,  //要传入的参数  
       UBaseType_t uxPriority, //任务优先级  
       TaskHandle_t * const pxCreatedTask //任务创建句柄 ) {  
 TCB_t *pxNewTCB;  
 BaseType_t xReturn;  
  
  /* 通过判断堆栈的生长方向来创建TCB */  
  #if( portSTACK_GROWTH > 0 )  
  {  
   /* Allocate space for the TCB.  Where the memory comes from depends on   the implementation of the port malloc function and whether or not static   allocation is being used. */   pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );  
   if( pxNewTCB != NULL )   {    /* Allocate space for the stack used by the task being created.    The base of the stack memory stored in the TCB so the task can    be deleted later if required. */    pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */  
    if( pxNewTCB->pxStack == NULL )    {     /* Could not allocate the stack.  Delete the allocated TCB. */     vPortFree( pxNewTCB );     pxNewTCB = NULL;    }   }  }  #else /* portSTACK_GROWTH */  
  {  
  StackType_t *pxStack; //定义一个指向堆栈的指针  
  
   /* 为堆栈分配对应的空间,这里通过malloc分配的字节数要*4因为要分配空间的单位是半字(StackType_t = uint32_t) */  
   pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */  
  
   if( pxStack != NULL ) //若成功分配  
   {  
    /* 为任务控制块分配对应的空间 */    pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */  
  
    if( pxNewTCB != NULL )  
    {  
     /* 将任务的堆栈地址赋值给任务控制块中的堆栈地址 */     pxNewTCB->pxStack = pxStack;  
    }  
    else  
    {  
     /* 若申请失败则释放任务控制块 */     vPortFree( pxStack );  
    }  
   }  
   else  
   {  
    pxNewTCB = NULL;  
   }  
  }  
  #endif /* portSTACK_GROWTH */
//创建新的任务  
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );  
prvAddNewTaskToReadyList( pxNewTCB ); //将新创建的任务添加到就绪列表中  
xReturn = pdPASS;

在分配好了内存以后我们就可以开始处理任务&任务控制块以及各种列表项之间的映射

任务与TCB间映射

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 */  
  
 /* Avoid dependency on memset() if it is not required. */ #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 */  
  
 /* Calculate the top of stack address.  This depends on whether the stack grows from high memory to low (as per the 80x86) or vice versa. portSTACK_GROWTH is used to make the result positive or negative as required by the port. */ #if( portSTACK_GROWTH < 0 ) //若栈地址向下生长(STM32栈向下,堆向上)  
 {  
    //由于pxStack是由堆分配而来,因此栈的首地址应该为堆+栈的大小  
  pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );  
    //进行八字节对齐  
  pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 MISRA exception.  Avoiding casts between pointers and integers is not practical.  Size differences accounted for using portPOINTER_SIZE_TYPE type. */  
  
  /* Check the alignment of the calculated top of stack is correct. */  configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );  
  
  #if( configRECORD_STACK_HIGH_ADDRESS == 1 )  
  {  
   /* Also record the stack's high address, which may assist   debugging. */   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 */  
  
 /* 将任务名称存储 */ for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )  
 {  
  pxNewTCB->pcTaskName[ x ] = pcName[ x ];  
  
  /* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than  
  configMAX_TASK_NAME_LEN characters just in case the memory after the  string is not accessible (extremely unlikely). */  if( pcName[ x ] == 0x00 )  
  {  
   break;  
  }  
  else  
  {  
   mtCOVERAGE_TEST_MARKER();  
  }  
 }  
  
 /* Ensure the name string is terminated in the case that the string length  
 was greater or equal to configMAX_TASK_NAME_LEN. */ 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;  
 #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 ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */  
 listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );

映射关系完成后就要开始向对应的内存中写入相关信息了,具体是通过cpxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );来实现的,下面我们看一下具体的操作

在这里插入图片描述

信息的写入

相信有一定汇编基础的同学应该不会感到陌生,这就是一些基本的任务指针以及一些寄存器的配置

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )  
{  
 /* 将指定的内容存放到对应的地址中 */ pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */  
 *pxTopOfStack = portINITIAL_XPSR;  /* 0x01000000代表此时bit24位被置1,启用Thumb指令 */ pxTopOfStack--;  
 *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* 存放PC指针位置,为指向函数的指针 */ pxTopOfStack--;  
 *pxTopOfStack = ( StackType_t ) portTASK_RETURN_ADDRESS; /* 代表返回的模式状态 */ 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;  
}

接下来我们要将创建好的任务添加到函数就绪列表中以开展余下的操作

任务的分配

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 )  
  {  
   /* 若这是第一个任务,则将前面已经创建好的任务控制块赋值给该任务 */   pxCurrentTCB = pxNewTCB;  
  
   if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )  
   {  
    /* 再次确认该任务是否为第一个任务,初始化一些相关列表:优先级列表&延时列表(有两个,便于延时交叉使用)&就绪列表 */    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 )  //比较当前任务和目标任务的优先级  
    {  
     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. */  if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )  
  {  
	   taskYIELD_IF_USING_PREEMPTION(); //进行任务的切换 
  }  
  else  
  {  
   mtCOVERAGE_TEST_MARKER();  
  }  
 }  
 else  
 {  
  mtCOVERAGE_TEST_MARKER();  
 }  
}

删除任务

#if ( INCLUDE_vTaskDelete == 1 )  //删除任务API允许标志位  
  
 void vTaskDelete( TaskHandle_t xTaskToDelete )  
 {  
 TCB_t *pxTCB;  
  
  taskENTER_CRITICAL(); //进入任务临界区获取要删除的任务控制块  
  {  
   /* 通过下面的API来判断传入的任务句柄是否为NULL,若是则删除当前任务,若否则删除对应句柄的任务控制块 */   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();  
   }  
  
   /* Increment the uxTaskNumber also so kernel aware debuggers can  
   detect that the task lists need re-generating.  This is done before   portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will   not return. */   uxTaskNumber++;  
  
   if( pxTCB == pxCurrentTCB ) //判断要删除的控制块是否为当前任务  
   {  
    /* A task is deleting itself.  This cannot complete within the  
    task itself, as a context switch to another task is required.    Place the task in the termination list.  The idle task will    check the termination list and free up any memory allocated by    the scheduler for the TCB and stack of the deleted task. */    vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );  
  
    /* 若是则先将任务放入等待删除列表同时列表自加 */    ++uxDeletedTasksWaitingCleanUp;  
  
    /* The pre-delete hook is primarily for the Windows simulator,  
    in which Windows specific clean up operations are performed,    after which it is not possible to yield away from this task -    hence xYieldPending is used to latch that a context switch is    required. */    portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );  
   }  
   else  //否则删除其他任务  
   {  
    --uxCurrentNumberOfTasks;  
    prvDeleteTCB( pxTCB );  
  
    /* 更新下一个任务的阻塞超时时间,防止阻塞故障 */    prvResetNextTaskUnblockTime();  
   }  
      //释放堆栈内存  
   traceTASK_DELETE( pxTCB );  
  }  
  taskEXIT_CRITICAL();  
  
  /* Force a reschedule if it is the currently running task that has just  
  been deleted. */  if( xSchedulerRunning != pdFALSE )  
  {  
   if( pxTCB == pxCurrentTCB ) //若执行的任务要被删除,则先会切换至其他任务  
   {  
    configASSERT( uxSchedulerSuspended == 0 );  
    portYIELD_WITHIN_API(); //释放空闲任务的内存  
   }  
   else  
   {  
    mtCOVERAGE_TEST_MARKER();  
   }  
  }  
 }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ESP32的FreeRTOS使用上与传统的FreeRTOS有一些区别。在ESP32中,基本不需要单独配置FreeRTOSConfig.h文件,因为ESP-IDF中的menuconfig功能可以对所有涉及到的内容进行配置,使用起来更加直观和便利。主要的数据类型说明中,有一个重要的数据类型是TickType_t。 在ESP32的魔改版FreeRTOS中,很少使用正经的事件集,而是使用ESP-IDF提供的更方便的事件循环。这使得在ESP32中使用事件循环更加方便。 另外,ESP32的分区表是采用进制格式而不是CSV文件。ESP-IDF提供了gen_esp32part.py工具来配置和构建分区表。默认情况下,使用的是默认分区表。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【ESP32+freeRTOS学习笔记-(一)freeRTOS介绍】](https://blog.csdn.net/weixin_45499326/article/details/128226443)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [ESPIDF开发ESP32学习笔记【ESP32上的FreeRTOS】](https://blog.csdn.net/qq_40500005/article/details/114794039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值