400行程序写一个实时操作系统(十七):调度算法的实现
前言
在上一篇博客笔者介绍了操作系统中的调度算法,调度算法的本质就是选择下一个任务。
如果读者认真看了上一篇文章的内容,那么接下来讲解的Sparrow的算法应该是非常清晰易懂的。
在实时操作系统Sparrow RTOS中,我们引入了优先级的概念,就是为了使任务的运行更加具有实时性,优先级由我们手动进行调整,往往我们希望优先级高的任务优先执行,那么,该怎么做呢?
调度算法的设计
我们使用ReadyBitTable这个uint32_T类型的变量来标识就绪的任务,只要任务是就绪态的,我们就执行 ReadyBitTable |= (1 << uxPriority)操作,这样ReadyBitTable 中为1的位就表示有就绪的任务。
那么如何得到对应的优先级数字呢?这里我们可以计算从高位到低位,离最近的1有多少个0,然后用31减去这个数目,就可以得到优先级的数字了。
在arm架构中,恰好有这么一条汇编指令clz,它可以计算从高位到低位,离最近的1之间的0的数目,正好符合我们的需求。
举例如下:
就绪表 | 值 | clz的结果 | 最大优先级 |
---|---|---|---|
ReadyBitTable | 00000000000000000000000000000100 | 29 | 2 |
ReadyBitTable | 00000000000000000000000100000011 | 23 | 8 |
不知道读者发现没有,其实这种方法跟上一节中所讲的RTThread的算法思想是一样的,只是我们使用clz指令代替了查表法。在FreeRTOS和RT-Thread等RTOS中,其实都有使用clz指令的方法,它们跟Sparrow的算法都大差不差。
修改程序
既然我们已经知道了思路,那么让我们开始写代码:
uint32_t ReadyBitTable = 0;//添加到合适的地方即可
__attribute__( ( always_inline ) ) static inline uint8_t FindHighestPriority( void )
{
uint8_t TopZeroNumber;
uint8_t temp;
__asm volatile
(
"clz %0, %2\n"
"mov %1, #31\n"
"sub %0, %1, %0\n"
:"=r" (TopZeroNumber),"=r"(temp)
:"r" (ReadyBitTable)
);
return TopZeroNumber;
}
void vTaskSwitchContext( void )
{
pxCurrentTCB = TcbTaskTable[ FindHighestPriority()];
}
修改xTaskCreat函数,添加一行代码:
void xTaskCreate( TaskFunction_t pxTaskCode,
const uint16_t usStackDepth,
void * const pvParameters,//You can use it for debugging
uint32_t uxPriority,
TaskHandle_t * const self )
{
uint32_t *topStack =NULL;
TCB_t *NewTcb = (TCB_t *)heap_malloc(sizeof(TCB_t *));
*self = ( TCB_t *) NewTcb;
TcbTaskTable[uxPriority] = NewTcb;//
NewTcb->uxPriority = uxPriority;
NewTcb->pxStack = ( uint32_t *) heap_malloc( ( ( ( size_t ) usStackDepth ) * sizeof( uint32_t * ) ) );
topStack = NewTcb->pxStack + (usStackDepth - (uint32_t)1) ;
topStack = ( uint32_t *) (((uint32_t)topStack) & (~((uint32_t) aligment_byte)));
NewTcb->pxTopOfStack = pxPortInitialiseStack(topStack,pxTaskCode,pvParameters,self);
pxCurrentTCB = NewTcb;
ReadyBitTable |= (1 << uxPriority); //添加这一行
}
修改空闲任务,任务内容为进入低功耗模式:
void EnterSleepMode(void)
{
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
__WFI();
}
//Task handle can be hide, but in order to debug, it must be created manually by the user
TaskHandle_t leisureTcb = NULL;
void leisureTask( )
{//leisureTask content can be manually modified as needed
while (1) {
EnterSleepMode();
}
}
实验
验证思路
设置一个任务的优先级为最大,观察这个任务是不是一直在执行。
程序
修改任务:
//Task Area!The user must create task handle manually because of debugging and specification
TaskHandle_t tcbTask1 = NULL;
TaskHandle_t tcbTask2 = NULL;
void led_bright( )
{
while (1) {
}
}
void led_extinguish( )
{
while (1) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
HAL_Delay(500);
switchTask();
}
}
当然,请确保led_extinguish优先级足够大:
void APP( )
{
xTaskCreate( led_bright,
128,
NULL,
1,
&tcbTask1
);
xTaskCreate( led_extinguish,
128,
NULL,
8,
&tcbTask2
);
}
实验现象:
stm32f103c8t6上的led灯一直在闪烁,说明任务led_extinguish一直在执行。
总结
介绍了Sparrow的调度算法,然后编写程序实现了优先级抢占算法的设计,最后修改原先的工程对调度算法进行验证。
本次实验的文件夹的地址:skaiui2/SKRTOS_sparrow at experiment
有需要的读者可以自取。