关于时间片调度算法issue的分析与解决

【操作系统】-- 时间片轮转调度算法、优先级调度算法、多级反馈队列调度算法 公平地、轮流地为各个进程服务,让每个进程在一定时间间隔内都可以得到响应。按照各进程到达就绪队列的顺序,轮流让各个进程执行一个时间片,若进程未在一个时间片内执行完,则会剥夺处理机,将进程重新放到就绪队列队尾重新排队。用于进程调度抢占式算法不会优点:公平,响应快,适用于分时操作系统。缺点:由于高频率的进程切换,因此有一定的开销,不区分任务的紧急程度。根据任务的紧急程度来决定处理顺序。每个 作业/进程 有各自的优先级,调度时选择优先级最高的 作业/进程。可用于作业调度,也可用于进程调度,还可以用于I/O调度。抢占 阅读详情

本文由RT-Thread论坛用户@blta原创发布:https://club.rt-thread.org/ask/article/b3b36a52556382b2.html

在之前 rt_schedule中need_insert_from_thread的问题 提问中,笔者提出了当前时间片调度算法过于复杂,且高优先级一旦打断未执行完时间片的任务会导致该任务重新插入到其优先级readylist末尾,存在严重的不公平性(破坏了时间片的连续)。

当然笔者也PR了一个解决方案 https://github.com/RT-Thread/rt-thread/pull/5954 暂未合并

最近又有一个小伙伴发现了时间片调度的issue https://github.com/RT-Thread/rt-thread/issues/6092

大致的情况是:

  1. 低优先级的存在任务A(ticks = a),B(ticks =b),; 高优先级任务C
  2. 如果高优先级 C内存在延时c 正好等于A的时间片a
  3. 结果就是低优先级的任务只有A在一直运行, B一直运行不了

这种情况的根本原因其实还是笔者之前提到的高优先级导致当前低优先级任务插入readylist位置不对的issue,

下面笔者再次配重新整理一下这个问题,配合图例逐步分析源码并结合测试例程展示不同情况下该issue导致的问题,并尝试解决。

源码分析

rt_tick_increase

/**
 * @brief    This function will notify kernel there is one tick passed.
 *           Normally, this function is invoked by clock ISR.
 */
void rt_tick_increase(void)
{
    struct rt_thread *thread;
    rt_base_t level;
    RT_OBJECT_HOOK_CALL(rt_tick_hook, ());

    level = rt_hw_interrupt_disable();

    /* increase the global tick */
#ifdef RT_USING_SMP
    rt_cpu_self()->tick ++;
#else
    ++ rt_tick;
#endif /* RT_USING_SMP */

    /* check time slice */
    thread = rt_thread_self();

    -- thread->remaining_tick;
    if (thread->remaining_tick == 0)
    {
        /* change to initialized tick */
        thread->remaining_tick = thread->init_tick;
        thread->stat |= RT_THREAD_STAT_YIELD;

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

    /* check timer */
    rt_timer_check();
}

里面只做了两件事:

  1. 当前任务的时间片递减, 如果用完了,置位RT_THREAD_STAT_YIELD状态,调用rt_schedule,
  2. 检测是否有任务的超时了(等待资源或延时超时),如果超时,最终也会调用rt_schedule

rt_schedule

Who calling

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ywyhfOJu-1656404212734)(https://oss-club.rt-thread.org/uploads/20220627/13ff8ea0da8862bade41c25da763f670.png.webp "image-20220627091459627.png")]

排除componets中使用的情况,rt_schedule主要在下面情况中被使用

  1. clock.c : 就是刚刚提及的在Systick中断中两种比较重要的调度: 时间片调度和超时调度
  2. ipc.c ,mempool.c: 另外一种比较重要的调度: 资源阻塞和就绪调度(资源调度)
  3. scheduler.c, thread.c: 本身调度器和线程API的使用导致的直接API调度
  4. timer.c : 软定时器超时调度,使用的也是_thread_timeout超时函数,也是超时调度

鉴于 API调度一般使用在初始化阶段,Application运行中主要使用的是时间片调度,超时调度,资源调度 。后面的讨论中主要绕后三种展开:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-whmcx39c-1656404212735)(https://oss-club.rt-thread.org/uploads/20220627/ed3f79a1f6841b27ebc37cb431181a1c.png.webp "image-20220627095239360.png")]

源码

/**
 * @brief This function will perform scheduling once. It will select one thread
 *        with the highest priority, and switch to it immediately.
 */
void rt_schedule(void)
{
    rt_base_t level;
    struct rt_thread *to_thread;
    struct rt_thread *from_thread;

    /* disable interrupt */
    level = rt_hw_interrupt_disable();

    /* check the scheduler is enabled or not */
    if (rt_scheduler_lock_nest == 0)
    {
        rt_ubase_t highest_ready_priority;

        if (rt_thread_ready_priority_group != 0)
        {
            /* need_insert_from_thread: need to insert from_thread to ready queue */
            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;
                }
                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_current_thread->stat &= ~RT_THREAD_STAT_YIELD_MASK;
            }

            if (to_thread != rt_current_thread)
            {
                /* if the destination thread is not the same as 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);

                /* switch to new thread */
                RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
                        ("[%d]switch to priority#%d "
                         "thread:%.*s(sp:0x%08x), "
                         "from thread:%.*s(sp: 0x%08x)\n",
                         rt_interrupt_nest, highest_ready_priority,
                         RT_NAME_MAX, to_thread->name, to_thread->sp,
                         RT_NAME_MAX, from_thread->name, from_thread->sp));

#ifdef RT_USING_OVERFLOW_CHECK
                _rt_scheduler_stack_check(to_thread);
#endif /* RT_USING_OVERFLOW_CHECK */

                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);

                    /* enable interrupt */
                    rt_hw_interrupt_enable(level);

#ifdef RT_USING_SIGNALS
                    /* check stat of thread for signal */
                    level = rt_hw_interrupt_disable();
                    if (rt_current_thread->stat & RT_THREAD_STAT_SIGNAL_PENDING)
                    {
                        extern void rt_thread_handle_sig(rt_bool_t clean_state);

                        rt_current_thread->stat &= ~RT_THREAD_STAT_SIGNAL_PENDING;

                        rt_hw_interrupt_enable(level);

                        /* check signal status */
                        rt_thread_handle_sig(RT_TRUE);
                    }
                    else
                    {
                        rt_hw_interrupt_enable(level);
                    }
#endif /* RT_USING_SIGNALS */
                    goto __exit;
                }
                else
                {
                    RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));

                    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);
            }
        }
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(level);

