freertos内核--任务调度剖析

2023-05-16

前言

在使用freertos的时候,我们都知道在创建了一系列任务之后,启用调度器,系统就可以帮我们管理任务,分配资源。本文主要对调度器的原理进行剖析,从vTaskStartScheduler()函数开始,一探究竟。

freertos版本:9.0.0

启动调度器

vTaskStartScheduler()

vTaskStartScheduler()用于开启调度器,具体代码如下:

void vTaskStartScheduler( void ){
	BaseType_t xReturn;
	xReturn = xTaskCreate( prvIdleTask,
						   "IDLE",configMINIMAL_STACK_SIZE,
						   ( void * ) NULL,
						   ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
						   &xIdleTaskHandle );
	if( xReturn == pdPASS )
	{	
		portDISABLE_INTERRUPTS();
		if( xPortStartScheduler() != pdFALSE )
		{
			/* Should not reach here as if the scheduler is running the
			function will not return. */
		}	
	}			   
}

代码已做删减(只显示了核心部分,下同)。可以看到在开启调度器的时候,vTaskStartScheduler()主要做了三件事:创建空闲任务;关闭中断,确保后续工作不会被Systick打断;同时调用xPortStartScheduler()(此处以ARM_CM3内核为例)

xPortStartScheduler()

xPortStartScheduler()函数代码如下:

BaseType_t xPortStartScheduler( void )
{
extern void vPortStartFirstTask( void );

	/* Make PendSV and SysTick the lowest priority interrupts. */
	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. */
	uxCriticalNesting = 0;

	/* 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. */
	prvTaskExitError();

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

第一步是对系统Systick中断和PendSV中断进行优先级设置,均设置为最低( 为什么要设置Systick中断和PendSV中断最低优先级),第二步使能Systick中断(但是中断不会发生,因为之前关闭了中断),最后调用vPortStartFirstTask()。

执行第一个任务

vPortStartFirstTask()

vPortStartFirstTask()用于执行第一个任务。

static void prvPortStartFirstTask( void )
{
	__asm volatile(
					" ldr r0, =0xE000ED08 	\n" /* Use the NVIC offset register to locate the stack. */
					" ldr r0, [r0] 			\n"
					" ldr r0, [r0] 			\n"
					" msr msp, r0			\n" /* Set the msp back to the start of the stack. */
					" cpsie i				\n" /* Globally enable interrupts. */
					" cpsie f				\n"
					" dsb					\n"
					" isb					\n"
					" svc 0					\n" /* System call to start first task. */
					" nop					\n"
				);
}

代码分析:

ldr r0, =0xE000ED08 	
ldr r0, [r0] 			
ldr r0, [r0] 			
msr msp, r0

这4步的目的是给主堆栈的栈顶指针msp赋初值,具体操作步骤如图:
在这里插入图片描述

主堆栈指针就指向了栈顶0x200008DB。

cpsie i
cpsie f

之后开启中断和异常,让下面的SVC中断能够响应

svc 0

产生系统调用服务号为0的SVC中断

SVC中断服务函数

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}				\n" /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
					"	msr psp, r0						\n" /* Restore the task stack pointer. */
					"	isb								\n"
					"	mov r0, #0 						\n"
					"	msr	basepri, r0					\n"
					"	orr r14, #0xd					\n"
					"	bx r14							\n"
					"									\n"
					"	.align 4						\n"
					"pxCurrentTCBConst2: .word pxCurrentTCB				\n"
				);
}

代码分析:

ldr	r3, pxCurrentTCBConst2	
ldr r1, [r3]				
ldr r0, [r1]

此部分代码是将pxCurrentTCBConst2这个任务控制块指向的第一个成员的值赋值给r0。从下图任务控制块的结构体可以看到,第一个成员是栈顶指针。

在这里插入图片描述

ldmia r0!, {r4-r11}	
msr psp, r0

以r0 为基地址,将栈中向上增长的8个字的内容加载到CPU寄存
器r4~r11,同时r0 也会跟着自增。并将自增后的r0赋值给psp,如下图所示:
在这里插入图片描述

mov r0, #0
msr	basepri, r0	

basepri寄存器置0,打开所有中断

orr r14, #0xd
bx r14	

当从SVC中断服务退出前,通过向r14寄存器最后4位按位或上0x0D,使得硬件在退出时使用进程堆栈指针PSP完成出栈操作并返回后进入线程模式、返回用户级。在SVC中断服务里面,使用的是MSP堆栈指针,是处在特权级。关于msp和psp可参考这篇文章:双堆栈…的区别

