RT-Thread内核实现(四):多优先级

  • 整体思想:
    前几章并不支持多优先级,都是手动切换指定的线程来运行。支持优先级后,调度器只会找当前最高优先级的线程来运行。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
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“公平的”选择它来执行。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值