首页 > 其他分享 >谈一谈RTOS的核心之一:调度器

谈一谈RTOS的核心之一:调度器

时间:2024-12-13 14:22:27浏览次数:7  
标签:谈一谈 RTOS 调度 MOV Current 寄存器 PCB Backups IndexTable

此篇文章在2023年6月27日被记录

调度器

上下求索,方可得道之精髓

引言

我还在学校的时候,实验室有一个学长在B站发布了这么一个视频,B站链接在这里,并且将代码开源在github,取名为suos,对于当时的我来说,实时操作系统是一个很新鲜的东西,特别是当看到代码里面的两个函数中的while(1),这完全是超出认知的东西。如今四年已经过去了,不敢说对RTOS的远离了解的很清楚,但是也是管中窥豹,略知一二,今天就记录一下对RTOS的调度器的理解,本文主要是简述原理,suos虽然说很简单,功能也少,但是在调度器这里确实实现的还算清楚,通俗易懂,因此借用学长的代码总结一番

C语言的栈调用过程

要想理解调度,必须先了解C语言函数调用的过程,裸机操作中,内存中专门会分配一块区域作为栈区,C函数在调用过程中,分为以下几个步骤:

  • 1、调用函数时,首先将当前函数的返回地址(调用下一条指令的位置)压入堆栈。这个返回地址指向了调用函数的下一条指令,以便在函数执行完后能够返回到正确的位置。
  • 2、如果函数有参数,将参数的值按照从右到左的顺序依次压入堆栈。
  • 3、将当前函数的局部变量在堆栈上分配空间。这些局部变量包括在函数内部定义的变量和临时变量。
  • 4、执行函数体内的代码。
  • 5、如果函数内部调用了其他函数,重复上述步骤,将控制权传递给被调用函数。
  • 6、当函数执行完毕时,将函数的返回值放在指定的寄存器中(如EAX寄存器),并且将函数的栈帧(包含局部变量和临时变量的空间)从堆栈中移除,释放内存。
  • 7、从堆栈中弹出之前保存的返回地址,将控制权返回到调用函数的位置,继续执行调用函数后面的代码。
  • 8、需要注意的是,堆栈的大小是有限的,所以函数调用的层级过多或者函数内部使用过多的局部变量可能导致堆栈溢出的问题。

上面是裸机的函数调用方式,在RTOS中,任务之间出现抢占时,希望记录这个任务当前的运行状态,因此就需要将当前的程序计数器和栈指针保存下来,下一次再运行时进行恢复,这就是调度器保存恢复现场的中的两个重要参数:程序计数器和堆栈指针

堆栈块

通过C语言的栈调用过程我们知道,裸机运行过程中存在栈调用,但是在RTOS中可能同时存在好几个任务,这些任务在运行过程中可能随时被切换,因此每个任务必须有一块自己的栈空间,只能自己占用,保证自己任务的正常运行,在任务挂起时保存起来,在任务运行的时候再恢复。这就是调度器保存恢复现场的中的一个重要参数:堆栈 。多提一嘴,在嵌入式领域经常会碰到堆栈溢出的问题,一般来说导致这种问题的原因就是另一个任务的栈空间溢出占用了另一个任务的栈空间导致任务崩溃。

CPU寄存器

寄存器是位于CPU内部的高速存储器,用于存储临时数据和执行算术和逻辑操作。寄存器在C语言运行过程中的几个用途:

  • 存储变量和计算结果:寄存器是CPU内部最快的存储器,用于存储频繁使用的变量和临时计算结果。通过将变量或计算结果保存在寄存器中,可以加快对它们的访问速度,提高程序的执行效率。
  • 函数调用和参数传递:寄存器在函数调用期间用于保存函数的参数和局部变量。一些寄存器(如参数寄存器)专门用于传递函数参数,而其他寄存器(如通用寄存器)用于保存局部变量和临时值。使用寄存器传递参数和保存局部变量可以减少内存访问的开销,提高函数调用的效率。
  • 存储指针:寄存器可以用于存储指针变量,例如指向数组、结构体或函数的指针。通过将指针存储在寄存器中,可以更快地进行指针操作和访问相关的数据结构。
  • 保存程序状态:某些特殊的寄存器,如标志寄存器,用于保存程序的状态信息。这些寄存器中的位表示条件状态、进位标志、零标志等,对于控制程序流程和执行条件语句非常重要。

