-
整体思想:
前几章并不支持多优先级,都是手动切换指定的线程来运行。支持优先级后,调度器只会找当前最高优先级的线程来运行。RT-Thread属于抢占式实时操作系统,CPU会被当前最高优先级线程抢占,除非最高优先级线程主动放弃,比如调用rt_thread_delay(rt_tick_t tick)
延时函数,会将线程状态改为挂起状态或者说阻塞状态,然后执行系统调度。 -
线程控制块添加了几属性
rt_uint8_t current_priority; /* 当前优先级 */
rt_uint8_t init_priority; /* 初始优先级 */
rt_uint32_t number_mask; /* 当前优先级掩码 */
rt_err_t error; /* 错误码 */
rt_uint8_t stat; /* 线程的状态 */
- 线程初始化函数增加的部分
rt_err_t rt_thread_init(struct rt_thread *thread,
const char *name,
void (*entry)(void *parameter),
void *parameter,
void *stack_start,
rt_uint32_t stack_size,
rt_uint8_t priority)
{
/*
* 省略...
*/
thread->init_priority = priority;
thread->current_priority = priority;
thread->number_mask = 0;
thread->error = RT_EOK;
thread->stat = RT_THREAD_INIT; // 初始化状态
return RT_EOK;
}
- 调度器初始化函数
初始化当前优先级(rt_current_priority
)为空闲线程的优先级,全局变量。
/* 初始化系统调度器 */
void rt_system_scheduler_init(void)
{
register rt_base_t offset;
/* 线程就绪列表初始化 */
for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)
{
rt_list_init(&rt_thread_priority_table[offset]);
}
/* 初始化当前优先级为空闲线程的优先级 */
rt_current_priority = RT_THREAD_PRIORITY_MAX - 1;
/* 初始化当前线程控制块指针 */
rt_current_thread = RT_NULL;
/* 初始化线程就绪优先级组 */
rt_thread_ready_priority_group = 0;
}
- 添加线程启动函数
启动一个线程并将其放到系统的就绪列表中,先将线程的状态为挂起状态,然后调用恢复线程函数rt_thread_resume(rt_thread_t thread)
,只有挂起状态的线程才能被恢复。然后进行系统调度。这里能看出当前优先级掩码(thread->number_mask
)的作用,将32位优先级中的对应位置1,在修改此线程优先级组(rt_thread_ready_priority_group
)时比较方便。
rt_err_t rt_thread_startup(rt_thread_t thread)
{
/* 设置当前优先级为初始优先级 */
thread->current_priority = thread->init_priority;
thread->number_mask = 1L << thread->current_priority;
/* 改变线程的状态为挂起状态 */
thread->stat = RT_THREAD_SUSPEND;
/* 然后恢复线程 */
rt_thread_resume(thread);
if (rt_thread_self() != RT_NULL)
{
/* 系统调度 */
rt_schedule();
}
return RT_EOK;
}
- 线程恢复函数
- 首先判断目标线程是否是挂起状态,然后从挂起队列移除。
- 这里提到了挂起队列,应该是指全局变量
rt_list_t rt_thread_defunct;
,这里没有用到,在官方源码中只有线程的退出(rt_thread_exit
)、分离(rt_thread_detach
)和删除(rt_thread_delete
)操作会将线程挂到rt_thread_defunct
上。 - 然后将线程插入就序列表,
void rt_schedule_insert_thread(struct rt_thread *thread)
,在该函数中会更新线程就绪优先级组(rt_thread_ready_priority_group
)的值。 - 与插入函数对应的有移除函数,
void rt_schedule_remove_thread(struct rt_thread *thread)
,插入操作使用优先级掩码进行置位,移除操作复位。
rt_err_t rt_thread_resume(rt_thread_t thread)
{
register rt_base_t temp;
/* 将被恢复的线程必须在挂起态,否则返回错误码 */
if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_SUSPEND)
{
return -RT_ERROR;
}
/* 关中断 */
temp = rt_hw_interrupt_disable();
/* 从挂起队列移除 */
rt_list_remove(&(thread->tlist));
/* 开中断 */
rt_hw_interrupt_enable(temp);
/* 插入就绪列表 */
rt_schedule_insert_thread(thread);
return RT_EOK;
}
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);
}
- 空闲函数的初始化也做了相应的修改
void rt_thread_idle_init(void)
{
/* 初始化线程,最低优先级 */
rt_thread_init(&idle,
"idle",
rt_thread_idle_entry,
RT_NULL,
&rt_thread_stack[0],
sizeof(rt_thread_stack),
RT_THREAD_PRIORITY_MAX-1);
rt_thread_startup(&idle);
}
- 启动系统调度器函数
rt_system_scheduler_start()
之前是手动指定某一个线程,现在是通过__rt_ffs()
获取当前优先级最高的线程。
/* 启动系统调度器 */
void rt_system_scheduler_start(void)
{
register struct rt_thread *to_thread;
register rt_ubase_t highest_ready_priority;
/* 获取就绪的最高优先级 */
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
/* 获取将要运行线程的线程控制块 */
to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
rt_current_thread = to_thread;
rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);
}
- 系统调度函数
rt_schedule ()
同样通过__rt_ffs()
获取当前优先级最高的线程,然后进行线程切换。
/* 系统调度 */
void rt_schedule(void)
{
rt_base_t level;
register rt_ubase_t highest_ready_priority;
struct rt_thread *to_thread;
struct rt_thread *from_thread;
/* 关中断 */
level = rt_hw_interrupt_disable();
/* 获取就绪的最高优先级 */
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
/* 获取就绪的最高优先级对应的线程控制块 */
to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
/* 如果目标线程不是当前线程,则要进行线程切换 */
if(to_thread != rt_current_thread){
rt_current_priority = (rt_uint8_t)highest_ready_priority;
from_thread = rt_current_thread;
rt_current_thread = to_thread;
/* 产生上下文切换 */
rt_hw_context_switch((rt_uint32_t)&from_thread->sp, (rt_uint32_t)&to_thread->sp);
rt_hw_interrupt_enable(level);
}else{
rt_hw_interrupt_enable(level);
}
}
- 阻塞延时函数
rt_thread_delay()
先更新剩余tick数,更新线程状态,更新全局线程就绪优先级组,然后进行系统调度。高优先级线程可以通过这种方式让出CPU。
void rt_thread_delay(rt_tick_t tick)
{
register rt_base_t tmp;
struct rt_thread *thread;
tmp = rt_hw_interrupt_disable();
thread = rt_current_thread;
thread->remaining_tick = tick;
/* 改变线程状态 */
thread->stat = RT_THREAD_SUSPEND;
/* 取反,将对应位清0 */
rt_thread_ready_priority_group &= ~thread->number_mask;
rt_hw_interrupt_enable(tmp);
rt_schedule();
}
- 时基更新函数
rt_tick_increase()
- 该函数是在SysTick中断服务函数中被调用,功能是更新系统tick数,以及所有线程的
remaining_tick
,如果不为0,则减1,如果等于0,则更新全局线程就绪优先级组。然后进行系统调度。 - 这里和PDF中介绍的有些区别,1. 空闲函数的剩余tick不用判断;2. 如果
rt_list_isempty(&thread->tlist)
为真表示该优先级下没有挂线程,不用更新remaining_tick
。
- 该函数是在SysTick中断服务函数中被调用,功能是更新系统tick数,以及所有线程的
void rt_tick_increase(void)
{
rt_ubase_t i;
struct rt_thread *thread;
rt_tick ++;
/* 扫描就绪列表中所有线程(idle线程不应该判断)的remaining_tick,如果不为0,则减1 */
for(i=0; i < RT_THREAD_PRIORITY_MAX - 1; i++)
{
thread = rt_list_entry( rt_thread_priority_table[i].next,
struct rt_thread,
tlist);
if(rt_list_isempty(&thread->tlist)){
continue;
}
if(thread->remaining_tick > 0)
{
thread->remaining_tick --;
if(thread->remaining_tick == 0){
/* 延时时间到了则恢复原优先级 */
rt_thread_ready_priority_group |= thread->number_mask;
}
}
}
/* 系统调度 */
rt_schedule();
}
- 主函数
main()
- 可以看到在初始化
rt_flag1_thread
后,就调用了rt_thread_startup(&rt_flag1_thread)
,用来修改线程状态并启动启动该线程,但是该线程并不会立即运行。因为rt_thread_startup()
中有判断,当rt_thread_self() != RT_NULL
时才会进行系统调度。 - 还要注意一点是,main函数第一步就是关闭总中断,直到启动系统调度器做第一次线程切换时才会被打开。
- 可以看到在初始化
int main(){
/* 硬件相关初始化 */
/* 关中断 */
rt_hw_interrupt_disable();
/* 设置SysTick中断频率 25MHz / 100 */
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_thread_init( &rt_flag3_thread,
"rt_flag3_thread", /* 线程名字,字符串形式 */
flag3_thread_entry,
RT_NULL,
&rt_flag3_thread_stack[0],
sizeof(rt_flag3_thread_stack),
4);
// rt_list_insert_before(&(rt_thread_priority_table[2]), &(rt_flag3_thread.tlist));
rt_thread_startup(&rt_flag3_thread);
rt_system_scheduler_start();
}
- 再来看获取就绪的最高优先级函数
int __rt_ffs(int value)
注释写的比较清楚了。
#ifndef RT_USING_CPU_FFS
/*
* __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
};
/**
* 该函数用于从一个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;
/* 检查 bits [23:16] */
if (value & 0xff0000)
return __lowest_bit_bitmap[(value & 0xff0000) >> 16] + 17;
/* 检查 bits [31:24] */
return __lowest_bit_bitmap[(value & 0xff000000) >> 24] + 25;
}
#endif
工程文件
扩展
Linux CFS调度优先级策略:调度器总是选择vruntime
值最小的进程来执行。优先级高的进程vruntime
值增长的慢。
例如 A、B两个进程,A权重为1,B优先级更高(nice值更低)权重为2,若A的vruntime增长速度为单位1,B的则为1/2,但是一个周期内A所获得的CPU时间为单位1,B获得的时间为2,所以宏观上AB的vruntime增长速度是相同,这样在一个周期内如果谁的vruntime值小说明他以前占用cpu的时间较短,因此CPU“公平的”选择它来执行。