实时操作系统何时进行任务调度?————FREERTOS源码分析

实时操作系统何时进行任务调度?————FREERTOS源码分析

0 本文要讨论的是什么

从编码风格的角度去理解,实时操作系统(Real Time Operating System)的关键特征就是引入任务调度器(task shceuler),实现单核多任务调度。

有了这一层次的理解,自然就不应当再像CV工程师那样只关注顶层的 API ,而应当进一步考虑这个问题————调度器是在什么时候起作用的?或者说,调度器的代码实现在哪儿?何种情况下处理器会执行这些代码?

产生了这样的疑惑,我们就不能再继续满足于各种论坛博客上的残羹剩饭,而要自己动手,分析操作系统的源码。这里我们以 FREERTOS 为例,对照代码回答上述问题。当然,分析的源码仅限于 ARM 架构(且限于ARMv7)部分。

1 在进入源码之前

先把结论摆出来————对 FREERTOS 来说,只在 SVC/TIMER 中断的处理函数中调用 SCHEDULER 相关的代码。
在这里插入图片描述

2 对着源码说

2.1 SVC 中断

下面的汇编代码是 SVC 中断的处理函数

FreeRTOS_SWI_Handler

	PRESERVE8

	; Save the context of the current task and select a new task to run.
	portSAVE_CONTEXT
	LDR R0, =vTaskSwitchContext
	BLX	R0
	portRESTORE_CONTEXT

代码首先通过函数宏 portSAVE_CONTEXT 完成现场保护,保存切出的任务,而后将调度器上下文对应的符号 vTaskSwitchContext 的地址加载到寄存器 R0 中:

LDR R0, =vTaskSwitchContext

然后使用 BLX 指令跳转到 R0 中保存的地址:

BLX	R0

执行完 vTaskSwitchContext 对应的代码后,通过函数宏 portRESTORE_CONTEXT 恢复现场,准备执行切入的任务。

2.2 定时器中断(TIMER INTERRUPT)

这里说的定时器其实是系统心跳,也就是 RTOS 的时间基准,操作系统在这个基准上实现时间片和软件定时功能支持。下面是对应的中断处理函数 vTaskSwitchContext() 的流程解析:

  1. 检查任务调度器是否被挂起(Suspended)。说明一下,有些时候(譬如原子操作)可能会避免任务切换而把调度器挂起,这种时候进入定时器中断就不再继续后面的流程,直接退出。
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ){
        /* The scheduler is currently suspended - do not allow a context
         * switch. */
        xYieldPending = pdTRUE;
        return;
}
  1. 检查栈是否溢出(Stack overflow)
/* Check for stack overflow, if configured. */
        taskCHECK_FOR_STACK_OVERFLOW();
  1. 保存异常状态信息
/* Before the currently running task is switched out, save its errno. */
#if ( configUSE_POSIX_ERRNO == 1 )
    {
        pxCurrentTCB->iTaskErrno = FreeRTOS_errno;
    }
#endif
  1. 又是通过函数宏选择切入的任务
taskSELECT_HIGHEST_PRIORITY_TASK();
traceTASK_SWITCHED_IN();
  1. 更新异常状态信息
/* After the new task is switched in, update the global errno. */
#if ( configUSE_POSIX_ERRNO == 1 )
    {
        FreeRTOS_errno = pxCurrentTCB->iTaskErrno;
    }
#endif

3 欣赏一下某个 DMEO 里面的C-向量表设计

声明 Xil_InterruptHandlervoid(*)(void) 类型的函数指针,而后通过 pxVectorEntry->Handler 调用进入 pxVectorEntry->CallBackRef 对应的地址。

typedef void (*Xil_InterruptHandler)(void *data);

typedef struct
{
	Xil_InterruptHandler Handler;
	void *CallBackRef;
} XScuGic_VectorTableEntry;

const XScuGic_VectorTableEntry *pxVectorEntry;
pxVectorEntry = &( pxVectorTable[ ulInterruptID ] );
pxVectorEntry->Handler( pxVectorEntry->CallBackRef );

再参考下面那一坨,上面的功能即以 CallBackRef 赋值 X0 寄存器,跳转执行 0x00000000 处的指令

/**
 * This typedef contains configuration information for the device.
 */
typedef struct
{
	u16 DeviceId;		/**< Unique ID  of device */
	u32 CpuBaseAddress;	/**< CPU Interface Register base address */
	u32 DistBaseAddress;	/**< Distributor Register base address */
	XScuGic_VectorTableEntry HandlerTable[XSCUGIC_MAX_NUM_INTR_INPUTS];/**<
				 Vector table of interrupt handlers */
} XScuGic_Config;
XScuGic_Config XScuGic_ConfigTable[XPAR_XSCUGIC_NUM_INSTANCES] =
{
	{
		XPAR_PSU_ACPU_GIC_DEVICE_ID,
		XPAR_PSU_ACPU_GIC_BASEADDR,
		XPAR_PSU_ACPU_GIC_DIST_BASEADDR,
		{{0}}		/**< Initialize the HandlerTable to 0 */
	}
};

static const XScuGic_VectorTableEntry *pxVectorTable = XScuGic_ConfigTable[ XPAR_SCUGIC_SINGLE_DEVICE_ID ].HandlerTable;

把向量表放到 0x00000000 的话,就能通过上述代码进到向量表了。

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值