FreeRTOS 任务调度及相关函数详解(一)

2023-05-16

文章目录

  • 一、任务调度器开启函数 vTaskStartScheduler()
  • 二、内核相关硬件初始化函数 xPortStartScheduler()
  • 三、启动第一个任务 prvStartFirstTask()
  • 四、中断服务函数 xPortPendSVHandler()
  • 五、空闲任务


一、任务调度器开启函数 vTaskStartScheduler()

这个函数的功能就是开启任务调度器的,这个函数在文件 tasks.c中有定义,缩减后的函数代码如下:

void vTaskStartScheduler( void )
{
	BaseType_t xReturn;
	xReturn = xTaskCreate( prvIdleTask, (1)
						"IDLE", configMINIMAL_STACK_SIZE,
						( void * ) NULL,
						( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
						&xIdleTaskHandle );
	#if ( configUSE_TIMERS == 1 ) //使用软件定时器使能
	{
		if( xReturn == pdPASS )
		{
			xReturn = xTimerCreateTimerTask(); (2)
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configUSE_TIMERS */
	
	if( xReturn == pdPASS ) //空闲任务和定时器任务创建成功。
	{
		portDISABLE_INTERRUPTS(); (3)
		#if ( configUSE_NEWLIB_REENTRANT == 1 ) //使能 NEWLIB
		{
			_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
		}
		#endif /* configUSE_NEWLIB_REENTRANT */
		xNextTaskUnblockTime = portMAX_DELAY;
		xSchedulerRunning = pdTRUE; (4)
		xTickCount = ( TickType_t ) 0U;
		portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); (5)
		if( xPortStartScheduler() != pdFALSE ) (6)
		{
			//如果调度器启动成功的话就不会运行到这里,函数不会有返回值的
		}
		else
		{
			//不会运行到这里,除非调用函数 xTaskEndScheduler()。
		}
	}
	else
	{
		//程序运行到这里只能说明一点,那就是系统内核没有启动成功,导致的原因是在创建
		//空闲任务或者定时器任务的时候没有足够的内存。
		configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
	}
	//防止编译器报错,比如宏 INCLUDE_xTaskGetIdleTaskHandle 定义为 0 的话编译器就会提
	//示 xIdleTaskHandle 未使用。
	( void ) xIdleTaskHandle;
}

(1)、创建空闲任务,如果使用静态内存的话使用函数 xTaskCreateStatic()来创建空闲任务,优先级为 tskIDLE_PRIORITY,宏 tskIDLE_PRIORITY 为 0,也就是说空闲任务的优先级为最低。

(2)、如果使用软件定时器的话还需要通过函数xTimerCreateTimerTask()来创建定时器服务任务。定时器服务任务的具体创建过程是在函数 xTimerCreateTimerTask()中完成的。

(3)、关闭中断,在 SVC 中断服务函数 vPortSVCHandler()中会打开中断。

(4)、变量 xSchedulerRunning 设置为 pdTRUE,表示调度器开始运行。

(5)、当宏 configGENERATE_RUN_TIME_STATS 为 1 的时候说明使能时间统计功能,此时需要用户实现宏 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS,此宏用来配置一个定时器/计数器。

(6)、调用函数 xPortStartScheduler()来初始化跟调度器启动有关的硬件,比如滴答定时器、FPU 单元和 PendSV 中断等等。

二、内核相关硬件初始化函数 xPortStartScheduler()

FreeRTOS 系统时钟是由滴答定时器来提供的,而且任务切换也会用到 PendSV 中断,这些硬件的初始化由函数 xPortStartScheduler()来完成,缩减后的函数代码如下:

BaseType_t xPortStartScheduler( void )
{
	/******************************************************************/
	/****************此处省略一大堆的条件编译代码**********************/
	/*****************************************************************/
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; (1)
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; (2)
	vPortSetupTimerInterrupt(); (3)
	uxCriticalNesting = 0; (4)
	prvStartFirstTask(); (5)
	//代码正常执行的话是不会到这里的!
	return 0;
}

(1)、设置 PendSV 的中断优先级,为最低优先级。

(2)、设置滴答定时器的中断优先级,为最低优先级。

(3)、调用函数 vPortSetupTimerInterrupt()来设置滴答定时器的定时周期,并且使能滴答定时器的中断,函数比较简单,大家自行查阅分析。

(4)、初始化临界区嵌套计数器。

(5)、调用函数 prvStartFirstTask()开启第一个任务。

三、启动第一个任务 prvStartFirstTask()

经过上面的操作以后我们就可以启动第一个任务了,函数 prvStartFirstTask()用于启动第一个任务,这是一个汇编函数,函数源码如下:

__asm void prvStartFirstTask( void )
{
	PRESERVE8
	ldr r0, =0xE000ED08 ;R0=0XE000ED08 (1)
	ldr r0, [r0] ;取 R0 所保存的地址处的值赋给 R0 (2)
	ldr r0, [r0] ;获取 MSP 初始值 (3)
	msr msp, r0 ;复位 MSP (4)
	cpsie I ;使能中断(清除 PRIMASK) (5)
	cpsie f ;使能中断(清除 FAULTMASK) (6)
	dsb ;数据同步屏障 (7)
	isb ;指令同步屏障 (8)
	svc 0 ;触发 SVC 中断(异常) (9)
	nop
	nop
}

(1)、将 0XE000ED08 保存在寄存器 R0 中。一般来说向量表应该是从起始地址(0X00000000)开始存储的,不过,有些应用可能需要在运行时修改或重定义向量表,Cortex-M 处理器为此提供了一个叫做向量表重定位的特性。向量表重定位特性提供了一个名为向量表偏移寄存器(VTOR)的可编程寄存器。VTOR 寄存器的地址就是 0XE000ED08,通过这个寄存器可以重新定义向量表,比如在 STM32F103 的 ST 官方库中会通过函数 SystemInit()来设置 VTOR 寄存器,

代码如下:

SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; //VTOR=0x08000000+0X00

通过上面一行代码就将向量表开始地址重新定义到了0X08000000,向量表的起始地址存储的就是 MSP 初始值。

(2)、读取 R0 中存储的地址处的数据并将其保存在 R0 寄存器,也就是读取寄存器 VTOR中的值,并将其保存在 R0 寄存器中。这一行代码执行完就以后 R0 的值应该为 0X08000000。

(3)、读取 R0 中存储的地址处的数据并将其保存在 R0 寄存器,也就是读取地址 0X08000000处存储的数据,并将其保存在 R0 寄存器中。我们知道向量表的起始地址保存的就是主栈指针MSP 的初始值,这一行代码执行完以后寄存器 R0 就存储 MSP 的初始值。现在来看(1)、(2)、(3)这三步起始就是为了获取 MSP 的初始值而已!

(4)、复位 MSP,R0 中保存了 MSP 的初始值,将其赋值给 MSP 就相当于复位 MSP。

(5)和(6)、使能中断。

(7)和(8)、数据同步和指令同步屏障。

(9)、调用 SVC 指令触发 SVC 中断,SVC 也叫做请求管理调用,SVC 和 PendSV 异常对于OS 的设计来说非常重要。SVC 异常由 SVC 指令触发。在 FreeRTOS 中仅仅使用 SVC 异常来启动第一
个任务,后面的程序中就再也用不到 SVC 了。

四、中断服务函数 xPortPendSVHandler()

在函数 prvStartFirstTask()中通过调用 SVC 指令触发了 SVC 中断,而第一个任务的启动就是在 SVC 中断服务函数中完成的,SVC 中断服务函数应该为 SVC_Handler(),但是
FreeRTOSConfig.h 中通过#define 的方式重新定义为了 xPortPendSVHandler(),如下:

#define xPortPendSVHandler PendSV_Handler

函数 vPortSVCHandler()在文件 port.c 中定义,这个函数也是用汇编写的,函数源码如下:

__asm void vPortSVCHandler( void )
{
	PRESERVE8
	ldr r3, =pxCurrentTCB ;R3=pxCurrentTCB 的地址 (1)
	ldr r1, [r3] ;取 R3 所保存的地址处的值赋给 R1 (2)
	ldr r0, [r1] ;取 R1 所保存的地址处的值赋给 R0 (3)
	ldmia r0!, {r4-r11, r14} ;出栈 ,R4~R11 和 R14 (4)
	msr psp, r0 ;进程栈指针 PSP 设置为任务的堆栈 (5)
	isb ;指令同步屏障
	mov r0, #0 ;R0=0 (6)
	msr basepri, r0 ;寄存器 basepri=0,开启中断 (7)
	orr r14, #0xd ; (8)
	bx r14 (9)
}

(1)、获取 pxCurrentTCB 指针的存储地址,pxCurrentTCB 是一个指向 TCB_t 的指针,这个指针永远指向正在运行的任务。这里先获取这个指针存储的地址,比如我现在的代码测试出来这个指针是存放在 0X20000044,如下图所示。
在这里插入图片描述
(2)、取 R3 所保存的地址处的值赋给 R1。通过这一步就获取到了当前任务的任务控制块的存储地址。比如当前我的程序中这个地址就为 0X20000EE8,如下图所示:
在这里插入图片描述
(3)、取 R3 所保存的地址处的值赋给 R0,我们知道任务控制块的第一个字段就是任务堆栈的栈顶指针 pxTopOfStack 所指向的位置,所以读取任务控制块所在的首地址(0X20000EE8)得到
的就是栈顶指针所指向的地址,当前我的程序中这个栈顶指针(pxTopOfStack)所指向的地址为0X20000E98,如下图所示
在这里插入图片描述
可以看出(1)、(2)和(3)的目的就是获取要切换到的这个任务的任务栈顶指针,因为任务所对应的寄存器值,也就是现场都保存在任务的任务堆栈中,所以需要获取栈顶指针来恢复这些寄存器值!

(4)、R4~R11,R14 这些寄存器出栈。这里使用了指令 LDMIA,LDMIA 指令是多加载/存储指令,不过这里使用的是具有回写的多加载/存储访问指令,用法如下:

LDMIA Rn! , {reg list}

表示从 Rn 指定的存储器位置读取多个字,地址在每次读取后增加(IA),Rn 在传输完成以后写回。对于 STM32 来说地址一次增加 4 字节,比如如下代码:

LDR R0, =0X800
LDMIA R0!, {R2~R4}

上面两行代码就是将 0X800 地址的数据赋值给寄存器 R2,0X804 地址的数据赋值给寄存器 R3,0X8008 地址的数据赋值给 R4 寄存器,然后,重点来了!此时 R0 为 800A!通过这一步我们就从任务堆栈中将 R4~R11 这几个寄存器的值给恢复了。这里有朋友就要问了,R0~R3,R12,PC,xPSR 这些寄存器怎么没有恢复?这是因为这些寄存器会在退出中断的时候 MCU 自动出栈(恢复)的,而 R4~R11 需要由用户手动出栈。这个我们在分析 PendSV 中断服务函数的时候会讲到。到这步以后我们来看一下堆栈的栈顶指针指到哪
里了?如下图所示:
在这里插入图片描述
从上图可以看出恢复 R4~R11 和 R14 以后堆栈的栈顶指针应该指向地址 0X20000EB8,也就是保存寄存器 R0 值的存储地址。退出中断服务函数以后进程栈指针 PSP 应该从这个地址开始恢复其他的寄存器值。

(5)、设置进程栈指针 PSP,PSP=R0=0X20000EB8,如下图所示:
在这里插入图片描述
(6)、设置寄存器 R0 为 0。

(7)、设置寄存器 BASEPRI 为 R0,也就是 0,打开中断!

(8)、R14 寄存器的值与 0X0D 进行或运算,得到的结果就是 R14 寄存器的新值。表示退出异常以后 CPU 进入线程模式并且使用进程栈!

(9)、执行此行代码以后硬件自动恢复寄存器 R0~R3、R12、LR、PC 和 xPSR 的值,堆栈使用进程栈 PSP,然后执行寄存器 PC 中保存的任务函数。至此,FreeRTOS 的任务调度器正式开始运行!

五、空闲任务

在前面讲解函数 vTaskStartScheduler()说过,此函数会创建一个名为“IDLE”的任务,这个任务叫做空闲任务。顾名思义,空闲任务就是空闲的时候运行的任务,也就是系统中其他的任务由于各种原因不能运行的时候空闲任务就在运行。空闲任务是 FreeRTOS 系统自动创建的,不需要用户手动创建。任务调度器启动以后就必须有一个任务运行!但是空闲任务不仅仅是为了满足任务调度器启动以后至少有一个任务运行而创建的,空闲任务中还会去做一些其他
的事情,如下:
1、判断系统是否有任务删除,如果有的话就在空闲任务中释放被删除任务的任务堆栈和任务控制块的内存。

2、运行用户设置的空闲任务钩子函数。

3、判断是否开启低功耗 tickless 模式,如果开启的话还需要做相应的处理空闲任务的任务优先级是最低的,为 0,任务函数为 prvIdleTask()。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS 任务调度及相关函数详解(一) 的相关文章

  • 详解FreeRTOS中的软件定时器

    软件定时器用于让某个任务定时执行 或者周期性执行 比如设定某个时间后执行某个函数 或者每隔一段时间执行某个函数 由软件定时器执行的函数称为软件定时器的回调函数 参考资料 Mastering the FreeRTOS Real Time Ke
  • FreeRTOS快速上手

    FreeRTOS使用 一 源码下载和移植文件提取 1 1 源码下载 在网站https sourceforge net projects freertos 可以找到freertos最新的源码 1 2 移植文件提取 根据第一步 我们会得到一个f
  • 【FreeRTOS 信号量】互斥信号量

    互斥信号量与二值信号量类似 但是互斥信号量可以解决二值信号量出现的优先级翻转问题 解决办法就是优先级继承 普通互斥信号量创建及运行 参阅安富莱电子demo 互斥信号量句柄 static SemaphoreHandle t xMutex NU
  • FreeRTOS软件定时器创建、复位、开始和停止(备忘)

    目录 一 简介 1 1 开发环境 1 2 摘要 二 STM32CubeIDE配置 三 创建定时器 3 1 头文件声明 3 2 工程文件定义 3 3 创建定时器 3 4 开启 复位 和关闭定时器 四 定时器回调函数 一 简介 1 1 开发环境
  • Freertos中vTaskDelay()是怎么用的

    1 常见的使用场景 void vLED Task void pvParameters while 1 Heartbeat LED vTaskDelay 1000 portTICK RATE MS 说明 上面这段代码的意思是 led翻转后经过
  • 【FreeRTOS开发问题】FreeRTOS内存溢出

    FreeRTOS内存溢出 如下图所示 FreeRTOS编译完成后可以看到 系统提示无法分配内存到堆 Objects Template axf Error L6406E No space in execution regions with A
  • FreeRTOS学习笔记<中断>

    中断概念 Cortex M的NVIC最多支持240个IRQ 中断请求 1个不可屏蔽中断 NMI 1个Systick 滴答定时器 定时器中断和多个系统异常 Cortex M处理器有多个用于管中断和异常的可编程寄存器 这些寄存器大多数都在 NV
  • FreeRTOS临界区

    FreeRTOS临界区是指那些必须完整运行 不能被打断的代码段 比如有的外设的初始化需要严格的时序 初始化过程中不能被打断 FreeRTOS 在进入临界区代码的时候需要关闭中断 当处理完临界区代码以后再打开中断 FreeRTOS 系统本身就
  • FreeRTOS_中断

    传送门 博客汇总帖 传送门 Cortex M3 中断 异常 传送门 Cortex M3笔记 基础 笔记内容参考 正点原子的FreeRTOS开发手册 cortex m3权威指南 Cortex M3和Cortex M4权威指南等 文中stm32
  • FreeRTOS:中断配置

    目录 一 Cortex M 中断 1 1中断简介 1 2中断管理简介 1 3优先级分组定义 1 4优先级设置 1 5用于中断屏蔽的特殊寄存器 1 5 1PRIMASK 和 FAULTMASK 寄存器 1 5 2BASEPRI 寄存器 二 F
  • 【FreeRTOS】任务通知的使用

    作者主页 凉开水白菜 作者简介 共同学习 互相监督 热于分享 多加讨论 一起进步 专栏资料 https pan baidu com s 1nc1rfyLiMyw6ZhxiZ1Cumg pwd free 点赞 收藏 再看 养成习惯 订阅的粉丝
  • 基于HAL库的FREERTOS-----------三.队列

    一 队列简介 在实际的应用中 常常会遇到一个任务或者中断服务需要和另外一个任务进行 沟通交流 这个 沟通交流 的过程其实就是消息传递的过程 在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式 但是如果在使用操作系统的应用中用
  • STM32F103移植FreeRTOS必须搞明白的系列知识---2(FreeRTOS任务优先级)

    STM32F103移植FreeRTOS必须搞明白的系列知识 1 Cortex CM3中断优先级 STM32F103移植FreeRTOS必须搞明白的系列知识 2 FreeRTOS任务优先级 STM32F103移植FreeRTOS必须搞明白的系
  • FreeRTOS之事件

    FreeRTOS之事件 声明 本人按照正点原子的FreeRTOS例程进行学习的 欢迎各位大佬指责和批评 谢谢 一 事件定义 事件 事件集 与高数上的集合意义差不多 事件啊 其实是实现任务间通信的机制 主要用于实现多任务间的同步 但是事件类型
  • FreeRTOS临界段

    1 临界段 在访问共享资源时不希望被其他任务或者中断打断的代码 这段要执行的代码称为临界段代码 2 设置临界段的目的 保护共享资源 例如 全局变量 公共函数 不可重入函数 函数里面使用 了一些静态全局变量 malloc 等 保护外设的实时性
  • 13-FreeRTOS任务创建与删除

    任务创建和删除API函数位于文件task c中 需要包含task h头文件 task h里面包函数任务的类型函数 例如 对xTaskCreate的调用 通过指针方式 返回一个TaskHandle t 变量 然后可将该变量用vTaskDele
  • FreeRTOS之系统配置

    1 FreeRTOS的系统配置文件为FreeRTOSConfig h 在此配置文件中可以完成FreeRTOS的裁剪和配置 在官方的demo中 每个工程都有一个该文件 2 先说一下 INCLUDE 开始的宏 使用 INCLUDE 开头的宏用来
  • FreeRTOSConfig.h 配置优化及深入

    本篇目标 基于上一篇的移植freertos stm32f4 freertos 上 修改 FreeRTOSConfig h 文件的相关配置来优化辅助 FreeRtos 的使用 并且建立一些基本功能 信号量 消息地列等 的简单应用位于 stm3
  • 如何将 void* 转换为函数指针?

    我在 FreeRTOS 中使用 xTaskCreate 其第四个参数 void const 是传递给新线程调用的函数的参数 void connect to foo void const task params void on connect
  • GNU Arm Cortex m4 上的 C++ 异常处理程序与 freertos

    2016 年 12 月更新现在还有一个关于此行为的最小示例 https community nxp com message 862676 https community nxp com message 862676 我正在使用带有 free

随机推荐