总而言之,CPU寄存器保存的值是程序运行过程中的,需要用寄存器值来恢复下一次的程序运行状态,这也是调度器保存恢复现场的中的一个重要参数:CPU寄存器

调度过程

我们从上述可知,在产生调度时,需要保存恢复的四个重要参数:程序计数器、堆栈指针、堆栈、CPU寄存器,每次产生调度时,保存当前任务的这些参数(现场),并且恢复下一个这些参数,多个任务这样轮回切换,就产生一种多个任务在并行进行的错觉。

时间片

上面我们知道了任务调度的过程,但是又产生一个问题,谁来调度,多久调度一次。通常情况下,会开启一个硬件定时器,定时运行RTOS内核,RTOS每间隔固定时间判断是否应该启动调度。在FREERTOS中,RTOS运行在滴答定时器的中断函数中,如果需要调度,则触发一次PENSV中断,在PENSV中断函数中进行一次任务调度

学长SUOS代码解读

suos中,核心的调度代码都是汇编实现的,在程序启动时启动一个定时器,直接在定时器中进行调度

;定时器0中断服务函数 进程调度在此实现 
Timer0_ServiceFunction:
	MOV 	SP_Backups, SP			;将当前用户的SP指针保存
	MOV 	SP, #(SystemStack - 1)	;将系统栈设置为当前栈
	LCALL 	Save_CPU_Context		;保存现场环境
	LCALL 	Control_Process			;调用进程调度函数 调用后系统将切换现场信息和断点
	LCALL 	Recovery_CPU_Context	;恢复现场信息
	MOV 	SP, SP_Backups			;恢复栈指针
RETI

保存恢复现场环境参数也是由汇编实现,很简单的变量赋值:

;保存CPU现场信息,保存上文
Save_CPU_Context:
	MOV 	PSW_Backups, PSW
	MOV 	ACC_Backups, A
	MOV 	B_Backups, B
	MOV	R0_Backups, R0
	MOV	R1_Backups, R1
	MOV	R2_Backups, R2
	MOV	R3_Backups, R3
	MOV	R4_Backups, R4
	MOV	R5_Backups, R5
	MOV	R6_Backups, R6
	MOV	R7_Backups, R7
	MOV	DPL_Backups, DPL
	MOV	DPH_Backups, DPH
RET

;恢复CPU现场信息,切换下文
Recovery_CPU_Context:
	MOV 	PSW, PSW_Backups
	MOV 	A, ACC_Backups
	MOV 	B, B_Backups
	MOV	R0, R0_Backups
	MOV	R1, R1_Backups
	MOV	R2, R2_Backups
	MOV	R3, R3_Backups
	MOV	R4, R4_Backups
	MOV	R5, R5_Backups
	MOV	R6, R6_Backups
	MOV	R7, R7_Backups
	MOV	DPL, DPL_Backups
	MOV	DPH, DPH_Backups
RET

调度过程使用C语言实现,主要分为两个部分:1、更新进程状态,寻找下一个准备启动的任务函数名为Refresh_Process。2、将缓存的参数值保存在上一任务的TCB中,并且从下一个任务TCB恢复参数到缓存参数中,函数名为Switch_Process

void Control_Process(void)
{
	EA = 0;
	Refresh_Process();
	Switch_Process();
	EA = 1;
}

void Refresh_Process(void)
{
	uint8_t i;
	for (i = 0;i<=PCB_Number;i++)
	{
		if (PCB_IndexTable[i]!=(void*)0 && PCB_IndexTable[i]->ProcessState == Block) ////PCB非空 进程被阻塞
			{
				///*信号量的阻塞唤醒设计有问题*/
				if (PCB_IndexTable[i]->BlockEvent == 0 && PCB_IndexTable[i]->s == SemaphoreNull)
					PCB_IndexTable[i]->ProcessState = Ready;
				else if (PCB_IndexTable[i]->BlockEvent>0)
				{
					--PCB_IndexTable[i]->BlockEvent;
					if (PCB_IndexTable[i]->BlockEvent == 0) PCB_IndexTable[i]->ProcessState = Ready;
				}
					/*
				if (PCB_IndexTable[i]->BlockEvent==0&&*(PCB_IndexTable[i]->s)>0)//计时器为0且信号量>0 唤醒
					{
						PCB_IndexTable[i]->ProcessState = Ready;
						continue;
					}
				if (PCB_IndexTable[i]->BlockEvent<=60000) 
					--PCB_IndexTable[i]->BlockEvent;
				else ;//待添加的阻塞检查事件
				if (PCB_IndexTable[i]->BlockEvent==0&&*(PCB_IndexTable[i]->s)>0)//计时器为0且信号量>0 唤醒
					{PCB_IndexTable[i]->ProcessState = Ready;continue;}
				*/
			}//if end
	}//for end
}