退出中断,由于此时sp指针使用任务指针psp,所以在进行中断退出的出栈操作时,是以psp指针指向地址开始出栈。这一部分均由硬件完成,相应寄存器会被置位,比如PC指针会更新成新任务的入口地址。

在这里插入图片描述

在我们创建任务时,会有初始化任务控制块和任务堆栈的操作。这时候再看看任务堆栈的初始化过程,和上面对比,很多问题都会迎刃而解。
任务堆栈初始化:

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
	/* Simulate the stack frame as it would be created by a context switch
	interrupt. */
	pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
	*pxTopOfStack = portINITIAL_XPSR;	/* xPSR */
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* PC */
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) portTASK_RETURN_ADDRESS;	/* LR */
	pxTopOfStack -= 5;	/* R12, R3, R2 and R1. */
	*pxTopOfStack = ( StackType_t ) pvParameters;	/* R0 */
	pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */

	return pxTopOfStack;
}

至此,pc指针指向任务的函数地址,sp指针(此时为psp)指向任务栈的栈顶,第一个任务成功运行。

任务切换

创建完了第一个任务,我们看到之后没有代码了,那操作系统如何对其他任务调度呢?
不要忘了之前的Systick和PendSV两个中断,因为之前关中断以及SVC执行的缘故,他们一直没有起作用。SVC中断执行完之后,Systick开始执行。

Systick中断

Systick中断使能

在开启调度器的时候,我们调用了vPortSetupTimerInterrupt()来使能系统时钟Systick,代码如下:

__attribute__(( weak )) void vPortSetupTimerInterrupt( void )
{
	/* Configure SysTick to interrupt at the requested rate. */
	portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
	portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}

主要工作是设置Systick定时器的中断周期和使能Systick中断

Systick中断服务函数

中断使能后,接下来的所有关于任务切换的工作都由Systick发起。

void xPortSysTickHandler( void )
{
	portDISABLE_INTERRUPTS();
	{
		/* Increment the RTOS tick. */
		if( xTaskIncrementTick() != pdFALSE )
		{
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
		}
	}
	portENABLE_INTERRUPTS();
}

xPortSysTickHandler()函数首先进行关中断处理,然后调用xTaskIncrementTick()函数

xTaskIncrementTick()函数代码如下:

BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
	/*调度器没有挂起时执行*/
	if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
	{
		/*系统时基+1*/
		const TickType_t xConstTickCount = xTickCount + 1;
		xTickCount = xConstTickCount;
		/*若系统时间溢出(比如stm32为32位),则交换延时列表*/
		if( xConstTickCount == ( TickType_t ) 0U )
		{
			taskSWITCH_DELAYED_LISTS();
		}
		/*有任务的阻塞时间到,解锁任务*/
		if( xConstTickCount >= xNextTaskUnblockTime )
		{
			/*遍历完所有阻塞时间到的任务*/
			for( ;; )
			{
				/*判断延时列表是否为空*/
				if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
				{
					/*设置解锁时间为最大值,不让进入if( xConstTickCount >= xNextTaskUnblockTime )*/
					xNextTaskUnblockTime = portMAX_DELAY; 
					break;
				}
				else
				{
					/*获取延时列表头部的TCB。延时列表是按照各任务的解锁时间顺序排列的,所以头部任务最先解锁*/
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
					/*获取解锁时间*/
					xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
					/*因为延时列表可能存在多个解锁时间相同的任务,需要遍历完*/
					if( xConstTickCount < xItemValue )
					{
						xNextTaskUnblockTime = xItemValue;
						break;
					}

					/*将任务移出延时列表*/
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );

					/*将任务移除事件列表*/
					if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
					{
						( void ) uxListRemove( &( pxTCB->xEventListItem ) );
					}
					/*将任务加入就绪列表*/
					prvAddTaskToReadyList( pxTCB );
					/* A task being unblocked cannot cause an immediate
					context switch if preemption is turned off. */
					#if (  configUSE_PREEMPTION == 1 )
					{
						/*判断当前任务的优先级,最大则进行任务切换,否则继续运行原有任务*/
						if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
						{
							xSwitchRequired = pdTRUE;
						}
					}
					#endif /* configUSE_PREEMPTION */
				}
			}
		}

	}
	else
	{
		/*挂起时间自增*/
		++uxPendedTicks;
	}
	/*全局变量xYieldPending为任务切换标志,在其他函数中调用*/
	#if ( configUSE_PREEMPTION == 1 )
	{
		if( xYieldPending != pdFALSE )
		{
			xSwitchRequired = pdTRUE;
		}
	}
	#endif /* configUSE_PREEMPTION */
	/*返回的xSwitchRequired决定是否需要任务切换*/
	return xSwitchRequired;
}

