FreeRTOS学习笔记-------任务创建与删除

本文详细介绍了FreeRTOS中任务的创建方法(动态和静态)、栈的使用、任务优先级设定,以及如何动态和静态分配内存。还探讨了栈大小对局部变量的影响和任务删除的机制。
摘要由CSDN通过智能技术生成

任务创建

在FreeRTOS中,任务就是一个函数,原型如下:

void TaskFunction( void * );

此函数没有返回值,创建的每个任务有自己的栈。

注意:当创建任务时,使用的任务函数相同时,局部变量存放在各自的栈中,但全局变量和静态变量是同一个。

任务创建的方式有两种,分别为动态创建和静态创建,即任务函数的栈等空间的创建方式。

动态创建

动态创建的函数原型如下:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,   // 任务函数
                        const char * const pcName,   // 任务名称,字符串
                        const configSTACK_DEPTH_TYPE usStackDepth,   // 栈大小,单位word
                        void * const pvParameters,   // 传递给任务函数的参数
                        UBaseType_t uxPriority,      // 任务优先级
                        TaskHandle_t * const pxCreatedTask )  // 任务句柄

其中任务函数就是需要创建的任务,TaskFunction_t是函数指针,原型是:

typedef void (* TaskFunction_t)( void * );

任务结构体包括任务的一些信息,比如优先级等等。参数pxCreatedTask指向任务结构体可以用来修改已创建任务的优先级、删除任务等等,官方源码如下:

prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
    if( pxCreatedTask != NULL )
    {
        /* Pass the handle out in an anonymous way.  The handle can be used to
         * change the created task's priority, delete the created task, etc.*/
        *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

动态创建任务成功返回pdPASS,否则errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY。

静态创建

静态创建的函数原型如下:

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,   // 任务函数
                                const char * const pcName,   // 任务名称
                                const uint32_t ulStackDepth, // 栈大小
                                void * const pvParameters,   // 传递给任务函数的参数
                                UBaseType_t uxPriority,      // 任务优先级
                                StackType_t * const puxStackBuffer,  // 分配栈的起始地址
                                StaticTask_t * const pxTaskBuffer )  // 任务控制块

静态创建任务时,参数puxStackBuffer和pxTaskBuffer必须是全局变量,否则创建失败,官方源码如下:

        if( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) )
        {
            /* The memory used for the task's TCB and stack are passed into this
             * function - use them. */
            pxNewTCB = ( TCB_t * ) pxTaskBuffer; /*lint !e740 !e9087 Unusual cast is ok as the structures are designed to have the same alignment, and the size is checked by an assert. */
            pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer;

            #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
                {
                    /* Tasks can be created statically or dynamically, so note this
                     * task was created statically in case the task is later deleted. */
                    pxNewTCB->ucStaticallyAllocated = tskSTATICALLY_ALLOCATED_STACK_AND_TCB;
                }
            #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */

            prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, &xReturn, pxNewTCB, NULL );
            prvAddNewTaskToReadyList( pxNewTCB );
        }
        else
        {
            xReturn = NULL;
        }

在keil中,通过调试发现,当未使用使用局部变量时,局部变量的地址为NULL,此时函数返回NULL。

函数返回值指向任务的控制模块,根据上述源码中prvInitialiseNewTask函数可知,返回值与动态创建时的变量pxCreatedTask作用相同。

当静态创建任务时,需在FreeRTOSConfig.h文件中添加配置项:

#define configSUPPORT_STATIC_ALLOCATION 1

并实现函数 vApplicationGetIdleTaskMemory,实现的方法如下所示:

StackType_t puxIdleTaskStackBuffer[100];
StaticTask_t xIdleTaskBuffer;

void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
                                    StackType_t ** ppxIdleTaskStackBuffer,
                                    uint32_t * pulIdleTaskStackSize )
{
    *ppxIdleTaskTCBBuffer = &xIdleTaskBuffer;
    *ppxIdleTaskStackBuffer = puxIdleTaskStackBuffer;
    *pulIdleTaskStackSize = 100;
}

删除任务

删除任务的函数原型是:

void vTaskDelete( TaskHandle_t xTaskToDelete );

只需传入需要删除任务的句柄即可,当传入参数为NULL时,表示删除自己。

注意:删除任务时,如果是自杀则需要空闲任务并释放TCB和栈,但如果是删除其他任务,则在删除函数中进行清理内存。由官方源码可知,如果任务自杀,同时空闲任务没有得到执行,则只是从链表中移除。

            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 ) );

                /* Increment the ucTasksDeleted variable so the idle task knows
                 * there is a task that has been deleted and that it should therefore
                 * check the xTasksWaitingTermination list. */
                ++uxDeletedTasksWaitingCleanUp;

                /* Call the delete hook before portPRE_TASK_DELETE_HOOK() as
                 * portPRE_TASK_DELETE_HOOK() does not return in the Win32 port. */
                traceTASK_DELETE( pxTCB );

                /* 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;
                traceTASK_DELETE( pxTCB );
                prvDeleteTCB( pxTCB );

                /* Reset the next expected unblock time in case it referred to
                 * the task that has just been deleted. */
                prvResetNextTaskUnblockTime();
            }

