[003] [RT-Thread学习笔记] 时钟节拍的获取与线程时间片轮转调度的实现

RT-Thread
学习笔记
时钟节拍获取
线程时间片轮转调度的实现
时间片轮转调度机制
时间片轮转调度的实现
总结

RT-Thread版本:4.0.5
MCU型号:STM32F103RCT6(ARM Cortex-M3 内核)

1 时钟节拍获取

时钟节拍可以通过RT_TICK_PER_SECOND宏配置,默认1000,即每秒执行1k次

RTOS时钟节拍通过中断触发模式的硬件定时器产生,当中断到来时将调用一次rt_tick_increase()函数,让全局变量rt_tick自增1。在STM32中采用滴答定时器systick实现(在drv_common.c中):

void SysTick_Handler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

    HAL_IncTick();
    rt_tick_increase();

    /* leave interrupt */
    rt_interrupt_leave();
}

可以通过调用rt_tick_get()函数获取到当前系统时钟节拍。

2 线程时间片轮转调度的实现

2.1 时间片轮转调度机制

RT-Thread是基于优先级的抢占式调度的实时操作系统,但是RT-Thread内核允许创建相同优先级的线程,对于相同优先级的线程系统按时间片大小(单位为1个时钟节拍)进行轮转调度,即每个线程执行相应的时长后就挂起,并接到相同优先级队列的队尾,等其他所有同优先级线程执行结束后,再唤醒它继续执行。

注意:此调度机制仅在当前系统中无更高优先级就绪线程存在的情况下才有效,因此时间片也仅对相同优先级的线程有效

2.2 时间片轮转调度的实现

主要通过上面提到的rt_tick_increase()函数实现:

void rt_tick_increase(void)
{
   struct rt_thread *thread;
   rt_base_t level;

   level = rt_hw_interrupt_disable();
   
   /* 全局变量 rt_tick 自加 */
   ++rt_tick;

   // 获取当前线程
   thread = rt_thread_self();
	/* 检查时间片 */
   -- thread->remaining_tick;
   if (thread->remaining_tick == 0)
   {
       /* 重新赋初值 */
       thread->remaining_tick = thread->init_tick;
       // 标记remaining_tick已重新加载
       thread->stat |= RT_THREAD_STAT_YIELD;

       rt_hw_interrupt_enable(level);
       rt_schedule();
   }
   else
   {
       rt_hw_interrupt_enable(level);
   }

   /* 检查硬件定时器 */
   rt_timer_check();
}

线程通过init_tick变量保存初始化时的时间片长度,用remaining_tick变量记录当前线程剩余的执行时间,当remaining_tick为0时:

  • 重新将remaining_tick值赋为时间片长度init_tick
  • RT_THREAD_STAT_YIELD标记当前线程,用于指示remaining_tick值已重新加载,并让出CPU资源
  • 调用rt_schedule()函数发起任务调用,函数代码如下:
void rt_schedule(void)
{
    rt_base_t level;
    struct rt_thread *to_thread;
    struct rt_thread *from_thread;
    /* 关中断 */
    level = rt_hw_interrupt_disable();
    // 调度锁打开
    if (rt_scheduler_lock_nest == 0)    
    {
        rt_ubase_t highest_ready_priority;
        // 线程就绪队列不为空    
        if (rt_thread_ready_priority_group != 0)   
        {
            int need_insert_from_thread = 0;    // 判断源线程是否需要插入到就绪队列中
            // 获取就绪队列中的最高优先级线程(数字越小优先级越高!)
            to_thread = _scheduler_get_highest_priority_thread(&highest_ready_priority);  

            // 判断当前线程是否处于运行状态
            if ((rt_current_thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING)
            {
                 // 就绪队列中的最高优先级小于当前线程优先级
                 if (rt_current_thread->current_priority < highest_ready_priority)  
                 {
                     to_thread = rt_current_thread; 
                 }
                 // 优先级相等且当前线程没有标记RT_THREAD_STAT_YIELD_MASK
                 else if (rt_current_thread->current_priority == highest_ready_priority 
                 && (rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK) == 0)     
                 {
                     to_thread = rt_current_thread;
                 }
                 else
                 {
                     need_insert_from_thread = 1;   //< 需要将源线程插入到就绪队列中
                 }
                 // 清除当前线程的RT_THREAD_STAT_YIELD_MASK标记
                 rt_current_thread->stat &= ~RT_THREAD_STAT_YIELD_MASK;     
            }
            //< 若目标线程不是当前线程,则需要开始上下文切换 
            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_OBJECT_HOOK_CALL(rt_scheduler_hook, (from_thread, to_thread));

                 if (need_insert_from_thread)
                 {
                     //< 将源线程插入到就绪队列中
                     rt_schedule_insert_thread(from_thread); 
                 }
                 // 从就绪队列中移除当前需要执行的目标线程
                 rt_schedule_remove_thread(to_thread);      
                 // 标记目标线程为运行状态
                 to_thread->stat = RT_THREAD_RUNNING | (to_thread->stat & ~RT_THREAD_STAT_MASK);    

                 /* 在线程环境中进行线程上下文切换 */
                 if (rt_interrupt_nest == 0)
                 {
                    extern void rt_thread_handle_sig(rt_bool_t clean_state);

                    RT_OBJECT_HOOK_CALL(rt_scheduler_switch_hook, (from_thread));

                    rt_hw_context_switch((rt_ubase_t)&from_thread->sp,
                            (rt_ubase_t)&to_thread->sp);

                    /* 开中断 */
                    rt_hw_interrupt_enable(level);
                    
                    goto __exit;
                 }
                 /* 在中断环境中进行线程上下文切换 */
                 else
                 {
                      rt_hw_context_switch_interrupt((rt_ubase_t)&from_thread->sp,
                            (rt_ubase_t)&to_thread->sp);
                    
                 }
            }
            else
            {
                // 将当前线程从就绪列表中移除
                rt_schedule_remove_thread(rt_current_thread);   
                rt_current_thread->stat = RT_THREAD_RUNNING | (rt_current_thread->stat & ~RT_THREAD_STAT_MASK);
            }
        }
    }

    /* 开中断 */
    rt_hw_interrupt_enable(level);
    
__exit:
    return     
}

在调度锁打开的状态下且线程就绪优先级队列不为空时,如果当前线程处于运行状态:

  • 当前线程优先级最高:不进行上下文切换,标记为运行状态,从就绪列表移除,继续执行;
  • 当前线程优先级与队列中最高优先级一样且没有标记已重置时间片:不进行上下文切换,同样标记为运行状态,从就绪列表移除;(若标记时间片已重置说明:当前线程主动让出CPU资源或当前时间片已用完)
  • 其他情况:将源线程插入到就绪队列中,然后从就绪队列移除当前需要执行的目标线程,将其标记运行状态,然后进行上下文切换

3 总结

  • 当就绪队列无更高的优先级线程时,相同优先级线程采用时间片轮转方式进行调度;
  • 时钟节拍在STM32中通过滴答定时器的硬件中断实现,用全局变量rt_tick记录系统从启动开始总共经过的时钟节拍数;
  • 每经过一个时钟节拍检查当前线程的时间片是否用完,用完会重置线程剩余时间片thread->remaining_tick,然后将线程状态标记RT_THREAD_STAT_YIELD,表示已重置时间片,然后发起任务调度;
  • 系统的心跳中断不会对每个线程的优先级和时间判断,而是仅检测当前运行线程的剩余时间片,提高了运行效率。

END

你可能感兴趣的:(RT-Thread,单片机,stm32,嵌入式硬件,rtos)