代码分析:

const TickType_t xConstTickCount = xTickCount + 1;
	  xTickCount = xConstTickCount;

这是代码的第一部分,负责系统时基计数。每进入Systick中断一次,xTickCount值+1。例如Systick中断1ms一次,则xTickCount的值就代表系统运行的毫秒数。当然xTickCount也会有溢出的时候,这视处理器而定。溢出了则从0计数,所以对于长时间的,精确的时间统计,我们会选择其他方案。

/*若系统时间溢出(比如stm32为32位),则交换延时列表*/
if( xConstTickCount == ( TickType_t ) 0U )
{
	taskSWITCH_DELAYED_LISTS();
}

这是代码第二部分。这里涉及到了任务的延时列表。freertos采用延时列表的机制来管理阻塞任务,通过给任务设置延时来让任务进入阻塞状态。例如:任务延时A:60ms,B:70ms,C:120ms,系统会按照当前时基值设置任务的解锁时间(假设xTickCount=40)。所以,任务的解锁时间为A:40+60=100ms,B:40+70=110ms,C:40+120=160ms。同时,系统将这些任务按照解锁时间将其插入延时列表,并按顺序排列,值最小的任务代表最先被解锁加入就绪列表,就会被插入到列表最前面。如下图所示:
在这里插入图片描述
现在再来谈上面代码的含义。因为Systick存在溢出问题,比如16位系统,会在xTickCount=65535时加一溢出。为了解决这一问题,freertos采用了延时列表和延时溢出列表,比如Systick的溢出值为140,A,B任务由于解锁时间均大于xTickCount,所以都会加入延时列表;而C任务由于40+120=20(溢出)<xTickCount,会被放进溢出延时列表。如下图:
在这里插入图片描述
延时列表中的A,B任务执行完后,延时列表为空。在xTickCount溢出后,xTickCount从0开始计数,并交换两列表(指针指向C所在的列表),等C任务解锁并执行。值得注意的是,此时C所在的列表变为延时列表,而刚刚A,B所在的变为溢出列表。这就是freertos对Systick溢出采用的解决办法。

/*有任务的阻塞时间到,解锁任务*/
if( xConstTickCount >= xNextTaskUnblockTime )
{
	/*遍历完所有阻塞时间到的任务*/
	for( ;; )
	{
		/*判断延时列表是否为空*/
		if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
		{
			/*设置解锁时间为最大值,不让进入if( xConstTickCount >= xNextTaskUnblockTime )*/
			xNextTaskUnblockTime = portMAX_DELAY; 
			break;
		}
		else
		{
			/*获取延时列表头部的TCB。延时列表是按照各任务的解锁时间顺序排列的,所以头部任务最先解锁*/
			pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
			/*获取解锁时间*/
			xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
			/*因为延时列表可能存在多个解锁时间相同的任务,需要遍历完*/
			if( xConstTickCount < xItemValue )
			{
				xNextTaskUnblockTime = xItemValue;
				break;
			}

			/*将任务移出延时列表*/
			( void ) uxListRemove( &( pxTCB->xStateListItem ) );

			/*将任务移除事件列表*/
			if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
			{
				( void ) uxListRemove( &( pxTCB->xEventListItem ) );
			}
			/*将任务加入就绪列表*/
			prvAddTaskToReadyList( pxTCB );
			/* A task being unblocked cannot cause an immediate
			context switch if preemption is turned off. */
			#if (  configUSE_PREEMPTION == 1 )
			{
				/*判断当前任务的优先级,最大则进行任务切换,否则继续运行原有任务*/
				if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
				{
					xSwitchRequired = pdTRUE;
				}
			}
			#endif /* configUSE_PREEMPTION */
		}
	}
}

}
else
{
/*挂起时间自增*/
++uxPendedTicks;
}

