Embedded FreeRTOS learning eight, xTaskCreate creates task details and restores interrupt task implementation

1. Create task function xTaskCreate

The task is not a very complicated thing, the task is a function xTaskCreate. To put it simply, to create a task, you have to provide its execution function, you have to provide important conditions such as the size of its stack, the execution space of the function, and the priority of the function. Because the task is running, the task function has a calling relationship and local variables, which are stored in the stack of the task; the task may be switched or suspended. At this time, the CPU register interrupt scene data is stored in the stack.

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 )

Parameter description:
(1) TaskFunction_t: typedef void (*TaskFunction_t)( void *); function pointer
(2) const char * const pcName: task name
(3) configSTACK_DEPTH_TYPE: #define configSTACK_DEPTH_TYPE uint16_t is an unsigned 2-byte value, Indicates the depth of the stack, which is actually allocated by the malloc function
(4) void * const pvParameters: is the parameter to be passed in
(5) UBaseType_t uxPriority: typedef unsigned short UBaseType_t; is an unsigned integer, indicating the size of the priority , the greater the value, the greater the priority
(6) TaskHandle_t * const pxCreatedTask: There is a TCB structure pointer, the parameters passed out

 

The full name of TCB_t is Task Control Block, which is the task control block. This structure contains all the information of a task, but there are a large number of conditional configuration options in the source code. The following masked options are all options that can be configured through conditions. Conditions are used to determine which definitions are used or not, and these are not needed for the time being, and the conditional configuration items are shielded. The most important parameters of TCB are defined above and the explanations of related variables are as follows

typedef struct tskTaskControlBlock             
    {
        // 这里栈顶指针必须位于TCB第一项是为了便于上下文切换操作,详见xPortPendSVHandler中任务切换的操作。
        volatile StackType_t    *pxTopOfStack;    

       
        // 表示任务状态,不同的状态会挂接在不同的状态链表下
        ListItem_t            xStateListItem;    
        // 事件链表项,会挂接到不同事件链表下
        ListItem_t            xEventListItem;        
        // 任务优先级,数值越大优先级越高
        UBaseType_t            uxPriority;            
        // 指向堆栈起始位置,这只是单纯的一个分配空间的地址,可以用来检测堆栈是否溢出
        StackType_t            *pxStack;            
        // 任务名
        char                pcTaskName[ configMAX_TASK_NAME_LEN ];

/*
//以下屏蔽掉的都是可以通过条件来配置的选项,通过条件来决定哪些定义使用或者不用,暂时不需要用到这 
//些,屏蔽掉,TCB最主要的参数在上面
//#####################################################################################

        // MPU相关暂时不讨论
        #if ( portUSING_MPU_WRAPPERS == 1 )
            xMPU_SETTINGS    xMPUSettings;        
        #endif
        // 指向栈尾,可以用来检测堆栈是否溢出
        #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;        
        #endif
        // 任务优先级被临时提高时,保存任务原本的优先级
        #if ( configUSE_MUTEXES == 1 )
            UBaseType_t        uxBasePriority;        
            UBaseType_t        uxMutexesHeld;
        #endif
        // 任务的一个标签值,可以由用户自定义它的意义,例如可以传入一个函数指针可以用来做Hook    函数调用
        #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

        // 支持NEWLIB的一个变量
        #if ( configUSE_NEWLIB_REENTRANT == 1 )
            struct    _reent xNewLib_reent;
        #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;         
        #endif

        // 延时是否被打断
        #if( INCLUDE_xTaskAbortDelay == 1 )
            uint8_t ucDelayAborted;
        #endif
        // 错误标识
        #if( configUSE_POSIX_ERRNO == 1 )
            int iTaskErrno;
        #endif
//###################################################################################
*/
    } tskTCB;
    typedef tskTCB TCB_t;

=========================================================================

2. Create the specific internal details of the task

Taking the simple task creation function as an example, three simple tasks vTask1, vTask2, vTask3 are created here

