2. 使用内核调度器创建多线程

任务目标

在 Zephyr RTOS 中创建三个线程,分别设置不同优先级,通过日志输出观察内核调度器如何按优先级抢占执行,以及同优先级线程的时间片轮转行为。

环境依赖

本案例基于 nRF52840DK 开发板,使用 Zephyr 3.4 及以上版本。需在 prj.conf 中启用以下配置:

CONFIG_THREAD_MONITOR=y
CONFIG_SCHED_THREAD_USAGE=y

完整实现

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

/* 线程栈与线程控制块 */
#define STACK_SIZE 512
#define THREAD_PRIO_LOW   7
#define THREAD_PRIO_MID   4
#define THREAD_PRIO_HIGH  1

K_THREAD_STACK_DEFINE(stack_low, STACK_SIZE);
K_THREAD_STACK_DEFINE(stack_mid, STACK_SIZE);
K_THREAD_STACK_DEFINE(stack_high, STACK_SIZE);

struct k_thread thread_low;
struct k_thread thread_mid;
struct k_thread thread_high;

/* 共享计数器,用于观察抢占 */
static volatile uint32_t counter = 0;

/* 线程入口函数 */
void thread_entry(void *p1, void *p2, void *p3)
{
    int id = (int)p1;
    int iterations = (int)p2;

    for (int i = 0; i < iterations; i++) {
        counter++;
        printk("Thread %d (prio %d) running, counter=%u\n",
               id, k_thread_priority_get(k_current_get()), counter);
        /* 主动让出CPU,观察同优先级调度 */
        k_yield();
    }
    printk("Thread %d finished\n", id);
}

void main(void)
{
    printk("Main thread (prio %d) starting...\n",
           k_thread_priority_get(k_current_get()));

    /* 创建三个线程:低、中、高优先级 */
    k_thread_create(&thread_low, stack_low, STACK_SIZE,
                    thread_entry, (void *)1, (void *)5, NULL,
                    THREAD_PRIO_LOW, 0, K_NO_WAIT);
    k_thread_create(&thread_mid, stack_mid, STACK_SIZE,
                    thread_entry, (void *)2, (void *)5, NULL,
                    THREAD_PRIO_MID, 0, K_NO_WAIT);
    k_thread_create(&thread_high, stack_high, STACK_SIZE,
                    thread_entry, (void *)3, (void *)5, NULL,
                    THREAD_PRIO_HIGH, 0, K_NO_WAIT);

    /* 主线程退出调度 */
    printk("Main thread done, counter=%u\n", counter);
}

构建与运行

west build -b nrf52840dk_nrf52840 -p always
west flash

使用串口终端(如 minicom 或 PuTTY)连接开发板,波特率 115200,观察日志输出。

验证方法与判定标准

预期调度顺序:

  1. 高优先级线程(prio 1)首先运行,完成全部 5 次迭代后退出;
  2. 中优先级线程(prio 4)随后运行,完成全部 5 次迭代后退出;
  3. 低优先级线程(prio 7)最后运行,完成全部 5 次迭代后退出。

判定通过条件:

  • 串口输出中,线程 3 的所有日志先于线程 2 出现,线程 2 的所有日志先于线程 1 出现;
  • 每个线程内部,由于 k_yield() 主动让出 CPU,同优先级线程(本例中仅一个)不会出现交错,但若将某两个线程设为相同优先级,可观察到两者交替运行;
  • 最终 counter 值应为 15(3 个线程 × 5 次迭代),且主线程打印的 counter 值可能小于 15,因为主线程在子线程完成前已退出。

调度行为说明

Zephyr 默认使用基于优先级的抢占式调度器。高优先级线程就绪时会立即抢占低优先级线程。当多个同优先级线程就绪时,采用时间片轮转(默认 2.5ms)。本案例中三个线程优先级各不相同,因此严格按优先级从高到低顺序执行。若需观察时间片轮转,可将 THREAD_PRIO_MID 改为与 THREAD_PRIO_LOW 相同值(如均为 7),并移除 k_yield(),即可看到两个线程交替输出。

线程调度时序图(优先级抢占) 时间 → 线程3 (prio 1) — 5次迭代 线程2 (prio 4) — 5次迭代 线程1 (prio 7) — 5次迭代 主线程 抢占 抢占 高优先级 中优先级 低优先级

上图展示了三个不同优先级线程的执行顺序:高优先级线程完全执行完毕后,中优先级线程才开始;中优先级线程结束后,低优先级线程才获得 CPU。每个线程内部通过 k_yield() 主动让出处理器,但因为没有同优先级竞争线程,所以不会发生轮转。