这是代码第三部分。它判断是否有任务解锁,其中xNextTaskUnblockTime代表最近的任务解锁时间。如果有任务需要解锁,就获取延时列表头部的任务TCB,同时获取其xItemValue值(就是任务解锁时间),之后将其移出延时列表,插入就绪列表,在判断它的优先级是否最高,最高则切换任务,否则不切换。通过这样的操作,任务才能再次被调度。
当然还有一点需要注意。以上步骤是在for循环里面实现的,它的退出条件如下:

/*因为延时列表可能存在多个解锁时间相同的任务,需要遍历完*/
if( xConstTickCount < xItemValue )
{
	xNextTaskUnblockTime = xItemValue;
	break;
}

之所以出现遍历多次的情况,是因为同一时间有可能有多个任务需要解锁,也就是说它们的解锁时间是相同的,这种情况不能忽略。

/*全局变量xYieldPending为任务切换标志,在其他函数中调用*/
#if ( configUSE_PREEMPTION == 1 )
{
	if( xYieldPending != pdFALSE )
	{
		xSwitchRequired = pdTRUE;
	}
}
#endif /* configUSE_PREEMPTION */

这是代码的最后部分。这里涉及到一个全局变量xYieldPending,它是任务切换的标志,会在其它函数中调用,比如vTaskNotifyGiveFromISR(),还有之后的vTaskSwitchContext()。因为有些任务不是在xTaskIncrementTick()中解除阻塞的,而是在其他函数中解除。他们将xYieldPending置位,以达到任务切换的目的。

至此,xTaskIncrementTick()函数结束。它的返回值会作为任务切换的依据,从上面代码的分析可知,任务只有在以下两种情况下才会自动切换:有任务解锁并且其优先级最高,或者xYieldPending置位。

PendSV中断

PendSV中断通过将PENDSV位置位来触发,此前已经在Systick中断服务函数中出现过。

portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;

PendSV中断服务函数

xPortPendSVHandler()代码如下:

__asm void xPortPendSVHandler( void )
{
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

	PRESERVE8

	mrs r0, psp
	isb

	ldr	r3, =pxCurrentTCB		/* Get the location of the current TCB. */
	ldr	r2, [r3]

	stmdb r0!, {r4-r11}			/* Save the remaining registers. */
	str r0, [r2]				/* Save the new top of stack into the first member of the TCB. */

	stmdb sp!, {r3, r14}
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0
	dsb
	isb
	bl vTaskSwitchContext
	mov r0, #0
	msr basepri, r0
	ldmia sp!, {r3, r14}

	ldr r1, [r3]
	ldr r0, [r1]				/* The first item in pxCurrentTCB is the task top of stack. */
	ldmia r0!, {r4-r11}			/* Pop the registers and the critical nesting count. */
	msr psp, r0
	isb
	bx r14
	nop
}

整个代码分为两部分:保存上文切换下文
保存上文部分:

mrs r0, psp
isb

ldr	r3, =pxCurrentTCB		/* Get the location of the current TCB. */
ldr	r2, [r3]

stmdb r0!, {r4-r11}			/* Save the remaining registers. */
str r0, [r2]				/* Save the new top of stack into the first member of the TCB. */

整个过程和SVC中断差不多。
R0=psp,R3=pxCurrentTCB,R2=pxCurrentTCB->TopOfStack(任务块第一个成员为栈顶地址),保存它们是为了之后使用。
因为在进入PendSV中断时,硬件已经自动将一些寄存器入栈了,所以此时psp的指向如下图所示,只需要将R4-R11手动入栈即可。
此时的R0指向栈顶,将其保存到R2中,更新当前任务栈的栈顶,方便以后任务切换时调用。
在这里插入图片描述
中间有一行代码值得注意:

stmdb sp!, {r3, r14}

这行代码的作用时保存R3,R14寄存器的值到堆栈中。在中断中执行时,需使用主堆栈,栈顶指针为msp。之所以要保存到主堆栈中,是因为中断退出的时候,使用的是msp进行出栈操作,而执行vTaskSwitchContext()会让R14的值发生改变,所以需要入栈保护。

至于R3,他在入栈前存的是pxCurrentTCB的地址,执行vTaskSwitchContext()不能确定会不会改变R3的值,所以也入栈保护。

mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0

关中断保护,因为之后要操作全局变量pxCurrentTCB。

切换下文部分:

bl vTaskSwitchContext
mov r0, #0
msr basepri, r0
ldmia sp!, {r3, r14}

ldr r1, [r3]
ldr r0, [r1]				/* The first item in pxCurrentTCB is the task top of stack. */
ldmia r0!, {r4-r11}			/* Pop the registers and the critical nesting count. */
msr psp, r0
isb
bx r14
nop