void Switch_Process(void)
{
	uint8_t i;
	//	0.判断是否需要切换进程:若没有处于就绪态的进程则无需切换
	bit Unwanted = 1;			//	不需要切换进程标志 为1时不切换
	for (i = 0;i<=PCB_Number;i++) 
		if (PCB_IndexTable[i]->ProcessState == Ready)
			Unwanted = 0;
	if (Unwanted) return;
	//	1.将用户栈空间复制到自己的PCB,腾出来给下一个进程
	for (i=0;i<StackSize;i++)
		PCB_IndexTable[PCB_Current]->ProcessStack[i] = UserStack[i];
	// 	2.将CPU环境复制到PCB,腾出来给下一个进程
	PCB_IndexTable[PCB_Current]->PSW = PSW_Backups;
	PCB_IndexTable[PCB_Current]->ACC = ACC_Backups;
	PCB_IndexTable[PCB_Current]->B   = B_Backups;
	PCB_IndexTable[PCB_Current]->R0  = R0_Backups;
	PCB_IndexTable[PCB_Current]->R1  = R1_Backups;
	PCB_IndexTable[PCB_Current]->R2  = R2_Backups;
	PCB_IndexTable[PCB_Current]->R3  = R3_Backups;
	PCB_IndexTable[PCB_Current]->R4  = R4_Backups;
	PCB_IndexTable[PCB_Current]->R5  = R5_Backups;
	PCB_IndexTable[PCB_Current]->R6  = R6_Backups;
	PCB_IndexTable[PCB_Current]->R7  = R7_Backups;
	PCB_IndexTable[PCB_Current]->DPL = DPL_Backups;
	PCB_IndexTable[PCB_Current]->DPH = DPH_Backups;
	PCB_IndexTable[PCB_Current]->SP  = SP_Backups;
	//  3.选择下一个就绪的进程并切换
	//
	i = PCB_Current;
	while(1)   /*所有进程都阻塞了这么办?*/
	{
		if (i==PCB_Number) 
			i=0;																				//	循环查找
		if (PCB_IndexTable[i]->ProcessState == Ready)	
			break; 																			//	找到下一个就绪的进程了,退出寻找
		i++;																		
	} 
	if (PCB_IndexTable[PCB_Current]->ProcessState == Running) //如果当前进程为运行态
		PCB_IndexTable[PCB_Current]->ProcessState = Ready;	//当前进程设置为就绪
	PCB_Current = i;
	PCB_IndexTable[PCB_Current]->ProcessState = Running;//找到的进程设置为运行状态
	//	4.将找到的进程CPU的现场信息复制到中转站
	PSW_Backups = PCB_IndexTable[PCB_Current]->PSW;
	ACC_Backups = PCB_IndexTable[PCB_Current]->ACC;
	B_Backups   = PCB_IndexTable[PCB_Current]->B;
	R0_Backups  = PCB_IndexTable[PCB_Current]->R0;
	R1_Backups  = PCB_IndexTable[PCB_Current]->R1;
	R2_Backups  = PCB_IndexTable[PCB_Current]->R2;
	R3_Backups  = PCB_IndexTable[PCB_Current]->R3;
	R4_Backups  = PCB_IndexTable[PCB_Current]->R4;
	R5_Backups  = PCB_IndexTable[PCB_Current]->R5;
	R6_Backups  = PCB_IndexTable[PCB_Current]->R6;
	R7_Backups  = PCB_IndexTable[PCB_Current]->R7;
	DPL_Backups = PCB_IndexTable[PCB_Current]->DPL;
	DPH_Backups = PCB_IndexTable[PCB_Current]->DPH;
	SP_Backups  = PCB_IndexTable[PCB_Current]->SP;
	//	5.将栈空间从PCB复制到用户栈
	for (i=0;i<StackSize;i++)
		UserStack[i] = PCB_IndexTable[PCB_Current]->ProcessStack[i];
}

