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

以上就是FreeRTOS实时操作系统的任务创建与任务切换的详细内容,更多关于FreeRTOS任务创建与切换的资料请关注我们其它相关文章!

(0)

相关推荐

  • FreeRTOS实时操作系统的任务概要讲解

    目录 1. 任务和协程(Co-routines) 1.1任务的特性 1.2任务概要 2. 任务状态 3.任务优先级 4.实现一个任务 5.空闲任务和空闲任务钩子(idle task和Idle Task hook) 5.1空闲任务 5.2空闲任务钩子 1. 任务和协程(Co-routines) 应用程序可以使用任务也可以使用协程,或者两者混合使用,但是任务和协程使用不同的API函数,因此在任务和协程之间不能使用同一个队列或信号量传递数据. 通常情况下,协程仅用在资源非常少的微处理器中,特别是RAM

  • FreeRTOS任务控制API函数的功能分析

    目录 1.相对延时 1.1函数描述 1.2参数描述 1.3用法举例 2.绝对延时 2.1函数描述 2.2参数描述 2.3用法举例 3.获取任务优先级 3.1函数描述 3.2参数描述 3.3返回值 3.4用法举例 4.设置任务优先级 4.1函数描述 4.2参数描述 4.3用法举例 5.任务挂起 5.1函数描述 5.2参数描述 5.3用法举例 6.恢复挂起的任务 6.1函数描述 6.2参数描述 7.恢复挂起的任务(在中断服务函数中使用) 7.1函数描述 7.2参数描述 7.3返回值 7.4用法举例

  • freertos实时操作系统空闲任务阻塞延时示例解析

    阻塞态:如果一个任务当前正在等待某个外部事件,则称它处于阻塞态. rtos中的延时叫阻塞延时,即任务需要延时的时候,会放弃CPU的使用权,进入阻塞状态.在任务阻塞的这段时间,CPU可以去执行其它的任务(如果其它的任务也在延时状态,那么 CPU 就将运行空闲任务),当任务延时时间到,重新获取 CPU 使用权,任务继续运行. 空闲任务:处理器空闲的时候,运行的任务.当系统中没有其他就绪任务时,空闲任务开始运行,空闲任务的优先级是最低的. 空闲任务 定义空闲任务: #define portSTACK_

  • FreeRTOS实时操作系统的任务创建和删除

    目录 前言 1.任务创建 1.1函数描述 1.2参数描述 1.3返回值 1.4用法举例 2.任务删除 2.1任务描述 2.2参数描述 前言 在FreeRTOS移植到Cortex-M3硬件平台的文章中,我们已经见过任务创建API,但那篇文章的重点在于如何移植FreeRTOS,本文将重点放在任务的创建和删除API函数上面. 任务创建和删除API函数位于文件task.c中,需要包含task.h头文件. 1.任务创建 1.1函数描述 BaseType_t xTaskCreate( TaskFunctio

  • FreeRTOS实时操作系统多任务管理基础知识

    目录 什么是多任务系统? FreeRTOS  任务与协程 1.任务(Task) 的特性 2.协程(Co-routine)的特性 任务状态 运行态 就绪态 阻塞态 挂起态 任务优先级 任务实现 任务控制块 任务堆栈 RTOS 系统的核心就是任务管理,FreeRTOS 也不例外,而且大多数学习 RTOS 系统的工程师或者学生主要就是为了使用 RTOS 的多任务处理功能,初步上手 RTOS 系统首先必须掌握的也是任务的创建.删除.挂起和恢复等操作,由此可见任务管理的重要性. 什么是多任务系统? 回想一

  • FreeRTOS实时操作系统的任务创建与任务切换

    目录 任务控制块数据结构 任务创建函数 定义就绪表 就绪表初始化 启动调度器 任务切换 任务控制块数据结构 任务控制块数据结构在task.c声明 typedef struct tskTaskControlBlock { volatile StackType_t * pxTopOfStack; //栈顶指针 ListItem_t xStateListItem; //任务节点 StackType_t * pxStack; //任务栈起始地址 char pcTaskName[configMAX_TAS

  • FreeRTOS实时操作系统的多优先级实现

    目录 如何实现任务多优先级 软件通用方法和硬件指令方法 如何实现任务多优先级 FreeRTOS中,数字优先级越小,逻辑优先级也越小,空闲任务优先级为0.List_t pxReadyTasksLists[configMAX_PRIORITIES]是数组,数组下标代表任务优先级,任务创建是根据设置的任务优先级插入到对应下标的列表根节点上,如下. 要支持多优先级,就是再任务切换时让pxCurrentTCB指向最高优先级的TCB即可,之前时手动再任务1.任务2来回切换,现在问题就是怎么找到优先级最高的就

  • FreeRTOS实时操作系统的任务应用函数详解

    目录 1.获取任务系统状态 1.1函数描述 1.2参数描述 1.3返回值 1.4用法举例 2.获取当前任务句柄 2.1函数描述 2.2返回值 3.获取空闲任务句柄 3.1函数描述 3.2返回值 4.获取任务堆栈最大使用深度 4.1函数描述 4.2参数描述 4.3返回值 4.4用法举例 5.获取任务状态 5.1函数描述 5.2参数描述 5.3返回值 6.获取任务描述内容 6.1函数描述 6.2参数描述 6.3返回值 7.获取系统节拍次数 7.1函数描述 7.2返回值 8.获取调度器状态 8.1函数

  • FreeRTOS实时操作系统的内核控制示例解析

    目录 前言 1.强制上下文切换宏 2.进入临界区宏 3.退出临界区宏 4.禁止可屏蔽中断宏 5.使能可屏蔽中断宏 6.启动调度器 6.1函数描述 7.停止调度器 7.1函数描述 8.挂起调度器 8.1函数描述 9.恢复被挂起的调度器 9.1函数描述 9.2返回值 9.3用法举例 10.调整系统节拍 10.1函数描述 10.2参数描述 10.3用法举例 前言 内核控制的一些功能需要移植层提供,为了方便移植,这些API函数用宏来实现,比如上下文切换.进入和退出临界区.禁止和使能可屏蔽中断.内核控制函

  • FreeRTOS实时操作系统的内存管理分析

    目录 1.heap_1.c 功能简介: 2.heap_2.c 功能简介: 3.heap_3.c 功能简介: 4.heap_4.c 功能简介: 5.heap_5.c(V8.1.0新增) 前言 本文介绍内存管理的基础知识,详细源码分析见< FreeRTOS内存管理示例分析> FreeRTOS提供了几个内存堆管理方案,有复杂的也有简单的.其中最简单的管理策略也能满足很多应用的要求,比如对安全要求高的应用,这些应用根本不允许动态内存分配的. FreeRTOS也允许你自己实现内存堆管理,甚至允许你同时使

  • FreeRTOS实时操作系统的任务通知方法

    目录 前言 1.发送通知-方法1 1.1函数描述 1.2参数描述 1.3返回值 2.发送通知-方法2 2.1函数描述 2.2参数描述 2.3用法举例 3.获取通知 3.1函数描述 3.2参数描述 3.3返回值 4.等待通知 4.1函数描述 4.2参数描述 4.3返回值 4.4用法举例 5.任务通知并查询 5.1函数描述 5.2参数描述 5.3返回值 前言 注:本文介绍任务通知的基础知识,详细源码分析见FreeRTOS进阶<FreeRTOS高级篇8---FreeRTOS任务通知分析> 每个RTO

  • FreeRTOS实时操作系统的列表与列表项操作示例

    目录 前言 列表项数据结构 列表项初始化 列表数据结构 将列表项按照升序排列插入到列表 将列表项从列表删除 前言 FreeRTOS列表与列表项其实就是链表和节点,在list.c和list.h实现 列表项数据结构 //列表项数据结构 typedef struct xLIST_ITEM { TickType_t xItemValue; //辅助值,用作节点做顺序排序 struct xLIST_ITEM * pxNext;//后继指针 struct xLIST_ITEM * pxPrevious;//

  • FreeRTOS实时操作系统特点介绍

    目录 1.什么是FreeRTOS? 2.为什么择 选择 FreeRTOS ? 3.FreeRTOS 特点 FreeRTOS资料与源码下载 FreeRTOS源码文件介绍 1.什么是FreeRTOS? Free 即免费的,RTOS 全称是 Real Time Operating System,中文就是实时操作系统.注意,RTOS 不是指某一个确定的系统,而是指一类系统.比如 uC/OS,FreeRTOS,RTX,RT-Thread 等这些都是 RTOS 类操作系统. 操作系统允许多个任务同时运行,这

随机推荐