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
有需要的读者可以自取。