FreeRTOS记录(三、RTOS任务调度原理解析_Systick、PendSV、SVC)(下)

举报
矜辰所致 发表于 2022/09/28 10:20:41 2022/09/28
【摘要】 虽然是FreeRTOS的为基础说明,但是原理上来说对于其他RTOS也是一样!

(本文承接上文)

本 FreeRTOS 专栏记录的开发环境:

FreeRTOS记录(一、熟悉开发环境以及CubeMX下FreeRTOS配置)
FreeRTOS 记录(二、FreeRTOS 任务 API 认识和源码简析)
FreeRTOS 记录(三、RTOS 任务调度原理解析 _Systick、PendSV、SVC)(上)

PendSV和SVC异常

PendSV异常用于任务切换。

为了保证操作系统的实时性,除了使用Systick的时间片调度,还得加入pendSV异常加入抢占式调度。

PendSV(可挂起的系统调用),异常编号为14,可编程。可以写入中断控制和状态寄存器(ICSR)设置挂起位以触发 PendSV异常。它是不精确的。因此,它的挂起状态可以在更高优先级异常处理内设置,且会在高优先级处理完成后执行。

为什么需要 PendSV异常?

如下图所示,如果中断请求在Systick异常前产生,则Systick可能会抢占IRQ处理(图中的IRQ优先级小于Systick)。这样执行上下文切换会导致IRQ延时处理,这种行为在任何一种实时操作系统中都是不能容忍的,在CortexM3中如果OS在活跃时尝试切入线程模式,将触发Fault异常。

在这里插入图片描述

为了解决上面的问题,使用了 PendSV异常。 PendSV异常会自动延迟上下文切换的请求,直到其他的eISR都完成了处理后才放行。为实现这个机制,需要把 PendSV编程为最低优先级的异常。

在FreeRTOS中,每一次进入Systick中断,系统都会检测是否有新的进入就绪态的任务需要运行,如果有,则悬挂PendSV异常,来缓期执行上下文切换。

如下:

在这里插入图片描述

在Systick中会挂起一个PendSV异常用于上下文切换,每产生一个Systick,系统检测到任务链表变化都会触发一个PendSV如下图:

在这里插入图片描述

PendSV业务流程

中断过程中不但要像一般的C函数调用一样保存(R0-R3,R12,LR,PSR),还要保存中断返回地址(return address)。中断的硬件机制会把EXC_RETURN放进LR,在中断返回时触发中断返回。

如下图:

在这里插入图片描述
如何触发PendSV异常

触发PendSV异常,向PendSV中断寄存器写1,触发一次PendSV异常。用户可以主动调用portYIELD函数进行任务切换,portYIELD函数如下:

/* Scheduler utilities. */
#define portYIELD() 															\
{																				\
	/* Set a PendSV to request a context switch. */								\
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;								\
																				\
	/* Barriers are normally not required but do ensure the code is completely	\
	within the specified behaviour for the architecture. */						\
	__asm volatile( "dsb" ::: "memory" );										\
	__asm volatile( "isb" );													\
}

PendSV源码简析

PendSV中断服务函数

我这里使用的是M0 内核中 FreeRTOS的源码xPortPendSVHandler

