调度器运行到main_thread_entry的完整执行链路(RA6M3 BSP)

核心结论:调度器启动的本质是「第一次上下文切换」,永远切换到就绪队列中优先级最高的线程main_thread_entry 不是调度器启动的默认入口,而是 main 线程被调度选中时的C语言执行起点,仅当更高优先级线程(如timer线程)阻塞后,main 线程才会被选中执行。

一、前置条件:调度器启动前的线程就绪化

调度器运行(rt_system_scheduler_start)前,系统已完成关键线程的创建和就绪化,为调度提供“可执行线程池”:

1. main线程的创建与就绪

rt_application_init()src/components.c)会创建main线程,核心动作:

void rt_application_init(void)
{
    rt_thread_t tid;
    // 以main_thread_entry为入口创建main线程(优先级10)
    tid = rt_thread_create("main", main_thread_entry, RT_NULL,
                           RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(tid != RT_NULL);
    // 启动线程:将main线程加入就绪队列
    rt_thread_startup(tid);
}
  • rt_thread_create 内部调用 rt_hw_stack_init,把main_thread_entry作为线程入口写入栈帧(PC寄存器值);
  • rt_thread_startupmain线程状态设为RT_THREAD_READY,并挂载到rt_thread_priority_table[10](优先级10的就绪链表),同时置位rt_thread_ready_priority_group的bit10。

2. timer线程的创建与就绪

软件定时器模块初始化时,会创建timer线程(优先级RT_TIMER_THREAD_PRIO=4),并通过rt_thread_startup()加入就绪队列:

  • 挂载到rt_thread_priority_table[4],置位rt_thread_ready_priority_group的bit4;
  • 优先级4(数值更小)高于main线程的10,成为就绪队列中“最高优先级线程”。

二、调度器运行:第一次上下文切换(核心步骤)

调度器启动的核心函数是rt_system_scheduler_startsrc/scheduler_up.c),本质是“选最高优先级线程 + 切过去执行”,代码逻辑拆解:

void rt_system_scheduler_start(void)
{
    struct rt_thread *to_thread;
    rt_ubase_t highest_ready_priority;

    // 步骤1:从就绪队列选最高优先级线程(RA6M3场景下是timer线程)
    to_thread = _scheduler_get_highest_priority_thread(&highest_ready_priority);
    // 步骤2:标记该线程为当前CPU的运行线程
    rt_cpu_self()->current_thread = to_thread;
    // 步骤3:清除临界切换标志,确保调度安全
    CLR_CRITICAL_SWITCH_FLAG();
    // 步骤4:将线程从就绪队列移除(避免重复调度),状态改为RUNNING
    rt_sched_remove_thread(to_thread);
    RT_SCHED_CTX(to_thread).stat = RT_THREAD_RUNNING;
    // 步骤5:触发上下文切换,跳转到to_thread的执行入口
    rt_hw_context_switch_to((rt_uintptr_t)&to_thread->sp);
    // 切换后永不返回
}

关键子步骤:_scheduler_get_highest_priority_thread 选线程逻辑

static struct rt_thread* _scheduler_get_highest_priority_thread(rt_ubase_t *highest_prio)
{
    rt_ubase_t highest_ready_priority;
    // 从就绪优先级位图中找最小编置位bit(RA6M3场景下是bit4)
    highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
    // 从对应优先级链表中取出第一个线程(timer线程)
    struct rt_thread *highest_priority_thread = RT_THREAD_LIST_NODE_ENTRY(
        rt_thread_priority_table[highest_ready_priority].next
    );
    *highest_prio = highest_ready_priority;
    return highest_priority_thread;
}

三、上下文切换到线程入口(从汇编到C的执行)

rt_hw_context_switch_to 是汇编实现的核心切换函数(libcpu/arm/cortex-m4/context_gcc.S),负责触发PendSV异常完成切换:

1. 触发PendSV异常

rt_hw_context_switch_to:
    LDR r1, =rt_interrupt_to_thread  // 保存目标线程sp到全局变量
    STR r0, [r1]
    LDR r0, =NVIC_INT_CTRL           // 触发PendSV异常(最低优先级,确保切换安全)
    LDR r1, =NVIC_PENDSVSET
    STR r1, [r0]
    BX lr

2. PendSV异常处理(第一次切换)

PendSV_Handler:
    MRS r0, PRIMASK                 // 关闭总中断,保护切换过程
    CPSID I
    ...
    LDR r0, =rt_interrupt_from_thread
    LDR r1, [r0]
    CBZ r1, switch_to_thread        // 第一次切换:from_thread=0,跳过旧线程现场保存
    ...
