caroline11 发表于 2021-4-25 08:37:22

RT-Thread系统任务并发执行和调度浅析

RT-Thread 内核介绍
内核是操作系统最基础也是最重要的部分。下图为 RT-Thread 内核架构图,内核处于硬件层之上,内核部分包括内核库、实时内核实现。

RT-Thread 内核及底层结构
内核库是为了保证内核能够独立运行的一套小型的类似 C 库的函数实现子集。这部分根据编译器的不同自带 C 库的情况也会有些不同,当使用 GNU GCC 编译器时,会携带更多的标准 C 库实现。

线程调度
线程是 RT-Thread 操作系统中最小的调度单位,线程调度算法是基于优先级的全抢占式多线程调度算法,即在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的,包括线程调度器自身。

线程控制块
在 RT-Thread 中,线程控制块由结构体 struct rt_thread 表示,线程控制块是操作系统用于管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包含线程与线程之间连接用的链表结构,线程等待事件集合等,详细定义如下:
/* 线程控制块 */
struct rt_thread
{
    /* rt 对象 */
    char      name;   /* 线程名称 */
    rt_uint8_ttype;                   /* 对象类型 */
    rt_uint8_tflags;                  /* 标志位 */

    rt_list_t   list;                   /* 对象列表 */
    rt_list_t   tlist;                  /* 线程列表 */

    /* 栈指针与入口指针 */
    void       *sp;                      /* 栈指针 */
    void       *entry;                   /* 入口函数指针 */
    void       *parameter;            /* 参数 */
    void       *stack_addr;             /* 栈地址指针 */
    rt_uint32_t stack_size;            /* 栈大小 */

    /* 错误代码 */
    rt_err_t    error;                  /* 线程错误代码 */
    rt_uint8_tstat;                   /* 线程状态 */

    /* 优先级 */
    rt_uint8_tcurrent_priority;    /* 当前优先级 */
    rt_uint8_tinit_priority;      /* 初始优先级 */
    rt_uint32_t number_mask;

    ......

    rt_ubase_tinit_tick;               /* 线程初始化计数值 */
    rt_ubase_tremaining_tick;         /* 线程剩余计数值 */

    struct rt_timer thread_timer;      /* 内置线程定时器 */

    void (*cleanup)(struct rt_thread *tid);/* 线程退出清除函数 */
    rt_uint32_t user_data;                      /* 用户数据 */
};
其中 init_priority 是线程创建时指定的线程优先级,在线程运行过程当中是不会被改变的(除非用户执行线程控制函数进行手动调整线程优先级)。cleanup 会在线程退出时,被空闲线程回调一次以执行用户设置的清理现场等工作。最后的一个成员 user_data 可由用户挂接一些数据信息到线程控制块中,以提供类似线程私有数据的实现。


时间片轮转和抢占
当我们创建一个或者多个任务之后,系统具体是怎么进行时间片计算和任务抢占的呢?主要涉及到两个中断函数:SysTick_Handler()和PendSV_Handler(),以及任务计划函数rt_schedule()*核心**

任务计划函数rt_schedule()具体实现
// schedule.c
/**
* This function will perform one schedule. It will select one thread
* with the highest priority level, then switch to it.
*/
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 = _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
                {
                  need_insert_from_thread = 1;
                }
            }

            /* higher priority thread is not equal to the current one, switch to run the            higher one */
            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

                if (rt_interrupt_nest == 0)
                {
                  extern void rt_thread_handle_sig(rt_bool_t clean_state);

                  rt_hw_context_switch((rt_ubase_t)&from_thread->sp,
                            (rt_ubase_t)&to_thread->sp);
#ifdef RT_USING_***NALS
                  if (rt_current_thread->stat & RT_THREAD_STAT_***NAL_PENDING)
                  {
                        extern void rt_thread_handle_sig(rt_bool_t clean_state);

                        rt_current_thread->stat &= ~RT_THREAD_STAT_***NAL_PENDING;

                        rt_hw_interrupt_enable(level);

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

                  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;
}
从以上源码可以看到,任务的优先级计算、状态切换等算法都是在这边完成的

而SysTick_Handler()和PendSV_Handler(),前者负责任务的运行状态检测,当发生抢占或者时间片轮转时,触发后者进行任务的上下文转换。
// board.c
/**
* This is the timer interrupt service routine.
*
*/
void SysTick_Handler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();
    HAL_IncTick();
    rt_tick_increase();
    /* leave interrupt */
    rt_interrupt_leave();
}

// clock.c
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();
}
通过查看源码可以看到,/ check time slice /部分是统计当前任务运行剩余的时间片,(thread->remaining_tick值),一旦当前任务时间片用完就调用rt_schedule()(在rt_thread_yield()会调用)进行任务状态切换,达到时间片轮转的功能。

以上看出来,系统心跳内部只检测了当前任务的时间片,并没有对其他任务进行检测,那抢占是如何触发的呢?

回到struct rthread的结构体我们可以看到,每一个任务都内嵌有一个定时器模块(struct rt_timer thread_timer; ),当任务调用rt_thread_mdelay()释放CPU资源时,定时器模块负责统计任务的挂起时间,每次插入定时器链表的时候,系统都会依据定时的超时时间进行有序排列(跳表 (Skip List) 算法),通过rt_timer_check()进行超时判断,就调用rt_schedule()进行任务状态切换,达到高优先级抢占的目的。

在PendSV_Handler()中断内任务才会进行真正的上下文切换,不同编译环境下的汇编源码有差别,但核心都是通过将任务运行的堆栈空间填充到CPU寄存器(R1-R11)
; r0 --> switch from thread stack
; r1 --> switch to thread stack
; psr, pc, lr, r12, r3, r2, r1, r0 are pushed into stack
PendSV_Handler   PROC
    EXPORT PendSV_Handler

    ; disable interrupt to protect context switch
    MRS   r2, PRIMASK
    CPSID   I

    ; get rt_thread_switch_interrupt_flag
    LDR   r0, =rt_thread_switch_interrupt_flag
    LDR   r1,
    CBZ   r1, pendsv_exit         ; pendsv already handled

    ; clear rt_thread_switch_interrupt_flag to 0
    MOV   r1, #0x00
    STR   r1,

    LDR   r0, =rt_interrupt_from_thread
    LDR   r1,
    CBZ   r1, switch_to_thread    ; skip register save at the first time

    MRS   r1, psp               ; get from thread stack pointer
    STMFD   r1!, {r4 - r11}         ; push r4 - r11 register
    LDR   r0,
    STR   r1,                 ; update from thread stack pointer

switch_to_thread
    LDR   r1, =rt_interrupt_to_thread
    LDR   r1,
    LDR   r1,                 ; load thread stack pointer

    LDMFD   r1!, {r4 - r11}         ; pop r4 - r11 register
    MSR   psp, r1               ; update stack pointer

pendsv_exit
    ; restore interrupt
    MSR   PRIMASK, r2

    ORR   lr, lr, #0x04
    BX      lr
    ENDP
总结
RT-Thread任务调度的时候,不是在系统的心跳中断内对每个任务进行优先级,时间片判断,而是单检测当前运行任务的时间片,并配合定时器模块,进行抢占。提高了运行效率。


页: [1]
查看完整版本: RT-Thread系统任务并发执行和调度浅析