void vTask1( void *pvParameters )
{    /* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		flagIdleTaskrun = 0;
		flagTask1run = 1;
		flagTask2run = 0;
		flagTask3run = 0;
		/* 打印任务的信息 */
		printf("T1\r\n");				
    }
}
void vTask2( void *pvParameters )
{	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		flagIdleTaskrun = 0;
		flagTask1run = 0;
		flagTask2run = 1;
		flagTask3run = 0;
		/* 打印任务的信息 */
		printf("T2\r\n");				
	}
}

void vTask3( void *pvParameters )
{	
	const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );		
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		flagIdleTaskrun = 0;
		flagTask1run = 0;
		flagTask2run = 0;
		flagTask3run = 1;
		/* 打印任务的信息 */
		printf("T3\r\n");				
		// 如果不休眠的话, 其他任务无法得到执行
		vTaskDelay( xDelay5ms );
	}
}
//主函数的实现
int main( void )
{
	prvSetupHardware();	
	xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
	xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);
	xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
    /* 启动调度器 */
	vTaskStartScheduler();
    /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

3. Existence of TCB task structure in RAM memory

In the figure below, a whole tsTaskControlBlock structure code data is put into the RAM memory, which shows the effect of allocating a TCB structure in the memory (only the drawing shows the effect, which may not be exactly the same in fact); as you can see, in The data saved in the stack space allocated in the RAM memory are:

Stack top pointer pxTopOfStack;   point to the last data storage location of the divided memory space
; state list xStateListItem;
event list  xEventListItem;    
task priority uxPriority;         
pointer to the stack start position pxStack; point to the start address position of the divided memory space          
task name pcTaskName[ configMAX_TASK_NAME_LEN ];

 =========================================================================

4. The problem of stack space allocation and size when task function is created

//任务创建函数
xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);

When creating this task, where are the parameters passed in saved? First look at the parameter 1000 of the size of the stack passed in. At this time, you need to figure out two problems. The first is where is the stack allocated from? The second is how to determine the size of the stack?

The first question is where is the stack allocated from? The stack is actually a piece of free memory. The heap2.cpp file of FreeRTOS defines a huge global array ucHeap. This array is not used by anyone. As a piece of free memory, the allocation of the stack space in the future will start from this huge The available memory is divided into the array to use as a stack for a certain task, as shown in the figure above; from the macro definition, the size of the array is 17*1024 bytes.

The second question, how to determine the size of the stack in the example? It can be allocated according to the size of the value entered and exited by the programmer. In the example, a memory of 1000*4 bytes is divided, and then the initial space of the memory is stored in the pxStack pointer.

 =========================================================================

5. Problems and functions of function pointer and parameter storage when task function is created

//任务创建函数
xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);

The first parameter is the function pointer xTaskCreat is actually the address addrF of the function. When we want to start this task or call this function, we can make the R15 register in the CPU, that is, the value of the PC register equal to the address of the function. . Just addrF.

The fourth parameter is the parameter value passed in with the function, usually stored in the R0 register.

=========================================================================

6. The task is resumed in the suspended state

The system allocates available space from the defined global array ucHeap to the task function xTaskCreate. In the example, a memory space of 1000*4 is allocated, pxStack points to the starting address of the memory, and pxTopOfStack points to the last data storage location in the memory.

When we create a task, the task function xTaskCreate has already created this part of the memory and helped us modify the contents of the RAM memory. In the TCB structure, we did not see the parameters and function pointers passed in. In fact, the task function has put These values ​​are written to registers such as R15 and R0 in CPU memory for use when resuming the task.

When the task is just created, the task has not been running yet, and it belongs to a suspended state. When the task is interrupted, the system will put the registers of the current task on the stack for site protection, including the return address of the R15 function, R0 parameters, etc.; then start running the interrupted task , the interrupt task ends, then start to restore the various registers in the stack in the RAM memory, execute from the return address of the R15 function, jump back to the last interrupted task, and continue to execute. As shown in FIG.

 

 

Guess you like

Origin blog.csdn.net/weixin_44651073/article/details/126923015