回答

收藏

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

RT-Thread RT-Thread 4131 人阅读 | 0 人回复 | 2021-04-25

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

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

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

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

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

  10.     /* 栈指针与入口指针 */
  11.     void       *sp;                      /* 栈指针 */
  12.     void       *entry;                   /* 入口函数指针 */
  13.     void       *parameter;              /* 参数 */
  14.     void       *stack_addr;             /* 栈地址指针 */
  15.     rt_uint32_t stack_size;            /* 栈大小 */

  16.     /* 错误代码 */
  17.     rt_err_t    error;                  /* 线程错误代码 */
  18.     rt_uint8_t  stat;                   /* 线程状态 */

  19.     /* 优先级 */
  20.     rt_uint8_t  current_priority;    /* 当前优先级 */
  21.     rt_uint8_t  init_priority;        /* 初始优先级 */
  22.     rt_uint32_t number_mask;

  23.     ......

  24.     rt_ubase_t  init_tick;               /* 线程初始化计数值 */
  25.     rt_ubase_t  remaining_tick;         /* 线程剩余计数值 */

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

  27.     void (*cleanup)(struct rt_thread *tid);  /* 线程退出清除函数 */
  28.     rt_uint32_t user_data;                      /* 用户数据 */
  29. };
复制代码

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


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

