FreeRTOS任务创建与任务切换

任务控制块数据结构

任务控制块数据结构在task.c声明

typedef struct tskTaskControlBlock
{
	volatile StackType_t * pxTopOfStack; //栈顶指针
	ListItem_t xStateListItem; //任务节点
	StackType_t * pxStack; //任务栈起始地址
	char pcTaskName[configMAX_TASK_NAME_LEN];//任务名称
}tskTCB;

任务创建函数

下面是用静态方式创建任务的函数

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);
	//项下做8字节对齐
	pxTopOfStack = (StackType_t*) (  (uint32_t)pxTopOfStack & (~(uint32_t)0x0007) );
	//存储名字
	for(x=(UBaseType_t)0;x<(UBaseType_t)configMAX_TASK_NAME_LEN;x++)
	{
		pxNewTCB->pcTaskName[x] = pcName[x];
		if(pcName[x]=='\0')
			break;
	}
	pxNewTCB->pcTaskName[configMAX_TASK_NAME_LEN-1] = '\0';
	
	//初始化TCB中的xStateListItem列表项
	vListInitialiseItem(& (pxNewTCB->xStateListItem));
	
	//设置xStateListItem列表项的拥有者即为传入的TCB
	listSET_LIST_ITEM_OWNER( &(pxNewTCB->xStateListItem), pxNewTCB );
	
	//初始化任务栈,并返回更新的栈顶指针
	pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack,pxTaskCode,pvParameters);
	
	//返回任务句柄
	if((void*)pxCreatedTask !=NULL )
	{
			*pxCreatedTask = (TaskHandle_t) pxNewTCB;
	}
}

下面看pxPortInitialiseStack是怎么初始化任务栈的
因为是向下生长的满栈,所以是--操作

#define portINITIAL_XPSR (0x01000000)
#define portSTART_ADDRESS_MASK ((StackType_t)0xfffffffeUL)
StackType_t * pxPortInitialiseStack(StackType_t* pxTopOfStack, TaskFunction_t pxCode, void*pvParameters)
{
    //-------------设置内核会自动加载的寄存器,且顺序不能变
	pxTopOfStack--;
	*pxTopOfStack = portINITIAL_XPSR;//xPSR bit24
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;//R15 PC
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError;//R14 LR
	pxTopOfStack -= 5; //R12 R3 R2 R1 
	*pxTopOfStack = ( StackType_t ) pvParameters;//R0
	
	//--------------下面8个需要手动保存
	pxTopOfStack-=8;
	
	//返回更新后的栈顶指针
	return pxTopOfStack;
}

如下图
在这里插入图片描述

定义就绪表

task.c中,就绪表就是列表类型的数组,元素个数是configMAX_PRIORITIES,就绪表把同一优先级的任务插入到同一优先级的链表中,数组下标表示优先级,如下图

#define configMAX_PRIORITIES   5
List_t pxReadyTasksLists[configMAX_PRIORITIES];

就绪表初始化

就绪表初始化就是把数组每个元素(即列表)初始化(即调用vListInitialise),结果如下

void prvInitialiseTaskLists(void)
{
	UBaseType_t uxPriority;
	for(uxPriority = (UBaseType_t)0 ;
	    uxPriority < (UBaseType_t) configMAX_PRIORITIES;
	    uxPriority ++)
	{
			vListInitialise(&(pxReadyTasksLists[uxPriority]));
	}
}

在这里插入图片描述

启动调度器

调用关系,这里手动指定第一个运行的任务
vTaskStartScheduler->xPortStartScheduler->prvStartFirstTask

void vTaskStartScheduler(void)
{
	//手动指定一个和要运行的任务
	pxCurrentTCB = &Task1TCB;
	//启动调度器
	if(xPortStartScheduler()!=pdFALSE)
	{
		//启动成功不会跑到这里
	}
}
//stm32 用4bit表示优先级,所以最低优先级则是15
#define configKERNEL_INTERRUPT_PRIORITY 15 