这里首先执行vTaskSwitchContext()函数。
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;
		/* Select a new task to run using either the generic C or port
		optimised asm code. */
		taskSELECT_HIGHEST_PRIORITY_TASK();
	}
}

首先判断调度器是否挂起,再决定是否对xYieldPending进行置位。如果挂起了,则置位xYieldPending,相当于下次开启时会强制进行一次任务切换。如果没挂起,则执行taskSELECT_HIGHEST_PRIORITY_TASK()函数。

taskSELECT_HIGHEST_PRIORITY_TASK()函数代码如下:

	#define taskSELECT_HIGHEST_PRIORITY_TASK()															\
	{																									\
	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 */

此函数用于选择优先级最高的任务,全局变量pxCurrentTCB就是在这里修改的。因为优先级最高的任务列表不一定就有任务存在(任务阻塞被暂时移除),所以要遍历出此刻有任务存在列表的最高优先级,然后调用listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) )函数,注意此处传入的参数为pxCurrentTCB。

listGET_OWNER_OF_NEXT_ENTRY()代码如下:

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
{																							\
List_t * const pxConstList = ( pxList );													\
	/* Increment the index to the next item and return the item, ensuring */				\
	/* we don't return the marker used at the end of the list.  */							\
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
	{																						\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}																						\
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											\
}

