FreeRTOS的xPortPendSVHandler函数默认笔记(自用)

目录

 ARM_CM3的源码

 代码中涉及的汇编指令介绍和一些寄存器简要(非常简要)介绍

简单描述函数过程

保存当前任务现场

保存当前任务现场

恢复下一任务的现场

找到下一个要执行的任务(就绪列表中最高优先级的任务)

切换到下一任务


 ARM_CM3的源码

__asm void xPortPendSVHandler( void )
{
	/*
		用于计算函数嵌套层数,便于后续uxCriticalNesting 用于记录中断嵌套的层数。
		这是为了确保在退出临界段时能够正确地重新开启被关闭的中断。
	*/
	extern uxCriticalNesting;	
	extern pxCurrentTCB;	//指向当前运行任务的句柄

	/*
	vTaskSwitchContext 的作用
	检查堆栈使用:确保任务堆栈没有溢出。
	查找下一个任务:找到优先级最高的就绪任务,并更新 pxCurrentTCB 变量,
	该变量指向当前正在运行的任务。
	运行时间统计:如果启用了运行时间统计功能,该函数还会计算任务的运行时间。
 	*/
	extern vTaskSwitchContext;

	PRESERVE8	//指定栈八字节对齐
	/*	保存现场	*/
	mrs r0, psp		//保存当前栈顶地址
	/*
	ISB指令的功能
	刷新流水线:ISB指令会刷新处理器的指令流水线,确保流水线中的所有指令都执行完毕。
	上下文同步:在上下文切换或更改处理器状态时,ISB确保这些更改对后续指令可见。
	上下文切换:在实时操作系统进行任务切换时,使用ISB可以确保当前任务的指令全部执行完毕,
	再切换到下一个任务。
	*/
	isb

	/* 
		Get the location of the current TCB. 
		r3 = pxCurrentTCB 将当前任务的 TCB指针地址存储在 r3 寄存器中。
	*/
	ldr	r3, =	pxCurrentTCB

	/*
		r2 = r3地址存储的tcb数据地址,类似解引用
	*/
	ldr	r2, [r3]
	/*
		Save the remaining registers.
		将r4到r11 寄存器中的值报存到r0地址除
		即将r4到r11寄存器的值保存到当前任务栈中
	*/
	stmdb r0!, {r4-r11}			/* Save the remaining registers. */
	
	/* 将r0指向的新的栈顶存入,pxCurrentTCB地址处 */
	str r0, [r2]				/* Save the new top of stack into the first member of the TCB. */
	/* 将r3和r14入栈 */
	stmdb sp!, {r3, r14}

	/* 写入configMAX_SYSCALL_INTERRUPT_PRIORITY到r0寄存器 */
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	/* 写入basepri,该寄存器用于屏蔽优先级比他低的中断 */
	msr basepri, r0
	/* 数据同步和指令同步 */
	dsb
	isb
	/* 执行vTaskSwitchContext函数 */
	bl vTaskSwitchContext
	/* 清楚中断屏蔽 */
	mov r0, #0
	msr basepri, r0
	/* 恢复r3和r14 */
	ldmia sp!, {r3, r14}
	ldr r1, [r3]
	ldr r0, [r1]				/* The first item in pxCurrentTCB is the task top of stack. */
	/* 恢复当前任务r4-r11寄存器值 */
	ldmia r0!, {r4-r11}			/* Pop the registers and the critical nesting count. */
	
	/* 将当前r0值即栈顶存入psp,方便切换任务 */
	msr psp, r0
	isb
	/* 之后硬件会自动把PC指针出堆栈(因为此时psp为新任务的堆栈顶指针所以出堆栈也是新任务的寄存器) */
	bx r14
	nop
}

 代码中涉及的汇编指令介绍和一些寄存器简要(非常简要)介绍

ldr:LDR指令可以从内存中读取数据到寄存器中。

