【嵌入式操作系统-5】操作系统中的上下文切换

操作系统中的任务状态

在FreeRTOS中存在四种任务运行状态:
在这里插入图片描述

  • 每一个状态下对应一个双向循环链表,里面存储了对应状态的任务列表。
  • 任务状态的转换实际上就是将对应任务的任务控制块从一个队列转移到另一个队列的过程。
  • 处于就绪队列中的任务将会被调度,在FreeRTOS中会选择优先级最高的任务让其运行。
  • 当就绪队列中的任务被调度器调度执行后,任务状态便从就绪态转为运行态,如果最高优先级的任务有多个并且使能了时间片轮询,那么操作系统会为这些任务分配时间片交替执行。
  • 当前任务正在运行的时间片用完或者有更高优先级的任务被唤醒时,操作系统会进行上下文切换,此时任务的状态便从运行态转换为就绪状态。
  • 正在运行的任务如果调用了阻塞的函数,设置的阻塞时间不是portMAX_DELAY,那么该任务将会被放入延时队列中,进入阻塞状态,如果阻塞时间设置为portMAX_DELAY,该任务将会被放入挂起队列中,进入挂起状态,避免该任务被周期性的唤醒。
  • 处于延时队列中的任务在条件满足之后任务将会重新添加到就绪队列中,等待被操作系统调度运行。
  • 正在运行的任务调用vTaskSuspend函数,将会把任务从就绪队列转移到挂起队列中,下一次进行任务切换时,调度器从就绪队列中重新查找优先级最高的任务进行调度。
  • 处于挂起队列中的任务在调用vTaskResume后将会重新添加到就绪队列中,等待被操作系统调度运行。

操作系统中的上下文切换

操作系统中的上下文切换分为如下几种情况:

  • 第一种是任务调用portYIELD()将PendSV的挂起位置1。
  • 第二种是处于阻塞状态的任务等待超时重新被唤醒或挂起的被任务恢复,并且他们的优先级足够高,在每个SysTick中断产生之后会检测是否需要进行上下文切换,如果需要则将PendSV的挂起位置1。
  • 第三种是使用了时间片轮询,当一个任务的时间片用完之后,必须要切换到下一个任务,此时SysTick中断中也会将PendSV的挂起位置1。

从上面几种情况中可以看出当需要调度器切换上下文时,必须要将PendSV的挂起位置1,这是切换上下文的前提条件。PendSV的优先级设置为最低,当系统中没有其他中断产生时候,PendSV中断被CPU响应。

#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. */						\
	__dsb( portSY_FULL_READ_WRITE );											\
	__isb( portSY_FULL_READ_WRITE );											\
}
  • 前面讲解过Cortex-M7处理器在带操作系统运行模式下,中断中使用的栈指针和线程中使用的栈指针是不同的,在中断中使用的是MSP,在线程中使用的是PSP,当前任务的栈顶值位于PSP中,如果直接使用push会把寄存器值放入MSP所指向的栈中,这样无法完成任务切换。
  • 因此第一步是通过MRS加载PSP的值到R0中,然后再加载pxTopOfStack的地址到R2中,加载R0是为了后面使用stm指令保存任务状态到栈上,而pxTopOfStack则保存了当前任务的栈顶,可用于直接访问线程栈和溢出检测等用途。
__asm void xPortPendSVHandler( void )
{
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

	PRESERVE8

	/* 获取当前任务栈顶地址 */
	mrs r0, psp
	isb
	/* 将TCB中变量pxTopOfStack的地址加载到R2中,在所有寄存器保存到栈上后需要更新栈顶位置 */
	ldr	r3, =pxCurrentTCB
	ldr	r2, [r3]
  • 然后将需要换出的任务状态保存到栈中,同时更新一下栈顶pxTopOfStack的值,这样换出工作便完成了。
	/* 如果当前任务正在进行浮点运算,需要保存浮点寄存器 */
	tst r14, #0x10
	it eq
	vstmdbeq r0!, {s16-s31}

	/* 保存通用寄存器 */
	stmdb r0!, {r4-r11, r14}

	/* 当把任务的寄存器值保存到栈上之后,栈顶位置发生了变化,需要更新TCB中栈顶位置 */
	str r0, [r2]
  • 进入临近段跳转到vTaskSwitchContext函数中,该函数从就绪队列中查找出下一个优先级最高的任务,将pxCurrentTCB指向新任务然后退出临界段。
	/* 将r0和r3(pxCurrentTCB地址) 保存到栈上*/
	stmdb sp!, {r0, r3}
	/* 屏蔽优先级数值大于等于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断,进入临界段 */
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	cpsid i
	msr basepri, r0
	dsb
	isb
	cpsie i
	/* 跳转到vTaskSwitchContext查找出优先级最高的就绪态任务赋值给pxCurrentTCB */
	bl vTaskSwitchContext
	/* 退出临界段*/
	mov r0, #0
	msr basepri, r0
  • 然后将新任务的栈顶指针加载到R0中,把对应的寄存器从栈中恢复,同时更新新任务的pxTopOfStack值,这样换入工作便完成了。
	/* 恢复R0和R3的值 */
	ldmia sp!, {r0, r3}

	/* 将pxCurrentTCB中pxTopOfStack变量保存的栈顶地址加载到R0中 */
	ldr r1, [r3]
	ldr r0, [r1]

		/* 将新任务的状态进行恢复 */
	ldmia r0!, {r4-r11, r14}

	/* 如果中断位置正包含浮点运算需要先恢复浮点寄存器 */
	tst r14, #0x10
	it eq
	vldmiaeq r0!, {s16-s31}
	
	/* 将新任务的栈顶指针值写入到psp中,PendSV退出之后就会使用该栈继续运行程序 */
	msr psp, r0
	isb
  • 在上下文切换成功之后通过LR进行返回,新任务从上次中断的位置继续往下运行。
	#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
		#if WORKAROUND_PMU_CM001 == 1
			push { r14 }
			pop { pc }
			nop
		#endif
	#endif
	/* 将LR寄存器的加载到PC中退出中断并返回线程 */
	bx r14
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咕咚.萌西

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值