因为同一优先级下可能存在多个任务,freertos的处理方法是使用时间片,从代码可以很容易看出,每进入一次这个函数,( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext都会执行一次,作用是切换到同优先级的下一个任务,让同优先级的每一个任务都能拥有相同的执行时间。

( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;

之后就是最核心的修改全局变量pxCurrentTCB,它作为参数传递给了pxTCB。至此,pxCurrentTCB更新,指向优先级最高任务。

接下来回到汇编,回到切换下文的内容。

mov r0, #0
msr basepri, r0
ldmia sp!, {r3, r14}

ldr r1, [r3]
ldr r0, [r1]				/* The first item in pxCurrentTCB is the task top of stack. */
ldmia r0!, {r4-r11}			/* Pop the registers and the critical nesting count. */
msr psp, r0
isb
bx r14
nop

首先是开中断,修改完了pxCurrentTCB要立即打开,不然会影响系统的实时性。之后的操作与入栈如出一辙,值得注意的是,之前R3,R14入栈的作用就在这里体现了,R3始终指向pxCurrentTCB的地址,R14保存的进入中断之前的处理器模式和堆栈指针。

到此,freertos的任务调度内核函数剖析结束。

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

freertos内核--任务调度剖析 的相关文章

  • FreeRTOS内核配置说明---FreeRTOS Kernel V10.2.1

    FreeRTOS内核是高度可定制的 使用配置文件FreeRTOSConfig h进行定制 每个FreeRTOS应用都必须包含这个头文件 用户根据实际应用来裁剪定制FreeRTOS内核 这个配置文件是针对用户程序的 而非内核 因此配置文件一般
  • FreeRTOS config开始的宏

    FreeRTOSConfig h系统配置文件中可以自定义 FreeRTOS h中定义默认值 configAPPLICATION ALLOCATED HEAP 默认情况下FreeRTOS的堆内存是由编译器来分配的 将宏configAPPLIC
  • FreeRTOS 软件定时器的使用

    FreeRTOS中加入了软件定时器这个功能组件 是一个可选的 不属于freeRTOS内核的功能 由定时器服务任务 其实就是一个定时器任务 来提供 软件定时器是当设定一个定时时间 当达到设定的时间之后就会执行指定的功能函数 而这个功能函数就叫
  • FreeRTOS-内核控制函数

    FreeRTOS 内核控制函数 FreeRTOS中有一些内核函数 一般来说这些内核函数在应用层不会使用 但是内核控制函数是理解FreeRTOS中断的基础 接下来我们逐一分析这些内核函数 taskYIELD 该函数的作用是进行任务切换 这是一
  • 解决错误“ #error “include FreeRTOS.h“ must appear in source files before “include event_groups.““例子分享

    今天来给大家分享一下 关于之前自己在学习FreeRTOS过程中遇到的一个错误提示 话不多说 我们直接来看 错误分析 首先 我们看一下错误的提示 error 35 error directive include FreeRTOS h must
  • FreeRTOS学习笔记 6 - 互斥量

    目录 1 创建 2 获取 3 释放 4 测试 FreeRTOS不支持调度方式的设置 所以下面2个宏定义可以随意设置值 define RTOS IPC FLAG FIFO 0x00 define RTOS IPC FLAG PRIO 0x01
  • FreeRTOS学习笔记(3、信号量、互斥量的使用)

    FreeRTOS学习笔记 3 信号量 互斥量的使用 前言 往期学习笔记链接 学习工程 信号量 semaphore 两种信号量的对比 信号量的使用 1 创建信号量 2 give 3 take 4 删除信号量 使用计数型信号量实现同步功能 使用
  • 【FreeRTOS开发问题】FreeRTOS内存溢出

    FreeRTOS内存溢出 如下图所示 FreeRTOS编译完成后可以看到 系统提示无法分配内存到堆 Objects Template axf Error L6406E No space in execution regions with A
  • STM32移植FreeRTOS的Tips

    转自 http bbs armfly com read php tid 7140 1 在FreeRTOS的demo文件夹中拷贝对应的FreeRTOSConfig h文件后 需要加入一行 define configUSE MUTEXES 1
  • FreeRTOS学习笔记<中断>

    中断概念 Cortex M的NVIC最多支持240个IRQ 中断请求 1个不可屏蔽中断 NMI 1个Systick 滴答定时器 定时器中断和多个系统异常 Cortex M处理器有多个用于管中断和异常的可编程寄存器 这些寄存器大多数都在 NV
  • freeRTOS使用uxTaskGetStackHighWaterMark函数查看任务堆栈空间的使用情况

    摘要 每个任务都有自己的堆栈 堆栈的总大小在创建任务的时候就确定了 此函数用于检查任务从创建好到现在的历史剩余最小值 这个值越小说明任务堆栈溢出的可能性就越大 FreeRTOS 把这个历史剩余最小值叫做 高水位线 此函数相对来说会多耗费一点
  • FreeRTOS_中断

    传送门 博客汇总帖 传送门 Cortex M3 中断 异常 传送门 Cortex M3笔记 基础 笔记内容参考 正点原子的FreeRTOS开发手册 cortex m3权威指南 Cortex M3和Cortex M4权威指南等 文中stm32
  • Error: L6218E: Undefined symbol vApplicationGetIdleTaskMemory (referred from tasks.o).

    我用的是F103ZET6的板子 移植成功后 编译出现两个错误是关于stm32f10x it c 里 void SVC Handler void void PendSV Handler void 两个函数的占用问题 随后编译出现以下两个问题
  • FreeRTOS:中断配置

    目录 一 Cortex M 中断 1 1中断简介 1 2中断管理简介 1 3优先级分组定义 1 4优先级设置 1 5用于中断屏蔽的特殊寄存器 1 5 1PRIMASK 和 FAULTMASK 寄存器 1 5 2BASEPRI 寄存器 二 F
  • FreeRTOS之软件定时器

    FreeRTOS之软件定时器 声明 本人按照正点原子的FreeRTOS例程进行学习的 欢迎各位大佬指责和批评 谢谢 include sys h include delay h include usart h include led h in
  • FreeRTOS笔记(一)简介

    这个笔记主要依据韦东山freertos快速入门系列记录 感谢韦东山老师的总结 什么是实时操作系统 操作系统是一个控制程序 负责协调分配计算资源和内存资源给不同的应用程序使用 并防止系统出现故障 操作系统通过一个调度算法和内存管理算法尽可能把
  • [FreeRTOS入门学习笔记]定时器

    定时器的使用步骤 1 定义一个handle xTimerCreate创建 2 启动定时器 在Task1中调用 通过队列通知守护任务来执行定时器任务 要再config头文件中定义守护任务相关配置 虽然定时器是在task1中启动 但是定时器的任
  • freeRTOS出现任务卡死的情况。

    最近在做一个产品二代升级的项目 代码是上一任工程师留下的 很多BUG 而且融合了HAL库和LL库 以及github上下载的GSM源码 很不好用 我这边是将2G模块换成了4G 且添加了单独的BLE模块 因此只在源码的基础上 去除2G和BLE代
  • 13-FreeRTOS任务创建与删除

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

    关于任务栈和系统栈的基础知识 可以参考之前的随笔 FreeRTOS 任务栈大小确定及其溢出检测 这里再次说明 define configTOTAL HEAP SIZE size t 17 1024 这个宏 官方文档解释 configTOTA

随机推荐