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,观察日志输出。
验证方法与判定标准
预期调度顺序:
- 高优先级线程(prio 1)首先运行,完成全部 5 次迭代后退出;
- 中优先级线程(prio 4)随后运行,完成全部 5 次迭代后退出;
- 低优先级线程(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(),即可看到两个线程交替输出。
上图展示了三个不同优先级线程的执行顺序:高优先级线程完全执行完毕后,中优先级线程才开始;中优先级线程结束后,低优先级线程才获得 CPU。每个线程内部通过 k_yield() 主动让出处理器,但因为没有同优先级竞争线程,所以不会发生轮转。