__exit:
    return;
}

调度的过程大致如下:

  1. 关中断
  2. 判断调度是否上锁,rt_thread_ready_priority_group是否为0
  3. _scheduler_get_highest_priority_thread 获取当前最高优先级
  4. 和当前优先级再次比较,确定真实的最高优先级
  5. 获取最高优先级readylist的第一个任务 to_thread
  6. to_thread 和 rt_current_thread 比较
  7. 如果相等,简单设置状态,继续运行
  8. 如果不相等,把当前任务插入其优先级readylist , 切换到 to_thread
  9. 开中断

得到to_thread的再判断

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lL4gjIqM-1656404212736)(https://oss-club.rt-thread.org/uploads/20220627/8e468dc6ce6a747bed51e19a17a40b13.png.webp “image-20220626122316431.png”)]

之所以搞的这么复杂,是因为当前的调度策略是把运行的任务,移出了readylist,那么获取的highest_ready_priority,to_thread只是红色圈中的,还需要结合正在运行的 thread 再次判断,下面将结合下图说明上图中的1,2,3 中情况

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xhURNuMS-1656404212736)(https://oss-club.rt-thread.org/uploads/20220627/408b514a80ddf01e6577e9fa5fc55296.png.webp “image-20220627104008931.png”)]

假设 当前rt_thread_priority_table中存在三个优先级列表,下标分别为h, m, l (h<m<l), 分别代表了高,中,低三个优先级

1. 当前优先级小于highest_ready_priority
 if (rt_current_thread->current_priority < highest_ready_priority)

这个很好理解:没有和当前任务优先级相同的任务,其优先级readylist为空,存在如下两种情况:

**1.1 低优先级就绪 **

存在 B,D,E3个任务, 当前 E 因资源阻塞或者延时中,B正在运行;某一时刻任务E就绪(资源就绪或者超时),插入对应readylist后发起一次调度:highest_ready_priority = l > m

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VQLU1WKy-1656404212738)(https://oss-club.rt-thread.org/uploads/20220627/607c75814e7bd14301770d22e780b432.png.webp “image-20220627100541109.png”)]

对于某一任务的阻塞,图例仅展示资源阻塞(调度),或者延时阻塞(调度)中的一种,下同,不再说明

**1.2 当前运行任务的时间片用完 **

同样存在 B,D,E 3个任务,当前B在运行; 某一时刻B的时间片用完,thread->stat |= RT_THREAD_STAT_YIELD,发起一次调度: highest_ready_priority = l > m

因为最高优先级m下,只有B一个任务,所以尽管其时间片已经使用完,还会复位ticks,再次运行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WaWLFd2E-1656404212739)(https://oss-club.rt-thread.org/uploads/20220627/607c75814e7bd14301770d22e780b432.png.webp “image-20220627100541109.png”)]

这两种情况,最后运行的还是当前任务

2. 当前优先级等于highest_ready_priority,且当前任务时间片未用完
if (rt_current_thread->current_priority == highest_ready_priority && (rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK) == 0)

存在 B,C 两个同优先级的任务,当前C因资源阻塞或者延时中,B正在运行; 某一时刻C就绪,插入对应readylist后发起一次调度: highest_ready_priority = m

但此时B的时间片还未用完,依旧无需礼让切换,让C等着吧。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X3FJcOVw-1656404212739)(https://oss-club.rt-thread.org/uploads/20220627/20d21df97d2686267e12a74dc7bab33a.png.webp “image-20220627104748330.png”)]

最后运行的也当前任务

3 需要切换新的任务

可以直接列出剩余的三种情况,

3.1 当前优先级等于highest_ready_priority,且当前任务时间片用完

if (rt_current_thread->current_priority == highest_ready_priority && (rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK) != 0)

存在 B,C 两个同优先级的任务,当前C已经resume就绪, B正在运行;某一时刻B的时间片用完,thread->stat |= RT_THREAD_STAT_YIELD,发起一次调度: highest_ready_priority = m

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-89oiyinH-1656404212740)(https://oss-club.rt-thread.org/uploads/20220627/20d21df97d2686267e12a74dc7bab33a.png.webp “image-20220627104801028.png”)]

需要礼让,把当前任务B插入对应优先级readylist后, 然后切换到C

3.2 当前优先级大于highest_ready_priority,且当前任务时间片用完

if (rt_current_thread->current_priority > highest_ready_priority && (rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK) != 0)

这个情况实际是不存在的:

(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK) != 0 表示时间片用完了,是通过rt_tick_increase->rt_schedule,即时间片用完,置位RT_THREAD_STAT_YIELD后发起的一次rt_schedule调度,调度结束会清除RT_THREAD_STAT_YIELD状态

rt_current_thread->current_priority > highest_ready_priority表示高优先级就绪,是通过 rt_tick_increase->rt_timer_check->_thread_timeout->rt_schedule

即高优先级的超时函数触发调度一次rt_schedule调度

很明显,这两种调度不会同时发生,就算rt_tick_increase->rt_schedule先发生,也会清除RT_THREAD_STAT_YIELD状态,导致条件不满足。

3.3 当前优先级大于highest_ready_priority,且当前任务时间片未用完

if (rt_current_thread->current_priority > highest_ready_priority && (rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK) == 0)

存在任务 ABCDE, 当前A,E因资源阻塞或者延时中,B正在运行;某一时刻A就绪,其任务优先级高于当前任务,虽然当前任务时间片有剩余,也必须切出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tkRtvLTk-1656404212741)(https://oss-club.rt-thread.org/uploads/20220627/1f7ac97e450f611085a08f489750be2e.png.webp “image-20220627105736347.png”)]

高优先级就绪后会把当前任务插入的readylist最后,也就是这个插入导致了众多的问题出现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i1MVlmcM-1656404212741)(https://oss-club.rt-thread.org/uploads/20220627/273af8da13e953920652f03ce9761fbb.png.webp “image-20220626121200429.png”)]

rt_schedule_insert_thread

void rt_schedule_insert_thread(struct rt_thread *thread)
{
    register rt_base_t temp;

    RT_ASSERT(thread != RT_NULL);

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();

    /* it's current thread, it should be RUNNING thread */
    if (thread == rt_current_thread)
    {
        thread->stat = RT_THREAD_RUNNING | (thread->stat & ~RT_THREAD_STAT_MASK);
        goto __exit;
    }

    /* READY thread, insert to ready queue */
    thread->stat = RT_THREAD_READY | (thread->stat & ~RT_THREAD_STAT_MASK);
    /* insert thread to ready list */
    rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),
                          &(thread->tlist));

    RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("insert thread[%.*s], the priority: %d\n",
                                      RT_NAME_MAX, thread->name, thread->current_priority));

    /* set priority mask */