优先级

优先级范围:0~(configMAX_PRIORITIES – 1)。
数值越小优先级越低。在默认的调度算法中,优先级高的任务先执行,相同优先级的任务轮流执行。如果高优先级的任务没有放弃CPU资源,则一直执行。

相同优先级任务轮流执行实验代码如下:

void vmainTask1Function( void * param);
void vmainTask2Function( void * param);

StackType_t puxIdleTaskStackBuffer[100];
StaticTask_t xIdleTaskBuffer;
/*-----------------------------------------------------------*/
StackType_t puxTask2StackBuffer[100];     // 必须是全局变量
StaticTask_t xTask2TCBBuffer;
TaskHandle_t xTask1Handle;

UBaseType_t task1Run = 0;
UBaseType_t task2Run = 0;

int main( void )
{
#ifdef DEBUG
  debug();
#endif

	prvSetupHardware();
	
	printf("Hello world!\r\n");
	xTaskCreate(vmainTask1Function, "task1", 100, NULL, 1, NULL);                 // 动态创建任务
  xTaskCreateStatic(vmainTask2Function, "task2", 100, NULL, 1, puxTask2StackBuffer, &xTask2TCBBuffer);                                                          // 静态创建任务

	/* Start the scheduler. */
	vTaskStartScheduler();

	/* Will only get here if there was not enough heap space to create the
	idle task. */
	return 0;
}
/*-----------------------------------------------------------*/
/*-----------------------------------------------------------*/

static void prvSetupHardware( void )
{
	/* Start with the clocks in their expected state. */
	RCC_DeInit();

	/* Enable HSE (high speed external clock). */
	RCC_HSEConfig( RCC_HSE_ON );

	/* Wait till HSE is ready. */
	while( RCC_GetFlagStatus( RCC_FLAG_HSERDY ) == RESET )
	{
	}

	/* 2 wait states required on the flash. */
	*( ( unsigned long * ) 0x40022000 ) = 0x02;

	/* HCLK = SYSCLK */
	RCC_HCLKConfig( RCC_SYSCLK_Div1 );

	/* PCLK2 = HCLK */
	RCC_PCLK2Config( RCC_HCLK_Div1 );

	/* PCLK1 = HCLK/2 */
	RCC_PCLK1Config( RCC_HCLK_Div2 );

	/* PLLCLK = 8MHz * 9 = 72 MHz. */
	RCC_PLLConfig( RCC_PLLSource_HSE_Div1, RCC_PLLMul_9 );

	/* Enable PLL. */
	RCC_PLLCmd( ENABLE );

	/* Wait till PLL is ready. */
	while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
	{
	}

	/* Select PLL as system clock source. */
	RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK );

	/* Wait till PLL is used as system clock source. */
	while( RCC_GetSYSCLKSource() != 0x08 )
	{
	}

	/* Enable GPIOA, GPIOB, GPIOC, GPIOD, GPIOE and AFIO clocks */
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC
							| RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE );

	/* SPI2 Periph clock enable */
	RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );


	/* Set the Vector Table base address at 0x08000000 */
	NVIC_SetVectorTable( NVIC_VectTab_FLASH, 0x0 );

	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );

	/* Configure HCLK clock as SysTick clock source. */
	SysTick_CLKSourceConfig( SysTick_CLKSource_HCLK );
	
	serialPortInit();
}

/*-----------------------------------------------------------*/
void vmainTask1Function( void * param)
{
	while(1) {
	  task1Run = 1;
	  task2Run = 0;
		printf("1");
	}
}

void vmainTask2Function( void * param)
{
//	char buf[100];
//	int i = 0;
	
	while(1) {
	  task1Run = 0;
	  task2Run = 1;
		printf("2");
		
//		for(i = 0; i < 100; i ++) {
//		  buf[i] = 1;
//		}
	}
}

void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
                                    StackType_t ** ppxIdleTaskStackBuffer,
                                    uint32_t * pulIdleTaskStackSize )
{
    *ppxIdleTaskTCBBuffer = &xIdleTaskBuffer;
    *ppxIdleTaskStackBuffer = puxIdleTaskStackBuffer;
    *pulIdleTaskStackSize = 100;
}

实验现象如下图所示:

当把任务1的优先级设为2时,则一直执行,现象如下图:

栈大小

在堆上申请内存的格式如下:

头部包括申请内存长度等信息,同时栈的生长方向为向下生长,即高地址到低地址。 当局部变量超过栈大小后,将导致程序崩溃。

有关任务创建的详细分析见文章https://blog.csdn.net/tmd182/article/details/135976194

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值