switch_to_thread:
    LDR r0, =rt_interrupt_to_thread
    LDR r1, [r0]
    LDR sp, [r1]                    // 加载目标线程的栈指针sp
    POP {r4-r11}                    // 恢复通用寄存器
    POP {r0-r3}                     // 恢复参数寄存器
    POP {r12}
    ADD sp, sp, #4                  // 跳过LR(初始栈帧中LR为_thread_exit)
    POP {lr}                        // 恢复LR=线程入口(如timer线程入口)
    ADD sp, sp, #4                  // 跳过xPSR
    CPSIE I                         // 开启总中断
    BX lr                           // 跳转到线程入口执行
  • RA6M3场景下,第一次切换的to_thread是timer线程,因此BX lr会跳转到timer线程入口;
  • 此时main_thread_entry还未执行,仅作为main线程栈帧中保存的“待执行入口”。

四、main_thread_entry的执行时机(timer线程阻塞后)

timer线程执行时的核心逻辑是“检查定时器事件 → 无事件则阻塞”,触发二次调度:

1. timer线程阻塞流程

static void rt_timer_thread_entry(void *parameter)
{
    while (1)
    {
        rt_timer_check();  // 检查是否有到期定时器
        if (无到期定时器)
        {
            // 阻塞timer线程(状态改为SUSPENDED),让出CPU
            rt_sem_take(&rt_timer_sem, RT_WAITING_FOREVER);
            // 阻塞时自动调用rt_schedule()触发重新调度
        }
    }
}

2. 重新调度选中main线程

rt_schedule() 会重复“选最高优先级线程”逻辑:

  • 此时timer线程已阻塞,就绪队列中仅剩main线程(优先级10);
  • _scheduler_get_highest_priority_thread 选中main线程作为to_thread
  • 再次触发PendSV异常,执行上下文切换。

3. 切换到main_thread_entry

本次切换的to_thread是main线程,PendSV异常处理中:

  • 从main线程的栈帧中恢复寄存器,BX lr跳转到栈帧中保存的main_thread_entry
  • 最终执行main_thread_entry的C逻辑:
    static void main_thread_entry(void *parameter)
    {
        RT_UNUSED(parameter);
    #ifdef RT_USING_COMPONENTS_INIT
        rt_components_init();  // 初始化RT-Thread组件
    #endif
        main();  // 跳转到用户编写的main函数
    }
    

五、核心链路总结(表格版)

阶段 核心动作 执行线程 关键函数/指令
调度器启动前 创建main/timer线程,加入就绪队列 初始化上下文(无调度) rt_application_init()/rt_thread_startup()
调度器运行 选最高优先级(4)的timer线程,触发PendSV -(切换过程无执行流) rt_system_scheduler_start()/rt_hw_context_switch_to()
第一次切换后 执行timer线程逻辑,无定时器则阻塞 timer线程 rt_timer_thread_entry()/rt_sem_take()
timer线程阻塞 触发重新调度,选优先级10的main线程 -(切换过程无执行流) rt_schedule()/_scheduler_get_highest_priority_thread()
第二次切换后 从栈帧恢复寄存器,跳转到main_thread_entry main线程 PendSV_Handler/BX lrmain_thread_entry()

六、关键概念对应关系

通俗表述 实际技术含义
“调度器运行” rt_system_scheduler_start() 完成“选最高优先级线程 + 第一次上下文切换”,系统进入多线程调度状态
“→ main_thread_entry” 当main线程成为调度器选中的to_thread时,通过PendSV异常恢复栈帧,CPU跳转到main_thread_entry执行
“优先级决定执行顺序” _scheduler_get_highest_priority_thread 永远选就绪队列中数值最小的优先级线程,因此timer线程先于main线程执行

验证方式(RA6M3 BSP)

  1. rt_timer_thread_entrymain_thread_entry中分别添加日志:
    // timer线程入口处
    LOG_D("enter timer thread (priority: %d)", RT_TIMER_THREAD_PRIO);
    // main_thread_entry开头
    LOG_D("enter main_thread_entry (priority: %d)", RT_MAIN_THREAD_PRIORITY);
    
  2. 编译下载后,串口日志会先打印enter timer thread,再打印enter main_thread_entry
  3. 若修改RT_MAIN_THREAD_PRIORITY为3(低于4),则调度器启动后会直接切换到main_thread_entry
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