[rtt]rt-thread线程调度机制线程切换时机(代码片段)

BRRRRRRRRR_ BRRRRRRRRR_     2022-12-27     174

关键词:

1. 问题

最初接触RTT时,对于线程切换时机的相关概念主要来自以下几个方面:

  1. RTT在创建线程时,需要输入线程的时间片参数,时间片的单位为OS Tick。
  2. 线程休眠函数rt_thread_delay()、设置软件定时器、以及一些如信号量、邮箱等可以设置timeout的线程间通讯和同步方式,设置的时间参数都是以OS Tick为最小单位。
  3. RTT文档也写出:操作系统中最小的时间单位是时钟节拍 (OS Tick)

由此,我便将其理解为:OS Tick也是系统调度的最小单位,每一个OS Tick,操作系统就会执行一次调度器,判断各线程状态和优先级,从而执行线程调度

但如果该说法成立,似乎又解释不通以下几个问题(以下假设tick为1ms时):

  1. 线程1在执行200us后就将自己挂起,如果到下一个tick才会运行调度器、运行线程2,明显是对CPU资源的极大浪费,同时期间的800ms时间是被谁占用?切换到空闲线程也是需要调度器来执行的。

  2. . 如果软件定时器设置为1ms,软件定时器优先级以下的线程都无法执行?

2. 验证

针对线程调度和OS Tick的理解与以上几个问题的冲突,决定先通过代码测试下我的想法是否正确。

测试代码:

static int thread_run_count = 0;
static int start_tick = 0;
static struct rt_completion tick_test_sema1;
static struct rt_completion tick_test_sema2;

void tick_test_thread1(void* arg)

    if(thread_run_count == 0)
    
        start_tick = rt_tick_get();//记录启动时间
    
    while(1)
    
        rt_completion_done(&tick_test_sema1);//释放sema1
        rt_completion_wait(&tick_test_sema2, RT_WAITING_FOREVER);//等待sema2

        if(thread_run_count == 100000)//两个线程切换了10000次
        
            rt_kprintf("-----tick count: %d------\\n", rt_tick_get() - start_tick);//输出测试耗时
            return;
        
        thread_run_count++;

    



//测试线程2,信号量1、2互换,其余相同
void tick_test_thread2(void* arg)

    if(thread_run_count == 0)
    
        start_tick = rt_tick_get();
    
    while(1)
    
        rt_completion_done(&tick_test_sema2);
        rt_completion_wait(&tick_test_sema1, RT_WAITING_FOREVER);

        if(thread_run_count == 100000)
        
            rt_kprintf("-----tick count: %d------\\n", rt_tick_get() - start_tick);
            return;
        
        thread_run_count++;

    



void tick_test(void)

    rt_completion_init(&tick_test_sema1);
    rt_completion_init(&tick_test_sema2);

    rt_thread_t test_thread = rt_thread_create("test1",tick_test_thread1, NULL,
                                                1024, 15, 20);
    if (test_thread != RT_NULL)
    
        rt_thread_startup(test_thread);
    


    test_thread = rt_thread_create("test2",tick_test_thread2, NULL,
                                    1024, 15, 20);
    if (test_thread != RT_NULL)
    
        rt_thread_startup(test_thread);
    

两个相同优先级的线程,线程1释放信号量1,之后挂起等待信号量2,线程2释放信号量2,之后挂起等待信号量1。
两个线程交替执行共100000次,输出执行耗时。

如果之前的想法成立,系统仅在每个tick时,才会执行调度器,那10W次的线程切换应至少消耗10W个tick,10W ms,而实际测试耗时为258个tick,258ms。

之后将OS Tick,由1ms改为10ms:

/* Tick per Second */
#define RT_TICK_PER_SECOND	100 

相同测试内容,耗时为27个tick,270ms。

很明显,两个线程频繁切换的耗时几乎是固定的,与OS Tick无关,那么之前的想法:OS Tick是系统调度的最小单位很明显就是错误的

3. 代码分析

  1. 每个OS Tick中断,会执行一次调度器,这个是之前就分析到的。
void rt_tick_increase(void)

    struct rt_thread *thread;

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

    /* 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;

        /* yield */
        rt_thread_yield();//------里面执行调度器
    

    /* check timer */
    rt_timer_check();


rt_err_t rt_thread_yield(void)

    rt_schedule();//-----这个是调度器

    return RT_EOK;



  1. rt_thread_delay()实际调用的是函数rt_thread_sleep(),进入该函数后查看,在对线程挂起、超时定时器进行设置并运行后,执行了一次调度器:
rt_err_t rt_thread_sleep(rt_tick_t tick)

    register rt_base_t temp;
    struct rt_thread *thread;

    /* set to current thread */
    thread = rt_thread_self();
    RT_ASSERT(thread != RT_NULL);
    RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);

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

    /* suspend thread */
    rt_thread_suspend(thread);

    /* reset the timeout of thread timer and start it */
    rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, &tick);
    rt_timer_start(&(thread->thread_timer));

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);

    rt_schedule();//-------一样执行了调度器,在这

    /* clear error number of this thread to RT_EOK */
    if (thread->error == -RT_ETIMEOUT)
        thread->error = RT_EOK;

    return RT_EOK;