#define portNVIC_SYSPRI2_REG (*((volatile uint32_t*) 0xe000ed20))
#define portNVIC_PENDSV_PRI  (((uint32_t)configKERNEL_INTERRUPT_PRIORITY)<<16UL)
#define portNVIC_SYSTICK_PRI  (((uint32_t)configKERNEL_INTERRUPT_PRIORITY)<<24UL)
BaseType_t xPortStartScheduler(void)
{
	//把pendsv systick中断优先级设置为最低
	portNVIC_SYSPRI2_REG |=portNVIC_PENDSV_PRI;
	portNVIC_SYSPRI2_REG |=portNVIC_SYSTICK_PRI;
	
	//启动第一个任务,该函数不会返回
	prvStartFirstTask();
	//不会运行到这里
	return 0;
}

在CM3中,0xE000ED08存放的是SCB_VTOR寄存器地址,SCB_VTOR是向量表的起始地址,即MSP的地址

__asm void prvStartFirstTask(void)
{
	PRESERVE8
	
	ldr r0, =0xE000ED08 //r0=0xE000ED08,相当于r0=&SCB_VTOR
	ldr r0, [r0]        //r0=*r0,即r0=*((uint32_t*)0xE000ED08),即r0=0x0
	ldr r0, [r0]        //r0=*r0,即r0=*((uint32_t*)0x0),即r0=msp指针值
	
	msr msp, r0         //msp=r0,msp指针获得msp地址
	
	cpsie i   //开中断
	cpsie f   //开中断
	dsb
	isb
	
	svc 0    //触发SVC系统调用,此后会进入SVCHandler
	nop      //下面这2个nop不会执行
	nop
}
__asm void vPortSVCHandler(void)
{
	extern pxCurrentTCB;
	PRESERVE8
	
	//r3=&pxCurrentTCB,注意pxCurrentTCB本身就是一个指针,是指向一个任务的TCB指针
	ldr r3, =pxCurrentTCB  
	
	//r1=*r3,即r1=pxCurrentTCB
	ldr r1, [r3]           
	
	//r0=*r1,此时r1=pxCurrentTCB,
	//又结构体第一个成员(栈顶指针)的地址和结构体地址数值上是相同的,
	//所以r0=*(&(pxCurrentTCB.pxTopOfStack)),即r0=pxCurrentTCB.pxTopOfStack,
	//即r0此时指向当前任务空闲栈顶位置
	ldr r0, [r1]           
	
	//以r0为开始把r4-r11依次保存到当前任务的psp栈
	ldmia r0!,{r4-r11}
	//更新当前任务psp栈指针
	msr psp,r0
	isb
	mov r0, #0
	msr basepri,r0 //开中断
    //此时r14是0xFFFF_FFF9,表示返回后进入Thread mode,使用MSP,返回Thmub状态,
    //这里其实是把bit2置1,要求返回后使用PSP
	orr r14,#0x0d  
	//返回,这里会自动加载初始化任务栈填写的xPSR、R15、R14、R12、R3-R0值到内核对应寄存器,
	//所以返回后就直接到pxCurrentTCB指向的任务
	bx r14  
}

任务切换

这里手动触发任务切换,其实就是向中断控制及状态寄存器ICSR(地址0xE000_ED04)PENDSVSET位(bit28)写1悬起pendsv中断,在pendsv_handler中寻找下一个要运行的任务并做任务切换
在这里插入图片描述

