FreeRTOS 笔记之④:任务的定义与任务的切换

本文深入探讨FreeRTOS的任务定义、创建和调度过程。从任务栈、任务函数、任务控制块的定义到任务创建函数的实现,详细解析了xTaskCreateStatic()、prvInitialiseNewTask()和pxPortInitialiseStack()等关键步骤。接着,介绍了就绪列表的定义、初始化和任务插入,以及调度器的启动和任务切换机制,包括taskYIELD()、xPortPendSVHandler()和TaskSwitchContext()等函数的作用。最后,文章提到了SVC和PendSV在系统调用中的角色。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1. 任务是什么

2. 创建任务

2.1 定义任务栈

2.2 定义任务函数

2.3 定义任务控制块

2.4  实现任务创建函数

2.4.1 xTaskCreateStatic()函数

2.4.2 prvInitialiseNewTask()函数

2.4.3 pxPortInitialiseStack()函数

3. 实现就绪列表

3.1 定义就绪列表

3.2 就绪列表初始化

3.3 将任务插入到就绪列表

4. 实现调度器

4.1 启动调度器

4.1.1 vTaskStartScheduler()函数

4.1.2 xPortStartScheduler()函数

4.1.3 prvStartFirstTask()函数

4.1.4 vPortSVCHandler()函数

4.2 任务切换

4.2.1 taskYIELD()

4.2.2 xPortPendSVHandler()函数

4.2.3 TaskSwitchContext()函数

5. 例程试验

6. SVC 和PendSV


1. 任务是什么

在裸机系统中,系统的主体就是main函数里面顺序执行的无限循环,这个无限循环里面CPU按照顺序完成各种事情。在多任务系统中,我们根据功能的不同,把整个系统分割成一个个独立的且无法返回的函数,这个函数我们称为任务。

任务(task),是抽象的东西,并没有一个严格的定义,一般是指由软件完成的一个活动。对于类似freeRTOS的系统来说,任务即线程/进程。

任务的大概形式具体见代码

void task_entry( void * parg)
{
    /* 任务主体,无限循环且不能返回 */
    for (;;) {
        /* 任务主体代码 */
    }
}

2. 创建任务

2.1 定义任务栈

在一个裸机系统中,如果有全局变量,有子函数调用,有中断发生。那么系统在运行的时候,全局变量放在哪里,子函数调用时,局部变量放在哪里,中断发生时,函数返回地址放哪里。如果只是单纯的裸机编程,它们放哪里我们不用管,但是如果要写一个RTOS,这些种种环境参数,我们必须弄清楚他们是如何存储的。在裸机系统中,他们统统放在一个叫栈的地方,栈是单片机RAM里面一段连续的内存空间,栈的大小一般在启动文件或者链接脚本里面指定,最后由C库函数_main进行初始化。

但是,在多任务系统中,每个任务都是独立的,互不干扰的,所以要为每个任务都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于RAM中。

实现两个变量按照一定的频率轮流的翻转,每个变量对应一个任务,那么就需要定义两个任务栈,具体见代码清单。在多任务系统中,有多少个任务就需要定义多少个任务栈。

#define TASK1_STACK_SIZE                    128
StackType_t Task1Stack[TASK1_STACK_SIZE];

#define TASK2_STACK_SIZE                    128
StackType_t Task2Stack[TASK2_STACK_SIZE];

任务栈其实就是一个预先定义好的全局数据,数据类型为 StackType_t,大小由TASK1_STACK_SIZE这个宏来定义,默认为128,单位为字,即512字节,这也是FreeRTOS推荐的最小的任务栈。

2.2 定义任务函数

任务是一个独立的函数,函数主体无限循环且不能返回。

/* 软件延时 */
void delay (uint32_t count)
{
	for(; count!=0; count--);
}
/* 任务1 */
void Task1_Entry( void *p_arg )
{
	for( ;; )
	{
		flag1 = 1;
		delay( 100 );		
		flag1 = 0;
		delay( 100 );
	}
}

/* 任务2 */
void Task2_Entry( void *p_arg )
{
	for( ;; )
	{
		flag2 = 1;
		delay( 100 );		
		flag2 = 0;
		delay( 100 );
	}
}

2.3 定义任务控制块

