FreeRTOS Learning Record 02--Tasks

0 Preface

@ Author         :Dargon
@ Record Date    :2021/07/12
@ Reference Book : `FreeRTOS源码详解与应用开发`,`ARM Cortex-M3与Cortex-M4权威指南`,`B站正点原子FreeRTOS讲解视频`
@ Purpose        :学习正点原子的miniFly,该飞控基于FreeRTOS系统开发的,所以学习一下记录下关于RTOS系统的一些基本操作,大概了解系统的工作原理,如何创建,运行,切换任务等等基本操作流程。在此进行学习的记录。

1 Task basics

1.1 Task priority

  • It is not the same thing as the interrupt priority. The smaller the value of the interrupt priority, the higher the corresponding priority.
  • The priority of the task corresponds to 0~ configMAX_PRIORITIES =32. During task scheduling, the task to be run will be selected according to the size of this priority, that is, the task with the highest priority in the ready state will be run.
  • When a task enters the queue of the ready list, the list is arranged in ascending order of priority. For example, the current priority is 4 5 , and the values ​​of the corresponding list items are (32-4) and (32-5) for queue operation in the ready state. The corresponding priority of 5 is in front of the priority of 4.

1.2 Task Control Block TCB_t

  • The information contained in a task corresponds to a structure

// --任务块的结构体
typedef struct tskTaskControlBlock
{
    
    
    // --volatile 表示每次都是直接从 内存地址去读取,而不是通过cache或者register读取,告诉编译器这个变量不需要优化
    // --任务控制块结构体 第一项就是对应的 任务堆栈指针
    volatile StackType_t	*pxTopOfStack;	/*< Points to the location of the last item placed on the tasks stack.  THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */

    // --xStateListItem 和链表有关的 根据该任务的状态,将此列表项添加到对应的状态列表中,
    // --xEventListItem 任务的事件列表项添加到 某个事件列表中。 
    ListItem_t			xStateListItem;	/*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
    ListItem_t			xEventListItem;		/*< Used to reference a task from an event list. */
    // --对应任务优先级
    UBaseType_t			uxPriority;			/*< The priority of the task.  0 is the lowest priority. */
    StackType_t			*pxStack;			/*< Points to the start of the stack. */
    // --申请内存 都已经是16个char了
    char				pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created.  Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */

    // 省略一堆的条件编译(后面用到的再说)
    
    // --关于互斥信号量 在初始化任务控制块结构体的时候 需要初始化这里的内容 
    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t		uxBasePriority;		/*< The priority last assigned to the task - used by the priority inheritance mechanism. */
        UBaseType_t		uxMutexesHeld;
    #endif
} tskTCB;
typedef tskTCB TCB_t;
  • Explain in detail the role of each item, and the role played later

  • volatile StackType_t *pxTopOfStack
    points to the last position of the stack of the task. Why do you say this, because when applying for the task stack, the memory pointer returned by the malloc function points to the first address of this new memory. If we need a task switch, we need to return to the function of the stack to save the scene, and we need to store the current registers in the stack so that we can continue to execute when we come back next time. But for STM32, the stack grows downward, so it needs one, the position of the top of the stack pxTopOfStack, to traverse down, perform – operation, and store it in the stack again and again.

  • ListItem_t xStateListItem
    Add this list item to the corresponding state list according to the current state of the task. For example, if the task is now in the ready state, it will be hung in the ready list according to its priority.

  • ListItem_t xEventListItem
    When the task is waiting for a certain message or semaphore, the task that has not waited and entered the blocking state will hang this list item of the task on the list of the corresponding queue. (When the queue is initialized, the corresponding two lists and will xTasksWaitingToSendbe xTasksWaitingToSendinitialized xTasksWaitingToReceive)

  • When the task corresponding to UBaseType_t uxPriority
    is initialized, the priority of the task is set


  • The task stack applied by StackType_t *pxStack is counted as the bottom of the stack! There will be such a sentence during initialization;

    pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t )
    
  • char pcTaskName[ configMAX_TASK_NAME_LEN ]
    is a char string, saving the name of the task, configMAX_TASK_NAME_LEN =16, up to 15 chars, which need \0to end,
    anyway, after you apply for this structure memory, even if you can’t use up the name memory, it still occupies 16 char positions.

  • The last one is about conditional compilation if a mutex semaphore is used.

1.3 Status of tasks

  • Running state,
    a running task, a task that occupies the CPU, 如果对应的单核处理器,那么不管在任何时刻永远都只有一个任务在运行the original single core means this!

  • ready state

  • blocked state

  • suspended state

2 API functions

2.1 Task creation

  • Dynamically create task source code analysis