void xPortPendSVHandler( void )
{
	/* This is a naked function.
		1.产生PendSV中断,硬件自动保存栈帧到任务A的栈中
		2.读取当前任务A的栈指针PSP,手动把一些寄存器压栈到当前任务栈。
		3.把当前任务A栈顶指针保存到任务A的任务控制块中。
		4.找到下一个任务B的任务控制块。(查找下一个优先级最高的就绪任务)
		5.把任务B控制块的栈顶指针指向的数据弹出到寄存器中
		6.更新PSP为任务B的栈顶指针。
		7.跳出PendSV中断。
		8.硬件自动弹出任务B栈中的栈帧。
 */
	__asm volatile
	(
	"	.syntax unified						\n" 
	"	mrs r0, psp							\n"/*将psp值放到r0,此时sp得值为msp*/
	"										\n"
	"	ldr	r3, pxCurrentTCBConst			\n" /* Get the location of the current TCB. 获取当前任务控制块,其实就获取任务栈顶 */
	"	ldr	r2, [r3]						\n"/*将r3寄存器值作为指针取内容存到r2,此时r2保存的为任务控制块首地址*/
	"										\n"
	"	subs r0, r0, #32					\n" /* Make space for the remaining low registers. */
	"	str r0, [r2]						\n" /* Save the new top of stack. */
	"	stmia r0!, {r4-r7}					\n" /* Store the low registers that are not saved automatically. */
	" 	mov r4, r8							\n" /* Store the high registers. */
	" 	mov r5, r9							\n"
	" 	mov r6, r10							\n"
	" 	mov r7, r11							\n"
	" 	stmia r0!, {r4-r7}					\n"
	"										\n"
	"	push {r3, r14}						\n"
	"	cpsid i								\n"
	"	bl vTaskSwitchContext				\n"/*执行上线文切换*/
	"	cpsie i								\n"
	"	pop {r2, r3}						\n" /* lr goes in r3. r2 now holds tcb pointer. */
	"										\n"
	"	ldr r1, [r2]						\n"
	"	ldr r0, [r1]						\n" /* The first item in pxCurrentTCB is the task top of stack. */
	"	adds r0, r0, #16					\n" /* Move to the high registers. */
	"	ldmia r0!, {r4-r7}					\n" /* Pop the high registers. */
	" 	mov r8, r4							\n"
	" 	mov r9, r5							\n"
	" 	mov r10, r6							\n"
	" 	mov r11, r7							\n"
	"										\n"
	"	msr psp, r0							\n" /* Remember the new top of stack for the task.记住新的栈顶指针 */
	"										\n"
	"	subs r0, r0, #32					\n" /* Go back for the low registers that are not automatically restored. */
	" 	ldmia r0!, {r4-r7}					\n" /* Pop low registers.  */
	"										\n"
	"	bx r3								\n"
	"										\n"
	"	.align 4							\n"
	"pxCurrentTCBConst: .word pxCurrentTCB	  "
	);
}

PendSV上下文切换函数

xPortPendSVHandler中调用的上下文切换vTaskSwitchContext,其核心任务就是找到当前处于就绪态的最高优先级的任务:

void vTaskSwitchContext( void )
{
	if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
	{
		/* The scheduler is currently suspended - do not allow a context
		switch. 
		标记调度器状态
		*/
		xYieldPending = pdTRUE;
	}
	else
	{
		xYieldPending = pdFALSE;
		traceTASK_SWITCHED_OUT();

		#if ( configGENERATE_RUN_TIME_STATS == 1 )
		{
		}
		#endif /* configGENERATE_RUN_TIME_STATS */

		/* 
		Check for stack overflow, if configured. 
		检查任务栈是否溢出
		*/
		taskCHECK_FOR_STACK_OVERFLOW();

		/* 
		Select a new task to run using either the generic C or port
		optimised asm code. 
		选择优先级最高的任务,把当前的任务控制块进行赋值
		*/
		taskSELECT_HIGHEST_PRIORITY_TASK();
		traceTASK_SWITCHED_IN();

		#if ( configUSE_NEWLIB_REENTRANT == 1 )
		{
		}
		#endif /* configUSE_NEWLIB_REENTRANT */
	}
}

寻找最高优先级函数

上下文切换vTaskSwitchContext中调用了taskSELECT_HIGHEST_PRIORITY_TASK()寻找最高优先级的任务:

taskSELECT_HIGHEST_PRIORITY_TASK()的硬件方式:

	/*-----------------------------------------------------------*/
	/*
	这里注释解释
	#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) ucPortCountLeadingZeros( ( uxReadyPriorities ) ) )

	#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );} 

	#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
	{																							\
	List_t * const pxConstList = ( pxList );		
		// pxIndex 为上一个任务索引,下一个要执行的即pxNext											\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
		if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
		{		
			// 由于是环形列表,切默认有一个结束项(xListEnd),如果pxIndex刚好为最后一项,则再指向后面一项																				\
			( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
		}																						\
		( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											\
	}
	由于相同优先级的任务可能会存在多个,需要从就绪任务列表中找到位于最前面的任务
	将其赋值给pxCurrentTCB。
	*/
	#define taskSELECT_HIGHEST_PRIORITY_TASK()														\
	{																								\
	UBaseType_t uxTopPriority;																		\
																									\
		/* Find the highest priority list that contains ready tasks. */								\
		portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );								\
		configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );		\
		listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );		\
	} /* taskSELECT_HIGHEST_PRIORITY_TASK() */