在裸机系统中,程序的主体是CPU按照顺序执行的。而在多任务系统中,任务的执行是由系统调度的。系统为了顺利的调度任务,为每个任务都额外定义了一个任务控制块,这个任务控制块就相当于任务的身份证,里面存有任务的所有信息,比如任务的栈指针,任务名称,任务的形参等。有了这个任务控制块之后,以后系统对任务的全部操作都可以通过这个任务控制块来实现。定义一个任务控制块需要一个新的数据类型,该数据类型在task.c这C头文件中声明

typedef struct tskTaskControlBlock
{
	volatile StackType_t    *pxTopOfStack;    /* 栈顶 */

	ListItem_t			    xStateListItem;   /* 任务节点 */
    
    StackType_t             *pxStack;         /* 任务栈起始地址 */
	                                          /* 任务名称,字符串形式 */
	char                    pcTaskName[ configMAX_TASK_NAME_LEN ];  
} tskTCB;
typedef tskTCB TCB_t;
TCB_t Task1TCB;

TCB_t Task2TCB;

2.4  实现任务创建函数

任务的栈,任务的函数实体,任务的控制块最终需要联系起来才能由系统进行统一调度。那么这个联系的工作就由任务创建函数xTaskCreateStatic()来实现,该函数在task.c定义。

2.4.1 xTaskCreateStatic()函数

#if( configSUPPORT_STATIC_ALLOCATION == 1 )

TaskHandle_t xTaskCreateStatic(	TaskFunction_t pxTaskCode,           /* 任务入口 */
					            const char * const pcName,           /* 任务名称,字符串形式 */
					            const uint32_t ulStackDepth,         /* 任务栈大小,单位为字 */
					            void * const pvParameters,           /* 任务形参 */
					            StackType_t * const puxStackBuffer,  /* 任务栈起始地址 */
					            TCB_t * const pxTaskBuffer )         /* 任务控制块指针 */
{
	TCB_t *pxNewTCB;
	TaskHandle_t xReturn;

	if( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) )
	{		
		pxNewTCB = ( TCB_t * ) pxTaskBuffer; 
		pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer;

		/* 创建新的任务 */
		prvInitialiseNewTask( pxTaskCode,        /* 任务入口 */
                              pcName,            /* 任务名称,字符串形式 */
                              ulStackDepth,      /* 任务栈大小,单位为字 */ 
                              pvParameters,      /* 任务形参 */
                              &xReturn,          /* 任务句柄 */ 
                              pxNewTCB);         /* 任务栈起始地址 */      

	}
	else
	{
		xReturn = NULL;
	}

	/* 返回任务句柄,如果任务创建成功,此时xReturn应该指向任务控制块 */
    return xReturn;
}

#endif /* configSUPPORT_STATIC_ALLOCATION */
  • FreeRTOS中,任务的创建有两种方法,一种是使用动态创建,一种是使用静态创建。动态创建时,任务控制块和栈的内存是创建任务时动态分配的,任务删除时,内存可以释放。静态创建时,任务控制块和栈的内存需要事先定义好,是静态的内存,任务删除时,内存不能释放。目前以静态创建为例来讲解,configSUPPORT_STATIC_ALLOCATION在FreeRTOSConfig.h中定义,我们配置为1。
  • 任务入口,即任务的函数名称。TaskFunction_t是在projdefs.h(projdefs.h第一次使用需要在include文件夹下面新建然后添加到工程freertos/source这个组文件)中重定义的一个数据类型,实际就是空指针。
#ifndef PROJDEFS_H
#define PROJDEFS_H

typedef void (*TaskFunction_t)( void * );

#define pdFALSE			( ( BaseType_t ) 0 )
#define pdTRUE			( ( BaseType_t ) 1 )

#define pdPASS			( pdTRUE )
#define pdFAIL			( pdFALSE )


#endif /* PROJDEFS_H */
  • 调用prvInitialiseNewTask()函数,创建新任务,该函数在task.c实现

2.4.2 prvInitialiseNewTask()函数

static void prvInitialiseNewTask( 	TaskFunction_t pxTaskCode,              /* 任务入口 */
									const char * const pcName,              /* 任务名称,字符串形式 */
									const uint32_t ulStackDepth,            /* 任务栈大小,单位为字 */
									void * const pvParameters,              /* 任务形参 */
									TaskHandle_t * const pxCreatedTask,     /* 任务句柄 */
									TCB_t *pxNewTCB )                       /* 任务控制块指针 */

{
	StackType_t *pxTopOfStack;
	UBaseType_t x;	
	
	/* 获取栈顶地址 */
	pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
	//pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
	/* 向下做8字节对齐 */
	pxTopOfStack = ( StackType_t * )
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值