因此所有通过delay函数挂起的线程,在挂起后都会立刻执行一次调度器,进行线程切换,无需等到下个OS Tick到来。



  1. 其他会将该线程挂起的操作:等待信号量、互斥锁,从邮箱、消息队列中阻塞接收数据时也基本相同:将当前线程挂起后执行调度器。以信号量为例:
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time)

    register rt_base_t temp;
    struct rt_thread *thread;

    /* parameter check */
    RT_ASSERT(sem != RT_NULL);
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);

    RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(sem->parent.parent)));

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

    RT_DEBUG_LOG(RT_DEBUG_IPC, ("thread %s take sem:%s, which value is: %d\\n",
                                rt_thread_self()->name,
                                ((struct rt_object *)sem)->name,
                                sem->value));

    if (sem->value > 0)
    
        /* semaphore is available */
        sem->value --;

        /* enable interrupt */
        rt_hw_interrupt_enable(temp);
    
    else
    
        /* no waiting, return with timeout */
        if (time == 0)
        
            rt_hw_interrupt_enable(temp);

            return -RT_ETIMEOUT;
        
        else
        
            /* current context checking */
            RT_DEBUG_IN_THREAD_CONTEXT;

            /* semaphore is unavailable, push to suspend list */
            /* get current thread */
            thread = rt_thread_self();

            /* reset thread error number */
            thread->error = RT_EOK;

            RT_DEBUG_LOG(RT_DEBUG_IPC, ("sem take: suspend thread - %s\\n",
                                        thread->name));

            /* suspend thread */
            rt_ipc_list_suspend(&(sem->parent.suspend_thread),
                                thread,
                                sem->parent.parent.flag);

            /* has waiting time, start thread timer */
            if (time > 0)
            
                RT_DEBUG_LOG(RT_DEBUG_IPC, ("set thread:%s to timer list\\n",
                                            thread->name));

                /* reset the timeout of thread timer and start it */
                rt_timer_control(&(thread->thread_timer),
                                 RT_TIMER_CTRL_SET_TIME,
                                 &time);
                rt_timer_start(&(thread->thread_timer));
            

            /* enable interrupt */
            rt_hw_interrupt_enable(temp);

            /* do schedule */
            rt_schedule();//------- timeout内没有获取到信号里昂,线程被挂起,之后执行调度器执行其他线程

            if (thread->error != RT_EOK)
            
                return thread->error;
            
        
    

    RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(sem->parent.parent)));

    return RT_EOK;

  1. 同时,可能会将其他线程唤醒的操作:通过邮箱发送数据、释放信号量、释放互斥锁等,也会执行一次调度器,以确保此时若有更高优先级的线程因等待该资源而挂起时,可以挂起当前线程来执行高优先级线程。
rt_err_t rt_sem_release(rt_sem_t sem)

    register rt_base_t temp;
    register rt_bool_t need_schedule;

    /* parameter check */
    RT_ASSERT(sem != RT_NULL);
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);

    RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(sem->parent.parent)));

    need_schedule = RT_FALSE;

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

    RT_DEBUG_LOG(RT_DEBUG_IPC, ("thread %s releases sem:%s, which value is: %d\\n",
                                rt_thread_self()->name,
                                ((struct rt_object *)sem)->name,
                                sem->value));

    if (!rt_list_isempty(&sem->parent.suspend_thread))
    
        /* resume the suspended thread */
        rt_ipc_list_resume(&(sem->parent.suspend_thread));
        need_schedule = RT_TRUE;
    
    else
        sem->value ++; /* increase value */

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);

    /* resume a thread, re-schedule */
    if (need_schedule == RT_TRUE)
        rt_schedule();//--------信号量释放完以后判断需要调度,在这执行调度器

    return RT_EOK;


3. 总结

  1. RTT文档中:操作系统中最小的时间单位是时钟节拍 (OS Tick)的说法并无错误,因为我们可以执行的以时间为衡量的操作,都是以OS Tick为最小单位的。同时如果没有线程主动挂起的情况,仅以时间片为依照来调度,也是以OS Tick为最小单位的。
  2. 但如果存在线程主动挂起的情况,挂起时间无法确定,此时仅以OS Tick为单位调度,会造成CPU资源的极大浪费,同时降低系统的实时性。因此RTT在所有可以挂起线程的操作后,都添加了主动执行一次调度器的操作,直接执行当前已就绪的最高优先级的线程。
  3. OS Tick是系统调度的最小单位的理解更正为:系统调度会发生在OS Tick中断和当前线程主动挂起以及高优先级线程获取到资源时。