// --任务动态创建过程
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

	BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
							const char * const pcName,
							const uint16_t usStackDepth,
							void * const pvParameters,
							UBaseType_t uxPriority,
							TaskHandle_t * const pxCreatedTask ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	{
    
    
	TCB_t *pxNewTCB; // --申请一个结构体指针
	BaseType_t xReturn;

		/* If the stack grows down then allocate the stack then the TCB so the stack
		does not grow into the TCB.  Likewise if the stack grows up then allocate
		the TCB then the stack. */
		#if( portSTACK_GROWTH > 0 ) // --关于条件编译 不看 关于堆栈指针向上生长 stm32是堆栈向下生长的
		{
    
    
            // ……省略
		}
		#else /* portSTACK_GROWTH */ // --开始任务create
		{
    
    
		StackType_t *pxStack;

			/* Allocate space for the stack used by the task being created. */
			// --malloc 堆栈内存 一个堆栈单元是 32bit =4byte
			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 )
			{
    
    
				/* Allocate space for the TCB. */
				// --堆栈完事之后 申请该任务控制块的内存
				pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */

				if( pxNewTCB != NULL )
				{
    
    
					/* Store the stack location in the TCB. */
					// --初始化堆栈内存
					pxNewTCB->pxStack = pxStack;
				}
				else
				{
    
    
					/* The stack cannot be used as the TCB was not created.  Free
					it again. */
					vPortFree( pxStack );
				}
			}
			else
			{
    
    
				pxNewTCB = NULL;
			}
		}
		#endif /* portSTACK_GROWTH */

		if( pxNewTCB != NULL )
		{
    
    
			#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
			{
    
    
				/* 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 /* configSUPPORT_STATIC_ALLOCATION */
			// --前面对于给你的 只是申请内存
			// --初始化任务其他项  里面挺多的初始化
			prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
			
			// --添加任务到 就绪列表中
			prvAddNewTaskToReadyList( pxNewTCB );
			xReturn = pdPASS;
		}
		else
		{
    
    
			xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
		}

		return xReturn;
	}