#if RT_THREAD_PRIORITY_MAX > 32
    rt_thread_ready_table[thread->number] |= thread->high_mask;
#endif /* RT_THREAD_PRIORITY_MAX > 32 */
    rt_thread_ready_priority_group |= thread->number_mask;

__exit:
    /* enable interrupt */
    rt_hw_interrupt_enable(temp);
}

可以明显看出,rt_schedule_insert_thread调用的是rt_list_insert_before,即把当然运行任务插入到其优先级readylist的最后

问题

因为当前运行线程是remove出readylist的, 再插入readylist是必需的,但是使用rt_list_insert_before把未执行完时间片的任务插入到readylist的最后面,下次轮到该优先级时,会直接执行C,B的时间片被分成了两次或以上来执行,同理C也可能面临同样的情况。这就导致了很多问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v4OWJzBz-1656404212743)(https://oss-club.rt-thread.org/uploads/20220627/136d2c18a7212980e1894032f662abde.png.webp “image-20220627144433820.png”)]

测试

测试环境: 基于stm32f4disc1开发板,创建3个线程分别控制LED3,LED4,LED5闪烁

  1. rt_led1_thread: 优先级9,时间片1,延时 t1 = 5ms闪烁LED4
  2. rt_led2_thread: 优先级11,时间片t2 =4(ms),自定义200延时闪烁LED5
  3. rt_led3_thread: 优先级11,时间片t3 = 2(ms),自定义300延时闪烁LED3

创建线程

int main(void)
{
    rt_thread_init( &rt_led1_thread,                 
                    "LED1",                          
                    led1_thread_entry,            
                    RT_NULL,                       
                    &rt_led1_thread_stack[0],       
                    sizeof(rt_led1_thread_stack),  
                    6,
                    1);                               
    rt_thread_startup(&rt_led1_thread);
    
    rt_thread_init( &rt_led2_thread,                 
                    "LED2",                          
                    led2_thread_entry,              
                    RT_NULL,                       
                    &rt_led2_thread_stack[0],     
                    sizeof(rt_led2_thread_stack),
                    11,
                    4);                         
    rt_thread_startup(&rt_led2_thread);
    
    
    rt_thread_init( &rt_led3_thread,       
                    "LED3",               
                    led3_thread_entry,        
                    RT_NULL,                   
                    &rt_led3_thread_stack[0],     
                    sizeof(rt_led3_thread_stack),  
                    11,
                    2);                         
    
    rt_thread_startup(&rt_led3_thread);
    while (1)
    {
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}

线程入口函数

/* customer short delay  */
void delay (uint32_t count)
{
    for(; count!=0; count--);
}

void led1_thread_entry( void *p_arg )
{
    for( ;; )
    {
        HAL_GPIO_WritePin(GPIOD, LD4_Pin, GPIO_PIN_SET);
        rt_thread_delay( 5 );
        HAL_GPIO_WritePin(GPIOD, LD4_Pin, GPIO_PIN_RESET);
        rt_thread_delay( 5);
    }
}

void led2_thread_entry( void *p_arg )
{
    for( ;; )
    {
        HAL_GPIO_WritePin(GPIOD, LD5_Pin, GPIO_PIN_SET);
        delay( 300 );
        HAL_GPIO_WritePin(GPIOD, LD5_Pin, GPIO_PIN_RESET);
        delay( 300 );
    }
}

void led3_thread_entry( void *p_arg )
{
    for( ;; )
    {
        HAL_GPIO_WritePin(GPIOD, LD3_Pin, GPIO_PIN_SET);
        delay( 300 );
        HAL_GPIO_WritePin(GPIOD, LD3_Pin, GPIO_PIN_RESET);
        delay( 300 );
    }
}

不同时间片对调度的影响

1)t1 = 5, t2 = 4, t3=2

新就绪的任务优先级高于当前任务,当前任务时间片有剩余

通过逻辑分析仪抓取三个LED pin 电平
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P6SkB3qQ-1656404212744)(https://oss-club.rt-thread.org/uploads/20220627/b6af85346d5e5c9237b221f2b651c5ef.png.webp “image-20220627152027184.png”)]

波形中可以明显看出thread2时间片 4ms(4 ticks) ,执行3ms后因为高优先级thread1打断,剩余的1ms会在thread3执行完后才执行。thread3甚至出现了连续执行的情况,稍后分析。总之时间片调度完全乱掉了。

**2)t1 = 5, t2 = 3, t3=2 **