taskSELECT_HIGHEST_PRIORITY_TASK()的通用方式:

	/*-----------------------------------------------------------*/

	#define taskSELECT_HIGHEST_PRIORITY_TASK()															\
	{
	/* uxTopReadyPriority 在每次把任务添加到就绪列表的时候会更新*/ 										\
	UBaseType_t uxTopPriority = uxTopReadyPriority;														\
																										\
		/* Find the highest priority queue that contains ready tasks. 
		一个优先级一个列表,查看当前最高优先级就绪列表下是否有任务 
		*/																								\
		while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )							\
		{																								\
			configASSERT( uxTopPriority );																\
			/* 如果当前最高优先级就绪列表没任务就查看下一个优先级列表 */ 									\
			--uxTopPriority;																			\
		}																								\
																										\
		/* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of						\
		the	same priority get an equal share of the processor time. 
		 获取下一个优先级最高任务的任务控制块*/															\
		listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );			\
		uxTopReadyPriority = uxTopPriority;																\
	} /* taskSELECT_HIGHEST_PRIORITY_TASK */

SVC异常

SVC(请求管理调用),异常编号为11,可编程。SVC产生的中断必须立即得到相应,否则将触发硬Fault。

系统调用处理异常,用户与内核进行交互,用户想做一些内核相关功能的时候必须通过SVC异常,让内核处于异常模式,才能调用执行内核的源码。触发SVC异常,会立即执行SVC异常代码。

下面的启动源码简析中我们可以知道系统在启动调度器函数vTaskStartSchedulerp最后运行到 rvPortStartFirstTask中会调用SVC并启动第一个任务。

为什么要用SVC启动第一个任务?

因为使用了OS,任务都交给内核。总不能像裸机调用普通函数一样启动一个任务。

M4只在上电的触发SVC异常,在SVC异常中启动第一个任务,只上电运行一次,M0上没有。

在这里插入图片描述

SVC源码简析

M0上面没用,特意生成了一个M4的来看看源码vPortSVCHandler

void vPortSVCHandler( void )
{
	__asm volatile (
					/* 获取当前任务控制块. 
					任务控制块的第一成员是------任务的栈顶
					获取到栈顶之后,剩下的事就是出栈工作
					出栈--------任务的堆栈
					*/
					"	ldr	r3, pxCurrentTCBConst2		\n" /* Restore the context. */
					"	ldr r1, [r3]					\n" /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
					"	ldr r0, [r1]					\n" /* The first item in pxCurrentTCB is the task top of stack. */
					"	ldmia r0!, {r4-r11, r14}		\n" /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. 出栈内核寄存器 R14其实就是异常返回值
					表示异常退出后,使用PSP*/
					"	msr psp, r0						\n" /* Restore the task stack pointer.更新栈指针到PSP */
					"	isb								\n"
					"	mov r0, #0 						\n"
					"	msr	basepri, r0					\n"/* 把basepri赋值为0,打开屏蔽中断 */
					"	bx r14							\n"
					"									\n"
					"	.align 4						\n"
					"pxCurrentTCBConst2: .word pxCurrentTCB				\n"
				);
				/*
				为什么没有恢复其他寄存器????其他在出栈的时候就会自动恢复(由硬件处理)
				最终跳转到任务的执行函数里面
				*/
}

FreeRTOS多任务启动源码简析

通过main.c中的main函数调用了osKernelStart();

osStatus osKernelStart (void)
{
  vTaskStartScheduler();
  
  return osOK;
}

vTaskStartScheduler

创建空闲任务,启动任务调度器vTaskStartScheduler

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,
												configIDLE_TASK_NAME,
												ulIdleTaskStackSize,
												( void * ) NULL, /*lint !e961.  The cast is not redundant for all compilers. */
												portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
												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,
								configIDLE_TASK_NAME,
								configMINIMAL_STACK_SIZE,
								( void * ) NULL,
								portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
								&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 )
	{
		/* freertos_tasks_c_additions_init() should only be called if the user
		definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
		the only macro called by the function. */
		#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
		{
			freertos_tasks_c_additions_init();
		}
		#endif

		/* 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. 
		此处关闭中断,以确保不会发生滴答声
        在呼叫 xPortstarts 计划之前或期间 ()。 堆栈
        创建的任务包含打开中断的状态字
        因此,当第一个任务完成时,中断会自动重新启用
        开始运行。
		*/
		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 ) configINITIAL_TICK_COUNT; 	//初始化 系统的节拍值为0

		/* 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.   NOTE:  If configGENERATE_RUN_TIME_STATS
		is set to 0 and the following line fails to build then ensure you do not
		have portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() defined in your
		FreeRTOSConfig.h file. */
		portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

		traceTASK_SWITCHED_IN();

		/* 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. 
		只有在无法启动内核时才能到达此行,
        因为没有足够的自由 BrTAS 堆来创建空闲任务
        或时间器任务。
		*/
		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

