在本章之前,RT-Thread还没有支持多优先级,我们手动指定了第一个运行的线程,并在此之后三个线程(包括空闲线程)互相切换,在本章中我们加入优先级的功能,第一个运行的程序是就绪列表里优先级最高的程线程,线程的切换也是切换到已经就绪的线程中优先级最高的一个。
就绪列表实际上由线程就绪优先级组rt_thread_ready_priority_group和线程优先级表rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]组成,我们在本章之前说的就绪列表是指这里的线程优先级表rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]。
线程就绪优先级组就是一个32位的整形数,每一个位对应一个优先级,位0对应优先级0,位1对应优先级1,以此类推。比如,当优先级为10的线程已经准备好,那么就将线程就绪优先级组的位10置1,表示线程已经就绪,然后根据10这个索引值,在线程优先级表10(rt_thread_priority_table[10])的这个位置插入线程。
本章我们为线程控制块增加与优先级相关的成员,其中还增加了错误码和线程状态成员,具体见下图加粗部分:
1.线程就绪优先级组
__rt_ffs函数是用来寻找32位整形数第一个(从低位开始)置1的位号,目的是找出线程就绪优先级组里优先级最高的线程,其代码如下:
/*** 该函数用于从一个32位的数中寻找第一个被置1的位(从低位开始),* 然后返回该位的索引(即位号) ** @return 返回第一个置1位的索引号。如果全为0,则返回0。 */
int __rt_ffs(int value)
{/* 如果值为0,则直接返回0 */if (value == 0) return 0;/* 检查 bits [07:00] 这里加1的原因是避免当第一个置1的位是位0时返回的索引号与值都为0时返回的索引号重复 */if (value & 0xff)return __lowest_bit_bitmap[value & 0xff] + 1;/* 检查 bits [15:08] */if (value & 0xff00)return __lowest_bit_bitmap[(value & 0xff00) >> 8] + 9; //9==8+1/* 检查 bits [23:16] */if (value & 0xff0000)return __lowest_bit_bitmap[(value & 0xff0000) >> 16] + 17; //17==16+1/* 检查 bits [31:24] */return __lowest_bit_bitmap[(value & 0xff000000) >> 24] + 25; //25==24+1
}
/* * __lowest_bit_bitmap[] 数组的解析* 将一个8位整形数的取值范围0~255作为数组的索引,索引值第一个出现1(从最低位开始)的位号作为该数组索引下的成员值。* 举例:十进制数10的二进制为:0000 1010,从最低位开始,第一个出现1的位号为bit1,则有__lowest_bit_bitmap[10]=1* 注意:只需要找到第一个出现1的位号即可*/
const rt_uint8_t __lowest_bit_bitmap[] =
{/* 00 */ 0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 10 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 20 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 30 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 40 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 50 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 60 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 70 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 80 */ 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 90 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* A0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* B0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* C0 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* D0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* E0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* F0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
};
2.线程1优先级表
线程优先级表即我们之前说的就绪列表,每个索引号对应线程的优先级,该索引下维护着一条双向链表,当线程就绪时,线程就会根据优先级插入到对应索引的链表,同一个优先级的线程都会被插入到同一条链表中(当同一个优先级下有多个线程时,需要时间片的支持,这个在后面章节我们在进一步讲解)。
①将线程插入到线程优先级表和移除分别由rt_schedule_insert_thread()和rt_schedule_remove_thread()这两个函数实现,它们的具体定义如下:
void rt_schedule_insert_thread(struct rt_thread *thread)
{register rt_base_t temp;/* 关中断 */temp = rt_hw_interrupt_disable();/* 设置线程状态为就绪态 */thread->stat = RT_THREAD_READY;/* 根据线程的当前优先级将线程插入到就绪列表的优先级表对应的链表上 */rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),&(thread->tlist));/* 设置线程就绪优先级组中对应的位 */rt_thread_ready_priority_group |= thread->number_mask;/* 开中断 */rt_hw_interrupt_enable(temp);
}void rt_schedule_remove_thread(struct rt_thread *thread)
{register rt_base_t temp;/* 关中断 */temp = rt_hw_interrupt_disable();/* 将线程从就绪列表删除 */rt_list_remove(&(thread->tlist));if (rt_list_isempty(&(rt_thread_priority_table[thread->current_priority]))){rt_thread_ready_priority_group &= ~thread->number_mask;}/* 开中断 */rt_hw_interrupt_enable(temp);
}
②修改调度器初始化函数rt_system_scheduler_init(),设置当前优先级为空闲线程的优先级(即最低)。
③修改线程初始化函数rt_thread_init()
④添加先启动函数rt_thread_startup(),该函数设置了当前优先级掩码并调用了rt_thread_resume函数恢复线程,该函数再调用了rt_schedule_insert_thread函数(该函数把线程设置为就绪态,此前是挂起态,然后根据线程的当前优先级把线程插入到对应的优先级表链表并设置对应优先级组的位)
⑤修改空闲线程初始化函数rt_thread_idle_init()
⑥修改启动系统调度器函数t_system_scheduler_start()
⑦修改系统调度函数rt_schedule()
⑧修改阻塞延时函数rt_thread_delay(),设置挂起时长并把线程状态设置为挂起态,且把线程就绪优先级组对应位设置为0
⑨修改时基更新函数rt_tick_increase(),如果挂起时长到了,则线程就绪优先级组对应位设置为1(但本章的代码把线程的状态改为挂起后好像没有改回来成就绪,但书里P103提到一点本来应该是把线程从优先级表中移除的,在定时器章节才会讲到,如果是移除后重新插入倒是会被设置为就绪态)
3.main函数
/************************************************************************* @brief main函数* @param 无* @retval 无** @attention*********************************************************************** */
int main(void)
{ /* 硬件初始化 *//* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 *//* 关中断 */rt_hw_interrupt_disable();/* SysTick中断频率设置 */SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );/* 调度器初始化 */rt_system_scheduler_init();/* 初始化空闲线程 */ rt_thread_idle_init(); /* 初始化线程 */rt_thread_init( &rt_flag1_thread, /* 线程控制块 */"rt_flag1_thread", /* 线程名字,字符串形式 */flag1_thread_entry, /* 线程入口地址 */RT_NULL, /* 线程形参 */&rt_flag1_thread_stack[0], /* 线程栈起始地址 */sizeof(rt_flag1_thread_stack), /* 线程栈大小,单位为字节 */2); /* 优先级 *//* 将线程插入到就绪列表 *///rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );rt_thread_startup(&rt_flag1_thread);/* 初始化线程 */rt_thread_init( &rt_flag2_thread, /* 线程控制块 */"rt_flag2_thread", /* 线程名字,字符串形式 */flag2_thread_entry, /* 线程入口地址 */RT_NULL, /* 线程形参 */&rt_flag2_thread_stack[0], /* 线程栈起始地址 */sizeof(rt_flag2_thread_stack), /* 线程栈大小,单位为字节 */3); /* 优先级 *//* 将线程插入到就绪列表 *///rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );rt_thread_startup(&rt_flag2_thread);/* 启动系统调度器 */rt_system_scheduler_start();
}
/* 线程1 */
void flag1_thread_entry( void *p_arg )
{for( ;; ){flag1 = 1;rt_thread_delay(2); flag1 = 0;rt_thread_delay(2); }
}/* 线程2 */
void flag2_thread_entry( void *p_arg )
{for( ;; ){flag2 = 1;rt_thread_delay(2); flag2 = 0;rt_thread_delay(2); }
}void SysTick_Handler(void)
{/* 进入中断 */rt_interrupt_enter();/* 更新时基 */rt_tick_increase();/* 离开中断 */rt_interrupt_leave();
}
4.实验现象
总结:本章通过在优先级组对应的位置1表示该线程已经就绪,系统调度函数在就绪的线程里面选出优先级最高的线程去执行。我们挂起线程的时候会把线程的状态改为挂起,并把其在优先级组对应的位置0,当挂起的时间结束后又把相因位设置为1表示线程已经就绪(但本章的代码把线程的状态改为挂起后好像没有改回来成就绪,但书里P103提到一点本来应该是把线程从优先级表中移除的,在定时器章节才会讲到,如果是移除后重新插入倒是会被设置为就绪态),等待调度器来调用。本章实验现象和前一章是一样的,区别就是本章是优先执行优先级最高的就绪线程,如果该线程被挂起才会去执行比它低优先级的线程。
最后声明一下,我这里只是对学习的知识点进行总结,本文章的大多数知识来自于野火公司出版的《RT-Thread 内核实现与应用开发实战—基于STM32》,这本书非常不错,有志学习RT-Thread物联网操作系统的人可以考虑一下。