#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

  • xTaskCreate() function

    1. Request a sizeof( TCB_t )memory block of a size
    2. After the application is successful, apply for a task stack memory and return the address topxNewTCB->pxStack
    3. Call the function prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL )to initialize other members of TCB_t.
    4. Call the function prvAddNewTaskToReadyList( pxNewTCB )to hang the task into the ready list.
    • prvInitialiseNewTask() function
      First of all, the code of the function is very long. The previous one xTaskCreate()is equivalent to applying for memory for TCB_t, and the initialization part is handed over here. This function has a lot of conditional compilation due to the initialization of other member variables of the task control block TCB_t. If it is not needed, it will be passed directly.

      1. Call memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) ) to fill the stack area of ​​the task with 0xa5. In this step, it is necessary to detect whether the stack memory is exhausted or remaining, which is based on the value of 0xa5 here.
      2. Calculate the position of the top of the stackpxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
      3. Initialize task namepxNewTCB->pcTaskName[ x ]
      4. Initialize task prioritypxNewTCB->uxPriority
      5. Initialize two list items
        vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
        vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
        
      6. The variable in the initialization state list item xStateListItem ->pvOwnerpoints to the task. Corresponding to its own task status, it is equivalent to hanging the task on the corresponding status list.
      7. Initialize the variables in the event list items xEventListItem->value, and you can see that the value set here, the greater the priority value of the corresponding task, the smaller the value of the corresponding list item, and the relatively higher in the list, reflecting the higher priority.
        listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
        
      8. Initialize the variable in the event list item xEventListItem ->pvOwnerto point to the task
      9. The initialization of the task stack, the calling function pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );, and the memory that was originally filled with 0xa5 should be initialized to those, which is a major event. pxTaskCodeis a function pointer pointing to the task function.
      10. The initialization task handle *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;pxCreatedTask is a pointer to pxNewTCBthis address
      • The pxPortInitialiseStack() function is located in the port.c file
        to initialize the stack area. The stack is used to save the scene during context switching. After creating a stack, it needs to be initialized, that is, to assign initial values ​​to some registers of the Cortex-M core, and these initial values ​​​​are first saved in the stack of subtasks. Save in order.
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
        
        StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
        {
                  
                  
            /* Simulate the stack frame as it would be created by a context switch
            interrupt. */
            // --用来保存现场
            /* Offset added to account for the way the MCU uses the stack on entry/exit
            of interrupts, and to ensure alignment. */
            pxTopOfStack--;
            // --xPSR =0x01000000 对应的bit24 为1,对应的功能表示处于 Thumb状态,使用Thumb指令
            *pxTopOfStack = portINITIAL_XPSR;	/* xPSR */
            
            // --将R(15)PC 初始化为对应的任务函数
            pxTopOfStack--;
            *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* PC */
            
            // --R(14) LR -- 链接寄存器 初始化为 prvTaskExitError 调用vPortRaiseBASEPRI() 关中断的函数
            pxTopOfStack--;
            *pxTopOfStack = ( StackType_t ) prvTaskExitError;	/* LR */
        
            // --跳过R12, R3, R2 and R1 寄存器,紧接着R0 保存任务函数的参数
            /* Save code space by skipping register initialisation. */
            pxTopOfStack -= 5;	/* R12, R3, R2 and R1. */
            *pxTopOfStack = ( StackType_t ) pvParameters;	/* R0 */
        
            /* A save method is being used that requires each task to maintain its
            own exec return value. */
            // --表示退出SVC和PendSV中断时候 CPU应该处于那种状态 portINITIAL_EXEC_RETURN = 0xfffffffd
            // --对应的bit0--bit3 =1101 表示返回后进入线程模式,使用进程栈指针PSP
            // --这种退出SVC 中断进入的模式,关系到第一个任务从SVC出来之后,后面需要以怎样的方式开始工作
            pxTopOfStack--;
            *pxTopOfStack = portINITIAL_EXEC_RETURN;
        
            // --跳过 R11, R10, R9, R8, R7, R6, R5 and R4 8个寄存器
            pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */
        
            // --重新返回栈顶指针
            return pxTopOfStack;
        }
        
        1. save xPSRregister value to stack
        2. save R15(PC)register value to stack
        3. save R14(LR)register value to stack
        4. Save R0register value task parameters to the stack
        5. save portINITIAL_EXEC_RETURNthe value on the stack
        6. Skip 8 registers, R11~R4, update the value at the top of the pxTopOfStack stack
        7. The values ​​of these registers, which are related to this task, are temporarily stored in the stack area of ​​the task, and when the task is switched, they will be popped from the stack again to the corresponding registers for on-site restoration.
    • The prvAddNewTaskToReadyList( pxNewTCB ) function is located in the task.c file

      1. The current number of tasks in the global variable uxCurrentNumberOfTasksis +1, and a new task is coming.
      2. If the created task is the first task, call prvInitialiseTaskLists()the function initialization list (that is, the list array of the previous ready state, 2 lists about blocking and 1 PendingReady list), and the list is initialized, not the list items. Basic task scheduling uses these lists.
      3. There are currently tasks running, but the task scheduler is not running. According to the priority of the created task, update it pxCurrentTCBto pxCurrentTCBcorrespond to the task to be run next, waiting to be called by the task scheduler.
      4. Update uxTaskNumber, used to record the label of the task control block, pxNewTCB->uxTCBNumber = uxTaskNumber;to show which task the created task itself is.
      5. Calling prvAddTaskToReadyList( pxNewTCB );the function will add to the ready list, adding to the list according to the priority value.
      6. If the task scheduler is enabled, the priority of the created task is higher than that pxCurrentTCBof the task, and a task switch is required.
        1. pxCurrentTCBThe global variable points to the running task. If it is updated, during task switching, the original running task site (corresponding register) will be saved to the stack corresponding to the task, and the stack content of the pointed task (the environment required by its own task) will be restored to the corresponding register, ready to pxCurrentTCBrun
      • The prvAddTaskToReadyList( pxNewTCB ) function is located in the task.c file
        itself is a macro definition
        1. Call macro taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );to record/update the year's highest priority in the ready listuxTopReadyPriority
          1. Finally call ( uxReadyPriorities ) |= ( 1UL << ( uxPriority )such an operation
          2. Use the bits of the 32bit variable to record the highest priority in the current ready list,
          3. When obtaining the highest priority from this variable, the hardware method used here calculates the number of leading 0s of the 32bit variable (corresponding to the assembly instruction)
        2. Call the function vListInsertEnd()to insert the created task into the ready list.

2.2 Task deletion

  • vTaskDelete() delete function source code analysis
    1. Use pxTCB = prvGetTCBFromHandle( xTaskToDelete ), according to the parameters, check the task handle to be deleted, if it is null, delete the task itself (pxCurrentTCB), otherwise delete the corresponding task.
    2. pxTCB->xStateListItemPerform the remove operation in the pending state list of the task .
    3. pxTCB->xEventListItemPerform the remove operation in the list of pending events of the task . For example, the task hangs on the queue's waiting message blocking list because it waits for a certain queue message or semaphore to block.
    4. uxTaskNumber++, for the number of the task control block, ++ is also required when creating a new task, and then assign it topxNewTCB->uxTCBNumber = uxTaskNumber;
    5. If the task to be deleted is currently running, insert the task pxTCB->xStateListIteminto the list xTasksWaitingTerminationof tasks waiting to be terminated and wait for deletion. Update the number of tasks waiting to be deleted++uxDeletedTasksWaitingCleanUp
    6. The deleted task is not a running task, the total number of current tasks is reduced by 1 --uxCurrentNumberOfTasks, and the function is called prvDeleteTCB( pxTCB );to free the memory of the task stack and the memory of the task control block TCB_t. Call the function prvResetNextTaskUnblockTimeto reset, the next blocking waiting time, (that is, the task that may be deleted is the task that is currently hanging on the blocking list)
    7. If it pxTCB == pxCurrentTCBis necessary to force a task switch.

2.3 Task blocking

  • Generally, in the system, a delay function is used in a task function to delay the task. However, how to implement the delay of the task is to hang the task on the blocking list according to the required delay time, and wait for a while. It is equivalent to delaying the time period. Executing the delay function will switch the task, and the task will enter the blocking state until the delay is completed, and the task will re-enter the ready state.

  • There are two types of task delays, one is relative delay vTaskDelay()(that is, how much delay is directly based on the operation), and the other is absolute delay vTaskDelayUntil()(equivalent to setting a running time for the task and delaying a set period of time)

  • vTaskDelay() relative delay source code analysis

    1. Call vTaskSuspendAll();the pending task scheduler.
    2. Call the function prvAddCurrentTaskToDelayedList()to hang the current task in the delay list.
    3. Call xTaskResumeAll();to resume the task scheduler.
    • Analysis of prvAddCurrentTaskToDelayedList() function
      1. Record the current moment valuexTickCount
      2. Remove the task from the ready listuxListRemove( &( pxCurrentTCB->xStateListItem ) )
      3. If the delay time is portMAX_DELAY, directly hang the task on the pending list
      4. Set the value of the status list item of the task to the waiting time value``
        listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
        
      5. If overflow occurs at the calculation time point xTimeToWake, it needs to be hung in the corresponding overflow blocking list, pxOverflowDelayedTaskListwhich is applied for when the task is initialized.
      6. For normal operation, directly hang in the blocking list,pxDelayedTaskList
      7. For the next blocking time point xNextTaskUnblockTime, update

2.4 Task pending

  • Source code analysis of vTaskSuspend() task suspend function
    1. Use pxTCB = prvGetTCBFromHandle( xTaskToDelete ), according to the parameters, check the task handle to be suspended, if it is null, suspend the task itself.
    2. pxTCB->xStateListItemPerform the remove operation in the pending state list of the task .
    3. pxTCB->xEventListItemPerform remove operation in the list of pending events of the task
    4. Add the task to the end of the suspended task list. The suspended task list is xSuspendedTaskList. All suspended tasks will be suspended on this list. Because the suspended suspend has no recovery time, just insert it directly at the end.
    5. Call the function prvResetNextTaskUnblockTimeto reset, the next blocking waiting time, (that is, the task that may be suspended is the task that is currently hanging on the blocking list)
    6. if pxTCB == pxCurrentTCB,
      1. If the task scheduler is already running, a task switch needs to be forced.
      2. The task scheduler is not enabled,
        1. If all tasks are suspended, pxCurrentTCB =nullthis situation is impossible, because there are idle tasks holding on there.
        2. Call the function vTaskSwitchContext();to find the next task to run. The situation at this time: the current task is to be suspended, the task scheduler is not enabled, and all tasks are not suspended to the suspend list. So to find the next task to run. Normally, the call of the task is completed by the task scheduler, but the task scheduler is not enabled, so you can only manually find the next task.

2.5 Task recovery

  • Source code analysis of vTaskResume() task recovery function
    1. Called to uxListRemove( &( pxTCB->xStateListItem ) )remove the task corresponding to this list item from the pending list.
    2. Add the task pxTCB->xStateListItemto the ready list prvAddTaskToReadyList( pxTCB );.
    3. Determine the priority of the restored task and whether task switching is required.

3 idle tasks

  • not yet processed

Normally, the call of the task is completed by the task scheduler, but the task scheduler is not enabled, so you can only manually find the next task.

2.5 Task recovery

  • Source code analysis of vTaskResume() task recovery function
    1. Called to uxListRemove( &( pxTCB->xStateListItem ) )remove the task corresponding to this list item from the pending list.
    2. Add the task pxTCB->xStateListItemto the ready list prvAddTaskToReadyList( pxTCB );.
    3. Determine the priority of the restored task and whether task switching is required.

3 idle tasks

  • not yet processed

Guess you like

Origin blog.csdn.net/Dallas01/article/details/118720651