启动调度器xPortStartScheduler

/*
 * See header file for description.
 */
BaseType_t xPortStartScheduler( void )
{
	/*
	前面一大段不用深入了解,先不看,我们找到配置 systick pendsv开始 
	*/
	/* configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0.
	See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */
	configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );

	/* This port can be used on all revisions of the Cortex-M7 core other than
	the r0p1 parts.  r0p1 parts should use the port from the
	/source/portable/GCC/ARM_CM7/r0p1 directory. */
	configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
	configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );

	#if( configASSERT_DEFINED == 1 )
	{
		volatile uint32_t ulOriginalPriority;
		volatile uint8_t * const pucFirstUserPriorityRegister = ( volatile uint8_t * const ) ( 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;
		}

		#ifdef __NVIC_PRIO_BITS
		{
			/* Check the CMSIS configuration that defines the number of
			priority bits matches the number of priority bits actually queried
			from the hardware. */
			configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
		}
		#endif

		#ifdef configPRIO_BITS
		{
			/* Check the FreeRTOS configuration that defines the number of
			priority bits matches the number of priority bits actually queried
			from the hardware. */
			configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
		}
		#endif

		/* 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. 
	配置 systick pendsv为最低的优先级,为了保证系统的实时性
	*/
	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.
	1、初始化systick----》配置为1ms的中断产生时基
	2、开启systick中断 
	*/
	vPortSetupTimerInterrupt();

	/* 
	Initialise the critical nesting count ready for the first task. 
	初始化关键嵌套计数,为第一个任务做好准备。
	临界段?
	*/
	uxCriticalNesting = 0;

	/*
	Ensure the VFP is enabled - it should be anyway. 
	初始化浮点数寄存器 M4特有,需要初始化
	*/
	vPortEnableVFP();

	/* Lazy save always. */
	*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;

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

	/* Should never get here as the tasks will now be executing!  Call the task
	exit error function to prevent compiler warnings about a static function
	not being called in the case that the application writer overrides this
	functionality by defining configTASK_RETURN_ADDRESS.  Call
	vTaskSwitchContext() so link time optimisation does not remove the
	symbol. */
	vTaskSwitchContext();
	prvTaskExitError();

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

prvPortStartFirstTask

启动第一个任务prvPortStartFirstTask

static void prvPortStartFirstTask( void )
{
	/* Start the first task.  This also clears the bit that indicates the FPU is
	in use in case the FPU was used before the scheduler was started - which
	would otherwise result in the unnecessary leaving of space in the SVC stack
	for lazy saving of FPU registers. */
	__asm volatile(
					/* 
						0xE000ED08 它是中断向量表的一个地址
						它存储的是MSP的指针
						最终获取到MSP的RAM的地址
					*/
					" ldr r0, =0xE000ED08 	\n" /* Use the NVIC offset register to locate the stack. */
					" ldr r0, [r0] 			\n"
					" ldr r0, [r0] 			\n"
					/* 
						Set the msp back to the start of the stack. 
						重新把MSP的地址,赋值为MSP 
						为什么需要加这一步,如果我们有在线升级功能
						使用了我们用户的Bootloder,
						中断向量表会更新,所以要重新赋值MSP
					*/
					" msr msp, r0			\n" /* Set the msp back to the start of the stack. 把MSP的地址,赋值为MSP */
					" mov r0, #0			\n" /* Clear the bit that indicates the FPU is in use, see comment above. */
					" msr control, r0		\n"
					" cpsie i				\n" /* Globally enable interrupts. 开启全局中断 */
					" cpsie f				\n"
					" dsb					\n"
					" isb					\n"
					" svc 0					\n" /* System call to start first task. 调用SVC */
					" nop					\n"
				);
}
/*-----------------------------------------------------------*/
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。