任务计划函数rt_schedule()具体实现
  1. // schedule.c
  2. /**
  3. * This function will perform one schedule. It will select one thread
  4. * with the highest priority level, then switch to it.
  5. */
  6. void rt_schedule(void)
  7. {
  8.     rt_base_t level;
  9.     struct rt_thread *to_thread;
  10.     struct rt_thread *from_thread;

  11.     /* disable interrupt */
  12.     level = rt_hw_interrupt_disable();

  13.     /* check the scheduler is enabled or not */
  14.     if (rt_scheduler_lock_nest == 0)
  15.     {
  16.         rt_ubase_t highest_ready_priority;

  17.         if (rt_thread_ready_priority_group != 0)
  18.         {
  19.             /* need_insert_from_thread: need to insert from_thread to ready queue */
  20.             int need_insert_from_thread = 0;

  21.             to_thread = _get_highest_priority_thread(&highest_ready_priority);

  22.             if ((rt_current_thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING)
  23.             {
  24.                 if (rt_current_thread->current_priority < highest_ready_priority)
  25.                 {
  26.                     to_thread = rt_current_thread;
  27.                 }
  28.                 else
  29.                 {
  30.                     need_insert_from_thread = 1;
  31.                 }
  32.             }

  33.             /* higher priority thread is not equal to the current one, switch to run the            higher one */
  34.             if (to_thread != rt_current_thread)
  35.             {
  36.                 /* if the destination thread is not the same as current thread */
  37.                 rt_current_priority = (rt_uint8_t)highest_ready_priority;
  38.                 from_thread         = rt_current_thread;
  39.                 rt_current_thread   = to_thread;

  40.                 RT_OBJECT_HOOK_CALL(rt_scheduler_hook, (from_thread, to_thread));

  41.                 if (need_insert_from_thread)
  42.                 {
  43.                     rt_schedule_insert_thread(from_thread);
  44.                 }

  45.                 rt_schedule_remove_thread(to_thread);
  46.                 to_thread->stat = RT_THREAD_RUNNING | (to_thread->stat & ~RT_THREAD_STAT_MASK);

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

  55. #ifdef RT_USING_OVERFLOW_CHECK
  56.                 _rt_scheduler_stack_check(to_thread);
  57. #endif

  58.                 if (rt_interrupt_nest == 0)
  59.                 {
  60.                     extern void rt_thread_handle_sig(rt_bool_t clean_state);

  61.                     rt_hw_context_switch((rt_ubase_t)&from_thread->sp,
  62.                             (rt_ubase_t)&to_thread->sp);
  63. #ifdef RT_USING_SIGNALS
  64.                     if (rt_current_thread->stat & RT_THREAD_STAT_SIGNAL_PENDING)
  65.                     {
  66.                         extern void rt_thread_handle_sig(rt_bool_t clean_state);

  67.                         rt_current_thread->stat &= ~RT_THREAD_STAT_SIGNAL_PENDING;

  68.                         rt_hw_interrupt_enable(level);

  69.                         /* check signal status */
  70.                         rt_thread_handle_sig(RT_TRUE);
  71.                     }
  72.                     else
  73.                     {
  74.                         rt_hw_interrupt_enable(level);
  75.                     }
  76. #else
  77.                     /* enable interrupt */
  78.                     rt_hw_interrupt_enable(level);
  79. #endif
  80.                     goto __exit;
  81.                 }
  82.                 else
  83.                 {
  84.                     RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interruptn"));

  85.                     rt_hw_context_switch_interrupt((rt_ubase_t)&from_thread->sp,
  86.                             (rt_ubase_t)&to_thread->sp);
  87.                 }
  88.             }
  89.             else
  90.             {
  91.                 rt_schedule_remove_thread(rt_current_thread);
  92.                 rt_current_thread->stat = RT_THREAD_RUNNING | (rt_current_thread->stat & ~RT_THREAD_STAT_MASK);
  93.             }
  94.         }
  95.     }

  96.     /* enable interrupt */
  97.     rt_hw_interrupt_enable(level);

  98. __exit:
  99.     return;
  100. }
复制代码

从以上源码可以看到,任务的优先级计算、状态切换等算法都是在这边完成的

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

  15. // clock.c
  16. void rt_tick_increase(void)
  17. {
  18.     struct rt_thread *thread;

  19.     /* increase the global tick */
  20. #ifdef RT_USING_SMP
  21.     rt_cpu_self()->tick ++;
  22. #else
  23.     ++ rt_tick;
  24. #endif

  25.     /* check time slice */
  26.     thread = rt_thread_self();

  27.     -- thread->remaining_tick;
  28.     if (thread->remaining_tick == 0)
  29.     {
  30.         /* change to initialized tick */
  31.         thread->remaining_tick = thread->init_tick;

  32.         thread->stat |= RT_THREAD_STAT_YIELD;

  33.         /* yield */
  34.         rt_thread_yield();
  35.     }

  36.     /* check timer */
  37.     rt_timer_check();
  38. }
复制代码

通过查看源码可以看到,/ 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)
  1. ; r0 --> switch from thread stack
  2. ; r1 --> switch to thread stack
  3. ; psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
  4. PendSV_Handler   PROC
  5.     EXPORT PendSV_Handler

  6.     ; disable interrupt to protect context switch
  7.     MRS     r2, PRIMASK
  8.     CPSID   I

  9.     ; get rt_thread_switch_interrupt_flag
  10.     LDR     r0, =rt_thread_switch_interrupt_flag
  11.     LDR     r1, [r0]
  12.     CBZ     r1, pendsv_exit         ; pendsv already handled

  13.     ; clear rt_thread_switch_interrupt_flag to 0
  14.     MOV     r1, #0x00
  15.     STR     r1, [r0]

  16.     LDR     r0, =rt_interrupt_from_thread
  17.     LDR     r1, [r0]
  18.     CBZ     r1, switch_to_thread    ; skip register save at the first time

  19.     MRS     r1, psp                 ; get from thread stack pointer
  20.     STMFD   r1!, {r4 - r11}         ; push r4 - r11 register
  21.     LDR     r0, [r0]
  22.     STR     r1, [r0]                ; update from thread stack pointer

  23. switch_to_thread
  24.     LDR     r1, =rt_interrupt_to_thread
  25.     LDR     r1, [r1]
  26.     LDR     r1, [r1]                ; load thread stack pointer

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

  29. pendsv_exit
  30.     ; restore interrupt
  31.     MSR     PRIMASK, r2

  32.     ORR     lr, lr, #0x04
  33.     BX      lr
  34.     ENDP
复制代码

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


分享到:
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条