str:将寄存器中的数据存储到内存中。

他们是对内存的操作,简单的说str r0 [r1] 这是将r0的值写入r1值所在的地址,如果str r0 r1这是不允许的。

mov:MOV指令可以将一个值从一个寄存器复制到另一个寄存器,或者将一个立即数(常数)加载到一个寄存器中。

mrs:MRS 指令通常用于读取和处理状态寄存器中的标志位,例如在异常处理或处理器模式切换时。

stmdb:STMDB{<条件>} 基址寄存器!, {寄存器列表}。指令是一个用于将多个寄存器的内容存储到连续的内存地址中的指令。这个指令之后会减小基址寄存器的值

ldmia:指令用于将多个连续的内存地址中的数据加载到一系列寄存器中。这个指令在加载数据之后会增加基址寄存器的值。

bl和bx:BL指令常用于调用子程序(函数)。BX指令用于实现分支和交换处理器的状态。它可以跳转到由寄存器指定的地址,并可以选择性地改变处理器的状态(如切换到Thumb状态)。

r14:LR寄存器,保存着“返回现场的下一个要执行的指令”。如调用函数时会将lr寄存器保存着函数返回后要执行的下一条指令。

sp和psp:sp不介绍了,PSP是用于管理进程堆栈的堆栈指针,它与另一个堆栈指针MSP(Main Stack Pointer)一起使用,用于不同的目的和上下文。freertos在任务中使用psp来管理任务栈。

basepri:用于屏蔽中断。

简单描述函数过程

保存当前任务现场

注:此时pxCurrentTCB指针还未更新,此时仍然指向当前任务。

保存当前任务现场
mrs r0, psp
ldr	r3, =	pxCurrentTCB
ldr	r2, [r3]
stmdb r0!, {r4-r11}
str r0, [r2]
stmdb sp!, {r3, r14}

mrs r0, psp:将psp值存入r0,此时c。

ldr    r3, =   pxCurrentTCB

ldr    r2, [r3]

:将pxCurrentTCB指针地址存入r3,然后地址x(x为r3所存储的值)存入r2。通俗来说r3存储pxCurrentTCB指针,r2存储当前任务的首地址。

stmdb r0!, {r4-r11}  :此时r0是当前任务的栈顶,将r4到r11的值压入当前任务栈。更新r0,此时r0存着当前任务的栈顶-4*8.

str r0, [r2]:将新的栈顶写入到当前任务的tcb中。

stmdb sp!, {r3, r14}:将r3和r14压入栈,方便后续切换任务。

vTaskSwitchContext:该函数就是获取当前就绪列表中优先级最高的任务,然后pxcurrentTCB指向该任务。详细看源码十分简单。

恢复下一任务的现场

找到下一个要执行的任务(就绪列表中最高优先级的任务)
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
dsb
isb
bl vTaskSwitchContext

mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY  将中断屏蔽值写入r0。

configMAX_SYSCALL_INTERRUPT_PRIORITY         FreeRTOS中用于设置受系统控制的中断优先级,比这个值的优先级低的中断受FreeRTOS控制。

msr basepri, r0   写入basepri寄存器,屏蔽受控制的中断。

dsb  数据同步 isb指令同步。

bl vTaskSwitchContext函数: 获取下一个要执行的任务,并使pxcurrentTCB指向下一个任务。

/* vTaskSwitchContext函数代码,钩子函数和一些我认为不重要的不写了,此处只是简略介绍函数流程

void vTaskSwitchContext( void )
{
/*
    uxSchedulerSuspended 是 FreeRTOS 实时操作系统中的一个变量,用于管理任务调度器的挂起状态。

    xYieldPending它的主要作用是用来指示是否需要进行任务切换。如果 xYieldPending 被设置为                 
    pdTRUE,这表明有更高优先级的任务变为就绪状态,因此需要进行上下文切换

    函数除去钩子函数和一个判断uxSchedulerSuspended是否不为零,不为零的话表示调度器挂起,设                        
    xYieldPending为true以便调度器重新活跃后可以马上知道有任务需要调度。
*/
    taskSELECT_HIGHEST_PRIORITY_TASK();
}