新就绪的任务优先级高于当前任务,当前任务时间片无剩余

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vobwJCEA-1656404212745)(https://oss-club.rt-thread.org/uploads/20220627/65dd10f9f351f67925e38fa7fae55d12.png.webp “image-20220627153223614.png”)]

还记得我们上面提到了3.2 不能同时满足的条件吗?

if (rt_current_thread->current_priority > highest_ready_priority && (rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK) != 0)

只是因为软件的写法(第一次rt_scheduler后清除了RT_THREAD_STAT_YIELD状态),这个条件不能同时满足。从波形图看这两条件是可以同时满足的:

某一个tick中断了,线程thread2的时间片用完了,时间片调度度后, rt_timer_check发现正好有一个高优先级线程超时了,于是先后触发了两次调度:

第一次: 时间片调度, rt_current = thread3, rt_thread_priority_table[11] -> thread2

第二次:超时调度,把rt_current插入其readylist , rt_thread_priority_table[11] -> thread2–>thread3, rt_current = thread2

然后,当高优先级thread1再次阻塞时,首先运行的还是thread2。

这就是为什么thread2连续运行了两个t2(3*2),同理 thread3也是, 和我们期望的轮流执行不一致。

3)t1 = 5, t2 = 5, t3=x (t2/t3 =5)

高优先级的延时正好等于某一低优先级线程的时间片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-48nRTlqD-1656404212746)(https://oss-club.rt-thread.org/uploads/20220627/a2472bf1459bac299fef1be3c75dbd79.png.webp “image-20220627161546060.png”)]

这种情况造成的结果比较明显,导致thread3一直没有运行的机会了,也就是小伙伴发现的issue https://github.com/RT-Thread/rt-thread/issues/6092

从现象上看情况3是最极端,最严重的,但是你可以立即发现。 不像情况1,2虽然表面上每个thread还在执行,内部的时间片已经完全乱掉,往往这种隐形的issue会导致更加严重的后果。

解决办法

既然看到了时间片调度的众多issue,也知道了问题的原因:高优先级打断未执行完时间片的任务导致该任务被重新插入到其优先级readylist末尾

那么就开始着手解决。

方案一

先看下小伙伴同时提出的解决办法:https://github.com/RT-Thread/rt-thread/pull/6095/files

他增加了一个RT_THREAD_STAT_SCHEDULING 状态来判断和避免issue

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-891K9ZEV-1656404212747)(https://oss-club.rt-thread.org/uploads/20220627/db0821261606d7034e5769b518f70957.png.webp “image-20220627163838256.png”)]

这种做法应该没啥问题,暂未测试

方案二

直击问题本质,既然是插入的问题导致的,调整一下插入顺序即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dViIpV9g-1656404212748)(https://oss-club.rt-thread.org/uploads/20220627/41c355fb448e12f2f5a11a8ff6f76601.png.webp “image-20220627170547682.png”)]

除了时间片使用完,需要yield,插入其readylist的末尾,其他情况均插入readylist的头部

再次测试t1 = 5, t2 = 5, t3=2,结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FgkQYDXw-1656404212748)(https://oss-club.rt-thread.org/uploads/20220627/ca638c753cc4a2971af6d4565b67eff5.png.webp “image-20220627165958080.png”)]

可以看到,ticks连续执行了,线程没有重复执行,没有跳过,也没有不被执行的,完美解决。

方案三

更近一步,可不可以不把运行的thread移除readylist呢?如果可以,没有remove,也就没有了insert,没有了issue

其实FreeRTOS使用的就是这种方案:

taskSELECT_HIGHEST_PRIORITY_TASK()

	#define taskSELECT_HIGHEST_PRIORITY_TASK()														\
	{																								\
	UBaseType_t uxTopPriority;																		\
																									\
		/* Find the highest priority list that contains ready tasks. */								\
		portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );								\
		configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );		\
		listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );		\
	} /* taskSELECT_HIGHEST_PRIORITY_TASK() */

listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
{																							\
List_t * const pxConstList = ( pxList );													\
	/* Increment the index to the next item and return the item, ensuring */				\
	/* we don't return the marker used at the end of the list.  */							\
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
	{																						\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}																						\
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											\
}

可以看出它再获取最高优先级后,会直接把readylist的index往后移动一次,获取下一个任务。虽然这个它的时间片ticks为常量1,但可以给我们提供一个很好的参考,没有remove, insert。

笔者之前PR的一个解决方案 https://github.com/RT-Thread/rt-thread/pull/5954 就是基于该方案

rt_list_jump_next

虽然rt_list_t也是一个双向链表,但是少了一个成员变量index,不能像FreeRTOS那样直接移动完成时间片的调度

首先我们需要新增一个rt_list_t操作函数rt_list_jump_next,完成rt_list_t头部往后移动一次,同时要保证list成员相对顺序不变

/**

 * @brief move the list to its next's next position
   *
 * @param l list to insert it
   */
   rt_inline void rt_list_jump_next(rt_list_t *l)
   {
   l->next->prev = l->prev;
   l->prev->next = l->next;


    l->prev = l->next;
    
    l->next->next->prev = l;
    l->next = l->next->next;
    
    l->prev->next = l;

}

大致操作如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4BAt4UE9-1656404212749)(https://oss-club.rt-thread.org/uploads/20220627/305ae2f95eb79672b4ef013bf2199fde.png.webp “image-20220627182045409.png”)]

这种直接移动list head的做法,一次调度会有6次指针赋值操作,

而原来的一次调度remove(4次), insert(4次)加起来是8次指针赋值操作

重定义_scheduler_get_highest_priority_thread

--- a/src/scheduler.c
+++ b/src/scheduler.c
@@ -180,6 +180,19 @@ static struct rt_thread* _scheduler_get_highest_priority_thread(rt_ubase_t *high
     highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
 #endif /* RT_THREAD_PRIORITY_MAX > 32 */

+    /* if current thread is yield , move the head of priority list to next and change its status to READY */
+    if((rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK) != 0)
+    {
+        if(rt_current_thread->tlist.next != rt_current_thread->tlist.prev)
+        {
+            /* multiple threads, move the list head to next */
+            rt_list_jump_next(&rt_thread_priority_table[rt_current_thread->current_priority]);
+        }
+
+        /* clear YIELD and ready thread*/
+        rt_current_thread->stat = RT_THREAD_READY | (rt_current_thread->stat & ~(RT_THREAD_STAT_YIELD_MASK|RT_THREAD_STAT_MASK));
+    }
+
     /* get highest ready priority thread */
     highest_priority_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
                               struct rt_thread,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XWbd9Jhp-1656404212750)(https://oss-club.rt-thread.org/uploads/20220627/04d946e87daa7483def3b46fe7f1ee7d.png.webp “image-20220627175149148.png”)]

简化rt_schedule

上面的分析可知,rt_schedule之所以搞的复杂,是因为获取的highest_ready_priority,to_thread不包含对当前正在运行thread的计算。现在我们不把rt_current_thread 移除其readylist,获得的highest_ready_priority,to_thread就是最终的

@@ -258,7 +271,6 @@ void rt_system_scheduler_start(void)
     rt_current_thread = to_thread;
 #endif /* RT_USING_SMP */

-    rt_schedule_remove_thread(to_thread);
     to_thread->stat = RT_THREAD_RUNNING;

     /* switch to new thread */
@@ -436,28 +448,8 @@ void rt_schedule(void)

         if (rt_thread_ready_priority_group != 0)
         {
-            /* need_insert_from_thread: need to insert from_thread to ready queue */
-            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;
-                }
-                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_current_thread->stat &= ~RT_THREAD_STAT_YIELD_MASK;
-            }
-
             if (to_thread != rt_current_thread)
             {
                 /* if the destination thread is not the same as current thread */
@@ -467,12 +459,6 @@ void rt_schedule(void)

                 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);

                 /* switch to new thread */
@@ -531,7 +517,6 @@ void rt_schedule(void)
             }
             else
             {
-                rt_schedule_remove_thread(rt_current_thread);
                 rt_current_thread->stat = RT_THREAD_RUNNING | (rt_current_thread->stat & ~RT_THREAD_STAT_MASK);
             }
         }
