09_FreeRTOS任务调度器

news2024/11/18 22:28:04

目录

开启任务调度器vTaskStartScheduler函数

xPortStartScheduler开启任务调度器函数

启动第一个任务

prvStartFirstTask开启第一个任务函数

vPortSVCHandler  SVC中断服务函数

出栈/压栈汇编指令详解


开启任务调度器vTaskStartScheduler函数

作用:用于启动任务调度器,任务调度器启动后, FreeRTOS 便会开始进行任务调度 

该函数内部实现,如下步骤:

1.创建空闲任务

2.如果使能软件定时器,则创建定时器任务

3.关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断

4.初始化全局变量,并将任务调度器的运行标志设置为已运行

5.初始化任务运行时间统计功能的时基定时器

6.调用函数xPortStartScheduler()

void vTaskStartScheduler( void )
{
BaseType_t xReturn;

	/* Add the idle task at the lowest priority. */
	/*静态任务创建*/
	#if( configSUPPORT_STATIC_ALLOCATION == 1 )
	{
		/*
		StaticTask_t *pxIdleTaskTCBBuffer = NULL;
		StackType_t *pxIdleTaskStackBuffer = NULL;
		uint32_t ulIdleTaskStackSize;

		/* The Idle task is created using user provided RAM - obtain the
		address of the RAM then create the idle task. */
		vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
		xIdleTaskHandle = xTaskCreateStatic(	prvIdleTask,
												"IDLE",
												ulIdleTaskStackSize,
												( void * ) NULL,
												( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
												pxIdleTaskStackBuffer,
												pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */

		if( xIdleTaskHandle != NULL )
		{
			xReturn = pdPASS;
		}
		else
		{
			xReturn = pdFAIL;
		}*/
	}

	#else
	/*动态任务创建*/
	{
		/* The Idle task is being created using dynamically allocated RAM. */
		
		/*创建空闲任务*/
		xReturn = xTaskCreate(	prvIdleTask,
								"IDLE", configMINIMAL_STACK_SIZE,
								( void * ) NULL,
								( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
								&xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
	}
	#endif /* configSUPPORT_STATIC_ALLOCATION */

	/*判断宏是否使能软件定时器*/
	#if ( configUSE_TIMERS == 1 )
	{
		/*空闲任务创建成功*/
		if( xReturn == pdPASS )
		{
			/*创建定时器任务*/
			xReturn = xTimerCreateTimerTask();
		}
		else
		{
			/*未实现*/
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configUSE_TIMERS */

	/*判断定时器任务是否创建成功*/
	if( xReturn == pdPASS )
	{
		/* Interrupts are turned off here, to ensure a tick does not occur
		before or during the call to xPortStartScheduler().  The stacks of
		the created tasks contain a status word with interrupts switched on
		so interrupts will automatically get re-enabled when the first task
		starts to run. */
		
		/*关中断*/
		portDISABLE_INTERRUPTS();

		#if ( configUSE_NEWLIB_REENTRANT == 1 )
		{
			/* Switch Newlib's _impure_ptr variable to point to the _reent
			structure specific to the task that will run first. */
			_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
		}
		#endif /* configUSE_NEWLIB_REENTRANT */

		/*初始化全局变量*/
		/*初始化下一个任务阻塞超时时间,因为时开启调度器没有任务运行,
		也就是没有阻塞时间所以直接赋值为最大*/
		xNextTaskUnblockTime = portMAX_DELAY;
		/*任务调度器正在运行*/
		xSchedulerRunning = pdTRUE;
		/*系统节拍*/
		xTickCount = ( TickType_t ) 0U;

		/* If configGENERATE_RUN_TIME_STATS is defined then the following
		macro must be defined to configure the timer/counter used to generate
		the run time counter time base. */
		
		/*统计任务运行时间未实现*/
		portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

		/* Setting up the timer tick is hardware specific and thus in the
		portable interface. */
		/*启动任务调度器,与硬件架构相关的配置部分,以及开启第一个任务*/
		/*运行这个函数时不会返回直接跳读第一个任务去执行*/
		if( xPortStartScheduler() != pdFALSE )
		{
			/* Should not reach here as if the scheduler is running the
			function will not return. */
		}
		else
		{
			/* Should only reach here if a task calls xTaskEndScheduler(). */
		}
	}
	else
	{
		/* This line will only be reached if the kernel could not be started,
		because there was not enough FreeRTOS heap to create the idle task
		or the timer task. */
		configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
	}

	/* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
	meaning xIdleTaskHandle is not used anywhere else. */
	( void ) xIdleTaskHandle;
}
/*-----------------------------------------------------------*/

xPortStartScheduler开启任务调度器函数

作用:该函数用于完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务

该函数内部实现,如下:

1.检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误

2.配置PendSV和SysTick的中断优先级为最低优先级

3.调用函数vPortSetupTimerlnterrupt()配置SysTick

4.初始化临界区嵌套计数器为0

5.调用函数prvEnableVFP()使能FPU6(M3没有)、调用函数prvStartFirstTask()启动第一个任务

BaseType_t xPortStartScheduler( void )
{
	/*检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误*/
	#if( configASSERT_DEFINED == 1 )
	{
		volatile uint32_t ulOriginalPriority;
		volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
		volatile uint8_t ucMaxPriorityValue;

		/* Determine the maximum priority from which ISR safe FreeRTOS API
		functions can be called.  ISR safe functions are those that end in
		"FromISR".  FreeRTOS maintains separate thread and ISR API functions to
		ensure interrupt entry is as fast and simple as possible. 

		Save the interrupt priority value that is about to be clobbered. */
		ulOriginalPriority = *pucFirstUserPriorityRegister;

		/* Determine the number of priority bits available.  First write to all
		possible bits. */
		*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;

		/* Read the value back to see how many bits stuck. */
		ucMaxPriorityValue = *pucFirstUserPriorityRegister;

		/* Use the same mask on the maximum system call priority. */
		ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;

		/* Calculate the maximum acceptable priority group value for the number
		of bits read back. */
		ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
		while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
		{
			ulMaxPRIGROUPValue--;
			ucMaxPriorityValue <<= ( uint8_t ) 0x01;
		}

		/* Shift the priority group value back to its position within the AIRCR
		register. */
		ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
		ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;

		/* Restore the clobbered interrupt priority register to its original
		value. */
		*pucFirstUserPriorityRegister = ulOriginalPriority;
	}
	#endif /* conifgASSERT_DEFINED */

	/* Make PendSV and SysTick the lowest priority interrupts. */
	/*配置PendSV和SysTick的中断优先级为最低优先级*/
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;

	/* Start the timer that generates the tick ISR.  Interrupts are disabled
	here already. */
	
	/*初始化滴答定时器*/
	vPortSetupTimerInterrupt();

	/* Initialise the critical nesting count ready for the first task. */
	
	/*初始化临界区嵌套计数器为0*/
	uxCriticalNesting = 0;

	/* Start the first task. */
	/*启动第一个任务*/
	prvStartFirstTask();

	/* Should not get here! */
	return 0;
}

启动第一个任务

假设我们要启动的第一个任务是任务A,那么就需要将任务A的寄存器值加载到CPU寄存器中,任务A的寄存器值,在一开始创建任务时就保存在任务堆栈里边!

注意:

1.中断产生时,硬件自动将xPSR, PC(R15), LR(R14), R12, R3-R0保存和恢复;而R4~R11需要手动保存和恢复

2.进入中断后硬件会强制使用MSP指针,此时LR(R14)的值将会被自动被更新为特殊的EXC_RETURN

prvStartFirstTask开启第一个任务函数

prvStartFirstTask开启第一个任务函数

用于初始化启动第一个任务前的环境,主要是重新设置MSP指针,并使能全局中断

1.什么是MSP指针?

程序在运行过程中需要一定的栈空间来保存局部变量等一些信息当有信息保存到栈中时,MCU会自动更新SP(R13)指针, ARM Cortex-M内核提供了两个栈空间:

主堆栈指针(MSP):它由OS内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。

进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)。

在FreeRTOS中,中断使用MSP (主堆栈) ,中断以外使用PSP (进程堆栈)

__asm void prvStartFirstTask( void )
{
	/*1首先是使用了 PRESERVE8,进行 8 字节对齐,这是因为,
	栈在任何时候都是需要 4 字节对齐的,而在调用入口得8字节对齐,
	在进行C编程的时候,编译器会自动完成的对齐的操作,而对于汇编,
	就需要开发者手动进行对齐。*/
	
	/*8字节对齐*/
	PRESERVE8

	/* Use the NVIC offset register to locate the stack. */
	
	/*向量表开始寄存器地址*/
	ldr r0, =0xE000ED08
	/* 获取向量表的值*/
	ldr r0, [r0]
	/*获取MSP的初始值(栈底指针)*/
	ldr r0, [r0]

	/* Set the msp back to the start of the stack. */
	/*初始化MSP*/
	msr msp, r0
	/* Globally enable interrupts. */
	
	/*使能全局中断*/
	cpsie i
	cpsie f
	dsb
	isb
	/* Call SVC to start the first task. */
	
	/*触发svc中断启动第一个任务*/
	svc 0
	nop
	nop
}

vPortSVCHandler  SVC中断服务函数

注意:SVC中断只在启动第一次任务时会调用一次,以后均不调用

当使能了全局中断,并且手动触发SVC中断后,就会进入到SVC的中断服务函数中

1.通过 pxCurrentTCB 获取优先级最高的就绪态任务的任务栈地址,优先级最高的就绪态任务是系统将要运行的任务。

2.通过任务的栈顶指针,将任务栈中的内容出栈到CPU寄存器中,任务栈中的内容在调用任务创建函数的时候,已初始化,然后设置 PSP 指针。

3.通过往 BASEPRI 寄存器中写 0,允许中断。

4. R14是链接寄存器LR,在ISR中(此刻我们在SVC的ISR中) ,它记录了异常返回值EXC_RETURN而EXC_RETURN只有6个合法的值(M4、M7),如下表所示:

__asm void vPortSVCHandler( void )
{
	/*首先是使用了 PRESERVE8,进行 8 字节对齐,这是因为,
	栈在任何时候都是需要 4 字节对齐的,而在调用入口得8字节对齐,
	在进行C编程的时候,编译器会自动完成的对齐的操作,而对于汇编,
	就需要开发者手动进行对齐。*/

	/*8字节对齐*/	
	PRESERVE8

	/*获取当前任务优先级最高的任务地址*/
	ldr	r3, =pxCurrentTCB	/* Restore the context. */
	/*通过任务地址获取首成员地址*/
	ldr r1, [r3]			/* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
	/*通过首成员获取首成员值*/	
	ldr r0, [r1]			/* The first item in pxCurrentTCB is the task top of stack. */
	/*出栈,把任务控制块里的变量赋值到CPU的R4-R11*/
	ldmia r0!, {r4-r11}		/* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
	/*设置PSP为任务栈指针此时R0地址是R11,把R0地址赋值给PSP,
	为的是退出中断时由硬件恢复其他寄存器*/
	msr psp, r0				/* Restore the task stack pointer. */
	isb
	
	/*basepri等于0的时候使能所有中断*/
	mov r0, #0
	msr	basepri, r0

	/*使用 PSP 指针,并跳转到任务函数*/	
	orr r14, #0xd
/*最后通过 r14指令,跳转到任务的任务函数中执行*/
	bx r14
}

出栈/压栈汇编指令详解

1.出栈(恢复现场),方向:从下往上(低地址往高地址):假设r0地址为0x04汇编指令示例:

/*任务栈r0地址由低到高,将r0存储地址里面的内容手动加载到CPU寄存器r4、r5、r6*/

Idmia r0!, {r4-r6}

r0地址(0x04)内容加载到r4,加载完后地址需要加4此时地址r0= r0+4 = 0x08

R0地址(0x08)内容加载到r5,加载完后地址需要加4此时地址r0=r0+4 = 0x0C

R0地址(0x0C)内容加载到r6,加载完后地址需要加4此时地址r0=r0+4 = 0x10

 

2.压栈(保存现场),方向:从上往下(高地址往低地址):假设r0地址为0x10汇编指令示例:

/*r0的存储地址由高到低递减,将r4、r5、r6里的内容存储到r0的任务栈里面。*/

stmdb r0!, {r4-r6}}

地址: r0=r0-4 = 0x0C,将r6的内容(寄存器值)存放到r0所指向地址(0x0C)

地址: r0=r0-4 = 0x08,将r5的内容(寄存器值)存放到r0所指向地址(0x08)

地址: r0=r0-4 = 0x04,将r4的内容(寄存器值)存放到r0所指向地址(0x08)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/191874.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LaoCat带你认识容器与镜像(实践篇一上)

实践篇主要以各容器的挂载和附加命令为主。 本章内容 Dockerfile基础命令详解。 本文实操全部基于Ubuntu 20.04 宿主机 > linux服务器本身 Docker > 20.10.22 从该章开始&#xff0c;我们就进入了Docker实践篇系列了&#xff0c;主要介绍Docker相关附加命令&#xff0c;…

Python中的字符串多样的操作方法

文章目录1.字符串中大小写字母的转变2.字符串的左右中对齐3.字符串查找的方法4.字符串的替换5. 字符串的判断6.字符串的截取7.字符串的拆分8.字符串的拼接9.格式化字符串10.格式化字符串的语法10.12.1 对齐选项&#xff08;[align]&#xff09;10.2 填充选项&#xff08;[fill]…

切入点(pointcut)的申明规则

Spring AOP 用户可能会经常使用 execution切入点指示符。执行表达式的格式如下&#xff1a;execution&#xff08;modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern&#xff08;param-pattern&#xff09; throws-pattern?&#xff09;ret-type-p…

HandlerInterceptorAdapter拦截器多个拦截器时执行的顺序

前提&#xff1a;拦截器加载顺序是 1 2 3.Configuration public class InterceptorConfig implements WebMvcConfigurer {Autowiredprivate Test1neInterceptor test1neInterceptor;Autowiredprivate Test2neInterceptor test2neInterceptor;Overridepublic void addIntercepto…

【Android -- 开源库】权限适配 RxPermissions 的基本使用

运行时权限 从 Android 6.0&#xff08;API 级别 23&#xff09;开始&#xff0c;用户开始在应用运行时向其授予权限&#xff0c;而不是在应用安装时授予。 系统权限分为两类&#xff1a; 正常权限&#xff1a;只需在你应用的 Androidmanifest.xml 中列出&#xff0c;安装时…

大数据---Hadoop安装教程(一)

Hadoop安装教程前期工作 以下步骤基于网络配置完成&#xff0c;并且能连接xshell和xftp工具 Linux安装: Linux—CentOS 7 64位安装教程 Xshell连接: Linux—Xshell连接 文章目录Hadoop安装教程前期工作安装vim编辑器免密登录ssh登录过程免密登录的原理操作1.免密登录2.开启远程…

比较排序——三路快速排序

快速排序&#xff08;Quicksort&#xff09;&#xff0c;计算机科学词汇&#xff0c;适用领域Pascal&#xff0c;c等语言&#xff0c;是对冒泡排序算法的一种改进。一、单路排序流程快速排序算法通过多次比较和交换来实现排序&#xff0c;其排序流程如下&#xff1a; (1)首先设…

e^(-x^2)的原函数(本求法为负面典型,仅供参考)

先放正确答案 ∫−∞∞e−x2π\int _{-\infin} ^{\infin} e^{-x^{2}}\sqrt{\pi}∫−∞∞​e−x2π​ 证法有许多 这是选自点我的一个证明方法。 首先要明确这是一个超越函数 求不了原函数 所以以下部分……纯属失败案例 注&#xff1a;原本在desmos上完成的推导&#xff0c;复制…

stable-diffusion-webui的基础功能手动安装,了解代码结构、依赖、模型出处

前言 虽然&#xff0c;当前有很多stable-diffusion-webui 的一键安装包&#xff0c;但是不易于彻底理解该项目 主要介绍了&#xff0c;手动安装 stable-diffusion-webui 基础功能的过程&#xff0c;手动安装&#xff0c;了解代码结构、依赖、模型出处 一、手动下载stable-dif…

2023 如何学好 Python web 开发?走心局送给大家

1.走心 我在没有接触互联网这个行业的时候&#xff0c; 就一直好奇网站是怎么构建的。 我现在虽然从事互联网相关的工作&#xff0c; 但是也一直没有接触过Web开发之类的东西&#xff0c; 不过兴趣终归还是要有的&#xff0c;还要自己动手去实践的。 Web开发的途径有好多种&am…

Linux 三剑客命令之 awk 详解

目录 一、Awk 实战讲解 1.1 awk 的原理 1.2 BEGIN 和 END 模块 1.3 运算符 1.4 常用 awk 内置变量 1.5 awk 正则 1.6 awk 常用字符串函数 一、Awk 实战讲解 awk 是一种很棒的语言&#xff0c;它适合文本处理和报表生成&#xff0c;其语法较为常见&#xff0c;借鉴了某些…

Fisco Bcos区块链五(后端区块链应用开发框架及流程)

文章目录项目框架&#xff1a;开发流程&#xff1a;1、创建一个Java工程2、引入FISCO BCOS Java SDK3、配置SDK证书4、业务逻辑开发第一步.将3编译好的Java合约引入项目中第二步.开发业务逻辑5. 运行应用项目框架&#xff1a; |-- build.gradle // gradle配置文件 |-- gradle …

element-ui组件的使用,导航菜单(NavMenu)组件、分页组件的使用

一、ElementUI 1、简介&#xff1a;是饿了么公司推出的基于Vue2的组件库 2、使用方法&#xff1a;具体可见官网https://element.eleme.cn/#/zh-CN/component/installation ​ &#xff08;1&#xff09;在vue项目中安装&#xff1a;npm install element-ui ​ &#xff08;…

win10 在线、离线安装microsoft store 版 WSL

实验环境&#xff1a; 操作系统&#xff1a;windows版本 10.0.19045.2486 WSL版本&#xff1a;Microsoft.WSL_1.0.3.0_x64_ARM64.msixbundle 离线安装 获取WSL安装包&#xff1a; 在此页面获取发布网址&#xff1a; Microsoft Store 中适用于 Linux 的 Windows 子系统的发行…

深度卷积对抗神经网络 基础 第七部分 StyleGAN

深度卷积对抗神经网络 基础 第七部分 StyleGAN 深度卷积神经网络模型已经应用在非常多的领域&#xff0c;但是其总包含了很多潜在的问题&#xff0c;比如说训练速度过慢&#xff0c;生成器与判别器的进化程度不平衡等等。那么&#xff0c;随着各种方法和算法的普及和进化&…

就业秘籍!这些软件测试的面试话术你要知道

近些年&#xff0c;各行各业找工作都不太容易&#xff0c;虽然身处技术岗位的软件测试因企业需求大&#xff0c;要比之其他行业容易&#xff0c;但&#xff0c;稍有不慎&#xff0c;也会让求职者与自己心仪的offer失之交臂&#xff0c;因此&#xff0c;大家在准备前面前&#x…

关键点匹配——商汤LoFTR源码详解

源码地址见文末 1.项目与参数配置解读 首先,进入目录,使用pip install -r requirements.txt配置环境。 首先,对于demo的运行,首先需要准备好需要用于关键点匹配的数据,提供的代码中置于了image文件夹下,然后是训练的权重,代码中下载了室内场景和室外场景的训练权重。 …

抓包展示vlan报文(8021Q)

VLAN数据帧格式要使交换机能够分辨不同VLAN的报文&#xff0c;需要在报文中添加标识VLAN信息的字段。IEEE 802.1Q协议规定&#xff0c;在以太网数据帧的目的MAC地址和源MAC地址字段之后、协议类型字段之前加入4个字节的VLAN标签&#xff08;又称VLAN Tag&#xff0c;简称Tag&am…

什么护眼台灯比较专业?2023央视推荐的护眼灯

台灯作为最常见的照明工具&#xff0c;也是因为有许多孩子都有近视的现象&#xff0c;从儿童青少年的近视人数可以看出&#xff0c;我国的近视人数是全国第一的&#xff0c;所以选择一款好的护眼台灯是很关键的&#xff0c;有哪些专业的护眼台灯呢&#xff1f;挑选台灯最主要是…

vue 使用 wangeditor 富文本编辑器

wangeditor 是一个轻量级 web 富文本编辑器&#xff0c;配置方便&#xff0c;使用简单。 1&#xff09;安装 wangeditor 终端安装 wangeditor 库&#xff1a; yarn add wangeditor/editor # 或者 npm install wangeditor/editor --save2&#xff09;页面绑定 创建一个 xxx.…