skaiuijing

前言

在上一篇博客笔者介绍了操作系统中的调度算法,调度算法的本质就是选择下一个任务。

如果读者认真看了上一篇文章的内容,那么接下来讲解的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的算法都大差不差。

修改程序

既然我们已经知道了思路,那么让我们开始写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
uint32_t ReadyBitTable = 0;//添加到合适的地方即可


__attribute__( ( always_inline ) ) static inline uint8_t FindHighestPriority( void )
{
uint8_t TopZeroNumber;
uint32_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函数,添加一行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

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); //添加这一行
}

修改空闲任务,任务内容为进入低功耗模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

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();
}
}

实验

验证思路

设置一个任务的优先级为最大,观察这个任务是不是一直在执行。

程序

修改任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//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优先级足够大:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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

有需要的读者可以自取。