PS:RTT的内核封装的较好,代码易读性高,之前也经常翻内核代码发现并不复杂,也比较易懂。如果大家在开发过程中有对RTT内核机制、逻辑的疑惑,建议先自己动手翻下底层代码,或许可以更快的解决问题。

rt-thread内核之线程调度器

http://www.cnblogs.com/King-Gentleman/p/4278012.html一、前言RT-Thread中提供的线程调度器是基于全抢占式优先级的调度,在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢... 查看详情

rt-thread内核之线程调度

4.RT-Thread中的线程?/** *线程结构 */struct rt_thread {   /**Object对象*/  char    name[RT_NAME_MAX];       &nb 查看详情

rt-thread--内核基础(代码片段)

...小的资源占用情况是3KBROM,1.2KBRAM。 线程调度线程是RT-Thread操作系统中最小的调度单位,线程调度算法是基于优先级的全抢占式多线程调度算法,即在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不... 查看详情

java中的线程调度

1.抢占式调度:抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行... 查看详情

rt_thread线程管理(代码片段)

一、概述RT-Thread的线程可认为是一系列独立线程的集合。每个线程在自己的环境中运行。在任何时刻,只有一个线程得到运行,RT-Thread调度器决定运行哪个线程。每个RT-Thread线程都有自己的堆栈。RT-Thread的线程模块可以... 查看详情

rt_thread线程管理(代码片段)

一、概述RT-Thread的线程可认为是一系列独立线程的集合。每个线程在自己的环境中运行。在任何时刻,只有一个线程得到运行,RT-Thread调度器决定运行哪个线程。每个RT-Thread线程都有自己的堆栈。RT-Thread的线程模块可以... 查看详情

java多线程中的调度策略

两种线程的调度模式:抢占式调度:抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至... 查看详情

java多线程并发03—线程上下文,线程调度

...呢?操作系统的设计者巧妙地利用了时间片轮转的方式。线程上下文对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上... 查看详情

将控制从线程返回到调度程序(上下文切换)

】将控制从线程返回到调度程序(上下文切换)【英文标题】:Returncontrolfromthreadtoscheduler(contextswitching)【发布时间】:2015-09-1119:29:25【问题描述】:我正在为一个大学项目编写一个使用上下文切换的简单线程库。我在从线程执... 查看详情

java线程池机制的原理是啥?

  线程池属于对象池.所有对象池都具有一个非常重要的共性,就是为了最大程度复用对象.那么线程池的最  重要的特征也就是最大程度利用线程.  首先,创建线程本身需要额外(相对于执行任务而必须的资源)的开销.  作... 查看详情

rt-thread(rtos)之初试线程(代码片段)

...建线程2.1、定义线程栈、线程函数、线程控制用typedef给RT-thread中涉及到的数据类型取名线程栈、线程函数、线程控制的声明在main.h中线程栈、线程函数、线程控制的实现(main.c)实验中变量的字节对齐量条件编译与头文件引入实现... 查看详情

线程的前世今生

...调度单元。进程拥有独立的内存地址空间,那时候还没有线程的概念。进程有3个状态,分别是阻塞、就绪、运行。当进程所需资源未到位时是阻塞状态,当进程拥有资源但未被CPU调度是就绪状态,当进程用有资源并且被CPU调度... 查看详情

rt-threadrtos的rt-thread/ucos/freertos简单比较

参考技术A1、任务管理及调度:RT-Thread-32/256可选优先级抢占式调度,线程数不限,相同优先级线程时间片轮转调度;支持动态创建/销毁线程。uCOS-256优先级抢占式调度,不允许相同优先级任务存在2、同步/通信机制:RT-Thread-支持... 查看详情

java线程调度

线程调度是指操作系统为线程分配处理器使用权的过程,调度主要方式有两种,分别是协同式线程调度和抢占式线程调度协同式调度线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通知系统切换到另... 查看详情

rt-thread嵌入式操作系统相关的问题(代码片段)

面试中问到RT-thread嵌入式操作系统相关的问题RT-thread操作系统调度器的实现细节RT-Thread中提供的线程调度器是基于优先级的全抢占式调度:在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外... 查看详情

操作系统之进程管理(代码片段)

进程管理进程和线程 参考博客https://github.com/CyC2018/CS-Notes进程进程是资源分配的基本单位。进程控制块(ProcessControlBlock,PCB)描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对PCB的操作。下图显示了4个程... 查看详情

线程优先级

  在初识并发这篇博客提到过,Java的线程机制是抢占式的:这表示调度机制会周期性地中断线程,将上下文切换到另一个线程,从而为每隔线程都提供时间片,使得每个线程都会分配到数量合理的时间去驱动它的任务。  ... 查看详情

进程和线程区别

...本单位和独立调度、分派的基本单位都是进程。而在引入线程的操作系统中,则把线程作为调度和分派的基本单位。而把进程作为资源拥有的基本单位,使传统进程的两个属性分开,线程便能轻装运行,从而可显著地提高系统的... 查看详情