标签:谈一谈,RTOS,调度,MOV,Current,寄存器,PCB,Backups,IndexTable
From: https://www.cnblogs.com/shumei52/p/18604847

相关文章

  • Freertos低功耗-Tickless模式
    此篇文章在2023年5月15日被记录很多嵌入式设备都对功耗有严格的控制,特别是消费电子对功耗的控制更为严格,Tickless是freertos中的一个可选模块,主要实现低功耗功能STM32类芯片的低功耗模式STM32之类的arm芯片通常有三种低功耗模式:睡眠模式(sleep):仅CPU时钟关闭,其他所有外......
  • Freertos-CPU使用率统计
    此篇文章在2023年5月23日被记录RTOS-任务CPU占用统计在项目开发过程中,有时会需要查看各个任务的资源占用,需要用到rtos的CPU使用统计,其原理也很简单,就是开一个频率特别高的定时器,rtos在运行过程累计各个任务的实际占用时长,继而统计显示FreeRTOSConfig配置//计算CPU使用率#......
  • 转载:【AI系统】计算图的调度与执行
    在前面的内容介绍过,深度学习的训练过程主要分为以下三个部分:1)前向计算、2)计算损失、3)更新权重参数。在训练神经网络时,前向传播和反向传播相互依赖。对于前向传播,沿着依赖的方向遍历计算图并计算其路径上的所有变量。然后将这些用于反向传播,其中计算顺序与计算图的相反。基于计算......
  • 转载:【AI系统】计算与调度
    上一篇我们了解了什么是算子,神经网络模型中由大量的算子来组成,但是算子之间是如何执行的?组成算子的算法逻辑跟具体的硬件指令代码之间的调度是如何配合?计算与调度计算与调度的来源图像处理在当今物理世界中是十分基础且开销巨大的计算应用。图像处理算法在实践中需要高效的实现......
  • 转载:【AI系统】计算图的调度与执行
    在前面的内容介绍过,深度学习的训练过程主要分为以下三个部分:1)前向计算、2)计算损失、3)更新权重参数。在训练神经网络时,前向传播和反向传播相互依赖。对于前向传播,沿着依赖的方向遍历计算图并计算其路径上的所有变量。然后将这些用于反向传播,其中计算顺序与计算图的相反。基于计算......
  • 如何使用 ABAP 代码调度一个 ABAP 程序,让其以后台作业的方式运行
    文章目录1.ZLAUNCH的运行效果2.FORM子例程`run_in_background`2.1定义数据变量2.2检查是否为前台模式2.3提取选择屏幕参数2.4打开后台作业2.5检查作业创建成功与否2.6调度被调用的报表2.7关闭后台作业2.8根据执行结果输出信息2.9终止程序......
  • 智慧医疗-120应急指挥调度系统建设方案
    1项目概述1.1建设背景根据国家卫生部统计,中国每年有约350万人死于心脑血管疾病,救治率不足6%,70%的患者在病发后的“黄金时间”内得到不到有效救助而死在了院前。院前、院内无体化的信息网络联系,伤员救治各自为战,信息不同,数据信息通过原始手段收集、汇总、上报。针对这些......
  • 基于遗传算法的梯级水电站群优化调度研究(Python代码实现)
     ......
  • 【2024年华为秋招-12月11日-第二题(200分)- 服务器训练任务调度】(题目+思路+Java&C++&Py
    题目内容团队申请了一组服务器,用于机器学习训练,为了充分利用资源,需要你来完成任务调度算法的实现。一台服务器同一时间只能执行一个训练任务,每个训练任务有训练时间和优先级。当空闲服务器不足时,优先执行高优先级的训练任务;如果多个训练任务的优先级相同,优先执行训练时......
  • 转载:【AI系统】计算与调度
    上一篇我们了解了什么是算子,神经网络模型中由大量的算子来组成,但是算子之间是如何执行的?组成算子的算法逻辑跟具体的硬件指令代码之间的调度是如何配合?计算与调度计算与调度的来源图像处理在当今物理世界中是十分基础且开销巨大的计算应用。图像处理算法在实践中需要高效的实现......