/*
    uxTopReadyPriority 存储的是当前就绪任务的最高优先级。
    uxNumberOfItems 保存当前列表中有多少个子项。
    listLIST_IS_EMPTY 判断列表是否为0
*/
#define listLIST_IS_EMPTY( pxList )	{\
( ( BaseType_t ) ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) )\
}

#define taskSELECT_HIGHEST_PRIORITY_TASK()\
{\
    UBaseType_t uxTopPriority = uxTopReadyPriority;	\
    while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )\
	{\
		configASSERT( uxTopPriority );\
		--uxTopPriority;\  
	}\
    listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB,&(pxReadyTasksLists[ uxTopPriority ]));\
	uxTopReadyPriority = uxTopPriority;\
}

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )\
{\
    List_t * const pxConstList = ( pxList );\
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;\
	if((void *)(pxConstList ->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )\
	{	\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;	\
	}\
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;\
}

*/
下面是我进一步简写,便于理解的流程函数。
void vTaskSwitchContext( void )
{
    if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
	{
		/*
            xYieldPending 设为pdTRUE,等调度器活跃时可以尽快知道有高优先级任务可调度 
        */
		xYieldPending = pdTRUE;
	}
    else
    {
        xYieldPending = pdFALSE;
/* 1
taskSELECT_HIGHEST_PRIORITY_TASK(); 
*/
        UBaseType_t uxTopPriority = uxTopReadyPriority;
//设( &pxReadyTasksLists[ uxTopPriority ] ) 为TopPriorityList
/* 2
        while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )	
*/
        while( TopPriorityList -> uxNumberOfItems ==  0 )//超出就绪列表中非零最高优先级列表
        {
            --uxTopPriority;
        }
     
/* 3
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ])); 
*/
        List_t * const pxConstList = ( &pxReadyTasksLists[ uxTopPriority ] );
/*
pxIndex 成员是遍历列表的一个“索引”,指向列表的遍历的下一项。pxIndex->pxNext是要遍历的下一项
*/
        /* 获取最高优先级列表的下一项 */
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;
        
        /* 判断下一项是否为xListEnd(列表尾部标志) */
        if( ( void * ) ( pxConstList )->pxIndex == ( void * ) 
        &( ( pxConstList )->xListEnd ) )
        {
            /* 如果为尾部,则返回头部项 */
            ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;
        }
/*
    pvOwner成员指向列表项的TCB(任务控制块)
*/
        ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;
    }
}

函数通过taskSELECT_HIGHEST_PRIORITY_TASK();宏来完成pxcurrentTCB指向下一任务。

切换到下一任务
    mov r0, #0
    msr basepri, r0
    ldmia sp!, {r3, r14}
    ldr r1, [r3]
    ldr r0, [r1]
    ldmia r0!, {r4-r11}
	msr psp, r0

 mov r0, #0

msr basepri, r0

清楚中断屏蔽

ldmia sp!, {r3, r14}

恢复上文中保存的r3值和r14值。

注:r3保存的是pxcurrentTCB指针的地址。假设指针的地址是x,我们指导我们通过找到地址x中所存的值,此值是指针的指向。pxcurrentTCB指向更新成指向下一任务了,所以此时地址x的值不是当前任务TCB了,而是下一任务TCB了。

ldr r1, [r3]

ldr r0, [r1]

r0为下一任务tcb首地址的值,即任务栈顶。

ldmia r0!, {r4-r11} 恢复现场。

msr psp, r0 

将psp设置成任务栈顶,这一步非常重要。他使得在任务返回时,后续执行任务时使用的是下一任务的任务栈。

bx r14 返回。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值