(END)

rt_system_scheduler_start

@@ -258,7 +271,6 @@ void rt_system_scheduler_start(void)
     rt_current_thread = to_thread;
 #endif /* RT_USING_SMP */

-    rt_schedule_remove_thread(to_thread);
     to_thread->stat = RT_THREAD_RUNNING;

     /* switch to new thread */

同样测试t1 = 5, t2 = 5, t3=2,结果正常如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FP2FCDvh-1656404212751)(https://oss-club.rt-thread.org/uploads/20220627/a05b09d5595739809f8defa457ebdb38.png.webp “image-20220627180659240.png”)]

时间片轮转(RR)、优先级调度算法以及多级反馈队列调度算法 一、调度算法 (一)时间片轮转(RR, Round-Robin) 例题:各进程到达就绪队列的时间、需要的运行时间如下表所示。使用时间片轮转调度算法,分析时间片大小分别是2、5时的进程运行情况。 常用于分时操作系统,更注重“响应时间”,因而此处不计算周转时间 1. 时间片大小为2 2. 时间片大小为5 如果时间片太大,使得每个进程都可以在一个时间片内就完成,则时间片轮转调度算法退化为先来先服务调度算法,并且会增大进程响应时间。因此时间片不能太大。 另一方面,进程调度、切换是有时间代价的(保存、恢 阅读详情

相关推荐

调度算法:时间片轮转、优先级、多级反馈队列的详细图解

总览: 调 度 算 法 : 1.时 间 片 轮 转 调 度 算 法 (RR) 2.优 先 级 调 度 算 法 3.多 级 反 馈 队 列 调 度 算 法 Tips:各种调度算法的学习思路 1.算法思想 2.算法规则 3.这种调度算法是用于作业调度还是进程调度? 4.抢占式?非抢占式? 5.优点和缺点 6. 是否会导致饥饿 例题:各进程到达就绪队列的时间、需要的运行时间如下表所示。使用时间片轮转调度算法,分析时间片大小分别是2,5时的进程运行情况。 时间片轮转调度算法: 轮流让就绪队列中的进程依次执行一个时

HelloByte的博客 2万+

RT-Thread入门笔记5-线程的时间片轮询调度

具体应用中,线程总数不受限制,能创建的线程总数只和具体硬件平台的内存有关。假设有2个优先级相同的就绪态线程A与B,A线程的时间片设置为10,B线程的时间片设置为5,那么当系统中不存在比A优先级高的就绪态线程时,系统会在A、B线程间来回切换执行,并且每次对A线程执行10个节拍的时长,对B线程执行5个节拍的时长。时间片只有在相同优先级的就绪态线程中起作用,系统对优先级相同的就绪态线程采用时间片轮转的调度方式进行调度时,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick)。

qq_42170079的博客 1541

C++编程实现进程调度算法(FCFS+RR)

操作系统的课程实验,C++编程实现进程调度算法(FCFS和RR)

Yuhan2001的优快云博客 1万+

记一次处理rt-thread优先级低线程无法执行

sss

yfj300的专栏 2783

OneOS操作系统入门-04:时间片轮转调度介绍与实验详解

时间片是操作系统分配给每个正在运行的进程(或线程)的一个固定时间段,在这个时间段内,进程可以独占CPU并执行其任务。时间片的主要目的是确保所有处于就绪状态的进程都能获得公平的CPU时间,避免某些进程长时间占用CPU。在时间片轮转调度中,系统会维护一个就绪队列,所有就绪进程按照到达的顺序排队等待执行。操作系统按照队列顺序依次分配时间片,每个进程在获得CPU后开始执行,直到其时间片用完。时间片的长度通常由操作系统的调度器决定,这个长度可以根据系统的性能要求和进程的优先级进行调整。

yjldsg的博客 2581

Sony DPT RP1/CP1破解攻略

前言 自己有一台Sony DPT CP1和一台kindle,两个都已经用了很多年。直到最近发现微信读书上经常可以找到一些免费而且不错的书,所以就萌生了在两台机器上使用微信读书的想法。kindle比较容易实现,直接打开内置浏览器,进入微信读书网页版就行了,而Sony的电纸书则只能通过破解+安装微信读书的方式实现。大概弄了两三个小时吧,也遇到了一些问题,所以这里记录一下整个过程,希望能方便后来的使用者。 首先感谢HappyZ, osc_t5nbj8ds。前者是提供公开破解的方法的大神,后者则是解决了我遇到的问题

weixin_43467572的博客 1万+

RT-thread内核之线程调度算法

一个操作系统如果只是具备了高优先级任务能够“立即”获得处理器并得到执行的特点,那么它仍然不算是实时操作系统。因为这个查找最高优先级线程的过程决定了调度时间是否具有确定性,例如一个包含n个就绪任务的系统中,如果仅仅从头找到尾,那么这个时间将直接和n相关,而下一个就绪线程抉择时间的长短将会极大的影响系统的实时性。当所有就绪线程都链接在它们对应的优先级队列中时,抉择过程就将演变为在优先级数组...

weixin_34112208的博客 533

TensorRT-LLM环境配置避坑指南:从安装到实战

本文是一份详尽的TensorRT-LLM环境配置避坑指南。文章重点解析了从Python、CUDA、PyTorch版本选择,到解决mpi4py安装这一核心难题的完整流程,并提供了经过验证的稳定版本组合与实战排错方案,帮助开发者高效部署大语言模型推理优化环境。

a0b1c2d3的专栏 983

rt thread 3.1.3版本操作系统定时器卡死的问题

1、操作系统定时器 操作系统定时器是由rt thread内核提供的一个定时功能,支持硬件定时器或软件定时器。最后在一个产品的使用了多个定时器来实现数据通信指示灯的功能,原理是创建一个周期定时器,创建一个单次定时器,当有数据通信时,启动周期定时器来控制灯闪烁,启动单次定时器来超时关闭灯和周期定时器。当有数据再次到来时重新启动,达到了闪灯的效果。 2、定时器卡死的问题 这个定时器由2个不同优先级的线程调用,在程序经过长时间运行后,会出现定时器卡死,即程序一直运行在rt_timer_...

fhqlongteng的博客 3230

深入解析HAL_UART_Receive_IT:如何高效配置串口中断接收

本文深入解析了STM32 HAL库中`HAL_UART_Receive_IT`函数的工作原理与高效配置方法。该函数并非直接接收数据,而是作为中断接收的启动器和配置器,通过开启相关中断,由`UART_Receive_IT`在后台完成数据搬运。文章详细对比了中断与轮询模式的区别,并提供了不定长数据接收的完整实战流程、常见问题避坑指南及高级应用技巧,帮助开发者构建稳定高效的串口通信。

weixin_29162439的博客 626

OpenCV轮廓匹配实战:5分钟搞定Hu矩形状识别(附完整代码)

本文详细介绍了使用OpenCV进行轮廓匹配的实战技术,重点讲解了Hu矩形状识别在工业质检等领域的应用。通过完整的代码示例和参数调优技巧,帮助开发者在5分钟内实现高效的形状识别,提升工业检测的准确性和效率。

nnn11的博客 675

记一次rt-thread的进程无法切换问题

rt-thread源码解析今天开发过程遇到一个很奇怪的问题,有一个对喇叭进行声音播放的进程里每次进入这个进程后其他任务都无法工作了,因为这个播放的任务优先级调到了比其他任务都低的级别,本质是为了能在播放音乐时不影响其他任务继续工作,所以在播放的任务是一个大约2分钟的传输数据,奇怪在于这两分钟内其他任务既然无法工作了,只有在这个任务结束后一切才正常,其实我很快就定位到问题,但在解决这个问题后还是将rt

Jerry的博客 5347

SAP SD POD 配置与 VLPOD 操作:3步解决发货10件开票7件的业务场景

本文详细解析了SAP SD POD功能在解决发货与开票数量差异中的应用,通过3步配置法实现精准匹配。重点介绍了POD核心逻辑、关键配置步骤及VLPOD操作技巧,帮助企业高效处理易损品运输等复杂场景,提升供应链管理效率。

weixin_30613343的博客 393

时间片轮转调度算法

轮转调度算法Round-Robin(RR) 在分时系统中都采用时间片轮转算法进行进程调度。 时间片是指一个较小的时间间隔,通常为10-100毫秒。【百度百科】在早期的时间片轮转法中,系统将所有的就绪进程按先来先服务的原则,排成一个队列,每次调度时,把CPU分配给队首进程,并令其执行一个时间片.时间片的大小从几ms到几百ms.当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片

程序员,他们想的是什么?他们想的永远都是技术,他们崇尚的也永远都是技术。 2万+

淘宝x-mini-wua生成机制与硬件信息关联性深度解析

本文深度解析了淘宝x-mini-wua的生成机制与硬件信息的关联性,详细介绍了其作为设备指纹令牌的核心作用及技术实现细节。通过分析基础硬件层、环境特征层和行为特征层的数据采集,揭示了淘宝如何利用AES加密和分级加密策略防止破解,并探讨了其安全防护与对抗策略。

weixin_29243279的博客 228

Google Earth Engine 红树林制图方法的代码(三个模块)

谷歌地球引擎红树林制图方法(GEM)提供了一种直观、易用且可复制的工具,适用于广泛的非专业沿海管理者和决策者。该工具全面回顾并整合了相关的红树林遥感文献,并利用云计算的力量,包括简化的基于图像的潮汐校准方法。GEM 可免费用于非营利用途,并在 GEE 中运行,包含全面且详细注释的代码。GEM 专门设计用于绘制全球范围内多日期红树林分布并量化其动态变化。目前可用的工具和方法未能充分利用沿海管理者通常持有的丰富地方专业知识——GEM 致力于填补这一空白,帮助结合地方专业知识与 GEE 的云计算能力。

此星光明博客 559

进程调度:时间片轮转调度算法

一、实验目的 (1) 加深对进程的理解 (2) 理解进程控制块的结构 (3) 理解进程运行的并发性 (4) 掌握时间片轮转法进程调度算法 二、实验原理 (1)建立进程控制块 (2)设计两个链队列,分别表示就绪队列和完成队列 (3)用户输入进程标识符,进程到达时间,进程所需的时间,申请空间存放进程,PCB信息。 (

FengNingXueChen的博客 3万+

Hindsight 内存爆炸 4 个词排查清单:9,284 条 6 成是 SSH 调试日志——Agent 标签系统的实战复盘

9,284 条记忆里 66% 是 SSH 调试日志。4 个标签被滥用=0 区分度。3 个 entity_labels + retain_mission + retain_every_n_turns 配置把噪声压回 5%——附 PATCH 脚本和实测前后数据。

Starry-SkyJing的博客 57

记录——时间片轮转调度算法模拟程序,用Java实现

1、合理设计PCB结构,设计模拟指令格式,并以文件形式存储,程序能够读取文件并自动生成指令序列。2、根据文件内容,建立模拟进程队列,并能采用时间片轮转调度算法对模拟进程进行调度。

yeliner的博客 9592

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

调度算法(时间片轮转 优先级调度 多级反馈队列)

1. 知识总览 调度算法 (1)时间片轮转调度算法(RR) (2)优先级调度算法 (3)多级反馈队列调度算法 Tips:各种调度算法的学习思路 1.算法思想 2.算法规则 3.这种调度算法是用于作业调度还是进程调度? 4.抢占式?非抢占式? 5.优点和缺点 6.是否会导致饥饿 2.时间片轮转调度算法(RR) (1)算法思想 公平地、轮流地为各个进程服务,让每个进程在一-定时间间隔内都可以得到响应 ...

yrx420909的博客 2万+

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

【操作系统 - 2】时间片轮转RR进程调度算法

【操作系统 - 2】时间片轮转RR进程调度算法。学习至此,发现很多学了但很久没用的知识,久而久之,慢慢遗忘。等哪天还需要的话,却发现已经忘得差不多了,即使整理了文档(word等),还是得从头再学一遍。读研第一学期,发现很多东西都可以从博客上学习到,也有不少博主呕心沥血整理了挺多有用的博文。于是,本人借此契机,也慢慢开始整理一些博文,不断改进完善中。整理博文(IT)有如下目的。1. 首要目的:记录“求学生涯”的所学所悟,不断修改,不断更新!(有读者的互动)2. 其次目的:在这“开源”的时代,整理并分享所学所悟

Houchaoqun_XMU的博客 5万+

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

【进程调度算法】时间片轮转调度算法、多级反馈队列调度算法(Java实现)

时间片轮转调度算法(RR)是十分简单的进程调度算法。 进程在执行时的情况 在该时间片内进程执行完毕,这种情况调度程序将立即把该进程弹出队列,并把CPU分配给新的队首进程 在该时间片内进程未执行完毕,调度程序将立即中断该进程执行,把该进程加入队尾,并将CPU分配给新的队首进程 该时间片未结束时有一个新的进程到达,就把新进程放到队尾,并继续执行该时间片(自己的理解,不一定对~) 第3种情况,在实现时的简单做法就是: 直接把运行时间加到该时间片结束或进程执行完毕,接着while循环把所有时间已到达的进程从pr

hans774882968的博客 6788

操作系统进程调度算法——先来先服务、时间片轮转、优先级调度算法

先来先服务调度算法、时间片轮转调度算法、优先级调度算法

weixin_57130368的博客 2万+

操作系统——时间片轮转调度算法(RR)

RR算法总体思路流程图

abalinoca的博客 1万+

进程调度算法之时间片轮转调度(RR),优先级调度以及多级反馈队列调度

(拓展:可以将因I/O而阻塞的进程重新放回原队列,这样I/O型进程就可以保持较高优先级)轮流让就绪队列中的进程依次执行一个时间片(每次选择的都是排在就绪队列队头的进程)1.题1:各进程到达就绪队列的时间、需要的运行时间、进程优先数如下表所示。2.题2:各进程到达就绪队列的时间、需要的运行时间、进程优先数如下表所示。(注:以下括号内表示当前时刻就绪队列中的进程、进程的剩余运行时间)每个作业/进程有各自的优先级,调度时选择优先级最高的作业/进程。因此新进程会抢占处理机,原来运行的进程放回k级队列队尾。

jungleirim的博客 1万+

时间片轮转进程调度算法(实习报告)

时间片轮转进程调度算法实验目的和要求实验内容实验过程数据结构:部分代码:实验结果:分析和讨论完整代码 实验目的和要求 (1) 掌握时间片轮转进程调度的概念和算法 (2) 加深对处理机分配的理解 实验内容 在集成开发环境下使用C++语言利用相应的函数,编写程序实现时间片轮转进程调度算法,学会运行程序和调试程序。具体内容包括: (1) 初始化各进程相关数据,包括各进程已运行时间、要求运行时间、进程...

weixin_43661839的博客 9902
上一篇: 这才是优美的文件系统挂载方式,亲测有效
下一篇: RT-ThreadXSTM32F407智能车培训报名啦!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值