#define taskYIELD() portYIELD()
#define portNVIC_INT_CTRL_REG (*((volatile uint32_t*)0xe000ed04))
#define portNVIC_PENDSVSET_BIT (1<<28UL)
#define portSY_FULL_READ_WRITE (15)
#define portYIELD() \
{ \
   //触发一次pendsv中断
  	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
	  __dsb(portSY_FULL_READ_WRITE); \
	  __isb(portSY_FULL_READ_WRITE); \
}
__asm void xPortPendSVHandler(void)
{
    //进入中断前会自动保存xPSR、R15、R14、R12、R3-R0到当前任务P栈中,此时任务栈顶指针指向要保存R11的位置
	extern pxCurrentTCB;
	extern vTaskSwitchContext;//这个函数是用来寻找下一个要运行的任务
	PRESERVE8
	
	mrs r0,psp  //当前任务栈psp指针存入r0,
	isb
	
	ldr r3,=pxCurrentTCB //r3=&pxCurrentTCB 
	ldr r2,[r3]          //r2=*r3, 即r2=pxCurrentTCB
	
	//以r0开始递减的手动保存r4-r11
	stmdb r0!,{r4-r11}
	//r0=*r2,此时r2=pxCurrentTCB,
	//又结构体第一个成员(栈顶指针)的地址和结构体地址数值上是相同的,
	//所以r0=*(&(pxCurrentTCB.pxTopOfStack)),即r0=pxCurrentTCB.pxTopOfStack,
	//即r0此时指向当前任务空闲栈顶位置
	str r0,[r2]
	//到这里上文就保存完成
	
	//将r3、r14压入栈保存起来(此时sp使用的是msp)
	//r14要保存是因为等下要调用函数,避免r14被覆盖无法从pendsv中断正常返回
	//r3要保存是因为r3保存的是当前正在运行任务控制块的二级指针&pxCurrentTCB,后面要通过r3来操作pxCurrentTCB来切到下文
	stmdb sp!,{r3,r14}
	//关中断,阈值是configMAX_SYSCALL_INTERRUPT_PRIORITY
	mov r0,#configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri,r0
	
	dsb
	isb

    //调用vTaskSwitchContext,功能是找到优先级最高的任务,然后让pxCurrentTCB = &优先级最高任务TCB,我们这里手动指定
	bl vTaskSwitchContext
    //开中断
	mov r0,#0
	msr basepri,r0

    //从msp中恢复r3、r14
	ldmia sp!,{r3,r14}
	
	//r1=*r3,此时r3=&pxCurrentTCB
	//即r1=pxCurrentTCB
	ldr r1,[r3] 
  
    //r0=*r1则r0=pxCurrentTCB.pxTopOfStack 理由前面讲过
	ldr r0,[r1]

    //这里把pxCurrentTCB保存的需要手动加载的值加载到内核的r4-r11
	ldmia r0!,{r4-r11}

    //更新加载了r4-r11的任务栈到psp
	msr psp,r0 
	isb
	//返回,这里会以psp自动加载保存了xPSR、R15、R14、R12、R3-R0值到内核的xPSR、R15、R14、R12、R3-R0寄存器,所以返回的是pxCurrentTCB任务
	bx r14
	nop
}

这里为了简单手动指定TCB

extern TCB_t Task1TCB;
extern TCB_t Task2TCB;
void vTaskSwitchContext(void)
{
  if(pxCurrentTCB == &Task1TCB)
	{
		pxCurrentTCB = &Task2TCB;
	}
	else
	{
		pxCurrentTCB = &Task1TCB;
	}
}

至此任务切换原理讲完

main.c

extern List_t pxReadyTasksLists[configMAX_PRIORITIES];

portCHAR flag1;
portCHAR flag2;

TaskHandle_t Task1_Handle;
StackType_t Task1Stack[128];
TCB_t Task1TCB;

TaskHandle_t Task2_Handle;
StackType_t Task2Stack[128];
TCB_t Task2TCB;

void delay(uint32_t x)
{
	for(;x!=0;x--);
}

void Task1_Fntry(void *arg)
{
	while(1)
	{
	  flag1=1;
	  delay(100);
	  flag1=0;
	  delay(100);
      taskYIELD();//手动触发切换任务
	}
}
void Task2_Fntry(void *arg)
{
	while(1)
	{
	 flag2=1;
	 delay(100);
	 flag2=0;
	 delay(100);
     taskYIELD();//手动触发切换任务
	}
}

int main(void)
{
    //初始化就绪列表
    prvInitialiseTaskLists();
		
    //创建任务1
	Task1_Handle = xTaskCreateStatic(Task1_Fntry,"task1",128,NULL,Task1Stack,&Task1TCB);
	//将任务添加到就绪列表
	vListInsertEnd(&pxReadyTasksLists[1],&((&Task1TCB)->xStateListItem));
		
	//创建任务2
	Task2_Handle = xTaskCreateStatic(Task2_Fntry,"task2",128,NULL,Task2Stack,&Task2TCB);
	//将任务添加到就绪列表
	vListInsertEnd(&pxReadyTasksLists[2],&((&Task2TCB)->xStateListItem));
		
	//启动调度器
	vTaskStartScheduler();
	for(;;);
}

仿真工程

https://download.csdn.net/download/weixin_41572450/12266952

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值