Zephyr 系统启动、线程与调度器

Zephyr 系统启动、线程与调度器

1. 从 Reset 到 z_cstart

各架构负责:

  1. 设置 CPU mode 和初始 stack;
  2. 必要时复制 .data、清理早期状态;
  3. 初始化最基本的 exception/vector 环境;
  4. 调用通用 C 入口 z_cstart()

具体 reset 入口位于 arch/<arch>/core/ 或 SoC startup。通用内核不假定某一种向量表或上下文格式。

2. z_cstart() 流程

kernel/init.c:z_cstart() 是通用启动主线:

1
2
3
4
5
6
7
8
9
10
11
12
13
z_cstart()
├─ gcov_static_init()
├─ z_sys_init_run_level(EARLY)
├─ arch_kernel_init()
├─ LOG_CORE_INIT()
├─ 初始化 dummy thread
├─ z_device_state_init()
├─ z_sys_init_run_level(PRE_KERNEL_1)
├─ arch_smp_init()
├─ z_sys_init_run_level(PRE_KERNEL_2)
├─ stack canary / early random
├─ prepare_multithreading()
└─ switch_to_main_thread()

PRE_KERNEL 阶段仍在 interrupt/early boot stack 上运行,不能使用会阻塞或依赖完整调度器的内核服务。

3. Main thread

prepare_multithreading()

  • 初始化 CPU 0;
  • 为每个 CPU 创建 idle thread;
  • 使用 z_setup_new_thread() 创建 z_main_thread
  • 入口为 bg_thread_main()
  • priority 为 CONFIG_MAIN_THREAD_PRIORITY
  • stack 为 CONFIG_MAIN_STACK_SIZE
  • 标记 main thread ready。

切换到 main thread 后执行:

1
2
3
4
5
6
7
8
9
10
bg_thread_main()
├─ POST_KERNEL init
├─ boot_banner()
├─ z_init_static()
├─ APPLICATION init
├─ z_init_static_threads()
├─ z_smp_init()
├─ SMP init level
├─ memory management finish
└─ main()

应用 main() 返回后,main thread 正常结束;其他线程和 idle 继续运行。

4. 初始化级别

include/zephyr/init.h 定义:

  1. EARLY
  2. PRE_KERNEL_1
  3. PRE_KERNEL_2
  4. POST_KERNEL
  5. APPLICATION
  6. SMP

SYS_INIT(fn, level, prio) 生成 struct init_entry 并放入:

1
.z_init_<level><priority>_<subpriority>_

链接脚本按 section name 排序。z_sys_init_run_level() 遍历起止符号并调用:

  • 系统 entry:entry->init_fn.sys()
  • device entry:设备初始化包装函数。

同一 level 的 priority 必须是 0–99 的十进制字面量或能直接展开为该字面量的宏,不能是算术表达式。

5. 线程对象

struct k_thread 的重要内容:

  • 架构保存上下文;
  • struct _thread_base 调度字段;
  • stack 信息;
  • entry 参数和返回状态;
  • timeout;
  • resource pool;
  • userspace permission/object metadata;
  • thread name、custom data、runtime stats;
  • SMP CPU mask/当前 CPU;
  • join、abort、suspend 状态。

线程 ID k_tid_t 实际是 struct k_thread *

6. 创建线程

静态线程

1
2
K_THREAD_DEFINE(name, stack_size, entry,
p1, p2, p3, prio, options, delay);

宏生成:

  • stack;
  • struct k_thread
  • _static_thread_data iterable entry。

启动时 z_init_static_threads() 先调用 z_setup_new_thread(),再按 delay 调度。

动态线程

1
2
3
k_thread_create(thread, stack, stack_size,
entry, p1, p2, p3,
prio, options, delay);

主要路径:

1
2
3
4
5
6
7
8
k_thread_create
→ z_impl_k_thread_create
→ setup_thread_stack
→ z_setup_new_thread
→ arch_new_thread
→ thread_schedule_new
→ z_ready_thread
→ z_reschedule

架构层负责把初始 stack 构造成首次 context switch 后能进入 thread entry 的格式。

7. 优先级模型

数值越小,逻辑优先级越高:

1
2
3
4
5
6
7
负数:cooperative priorities
-CONFIG_NUM_COOP_PRIORITIES ... -1

非负:preemptible priorities
0 ... CONFIG_NUM_PREEMPT_PRIORITIES - 1

最低:idle priority

宏:

1
2
3
4
5
K_PRIO_COOP(x)
K_PRIO_PREEMPT(x)
K_HIGHEST_THREAD_PRIO
K_LOWEST_APPLICATION_THREAD_PRIO
K_IDLE_PRIO

Cooperative thread 在主动阻塞、yield、终止或遇到 meta-IRQ thread 前不会被普通线程抢占。Preemptible thread 可被更高优先级 ready thread 抢占。

8. Ready queue 实现

调度策略由 Kconfig 选择:

  • simple/dumb queue:适合极少线程,线性查找;
  • multi-queue:按优先级维护队列;
  • scalable:红黑树,适合较多线程,插入/删除为 O(log n)。

抽象位于 kernel/priority_queues.c 和相关头文件。调度器不需要知道底层容器细节,只操作:

  • queue thread;
  • dequeue thread;
  • best/next thread。

选择依据不是“算法越复杂越好”,而是线程数量、RAM/ROM 和调度延迟需求。

9. 调度核心

kernel/sched.c 关键函数:

  • z_ready_thread():把线程加入 ready queue;
  • z_unready_thread():移除 ready 状态;
  • next_up():选择下一线程;
  • update_cache():更新 cached next thread;
  • z_reschedule():判断是否需要切换;
  • z_swap():完成通用调度状态转换并调用 arch switch;
  • z_pend_curr():当前线程挂入 wait queue;
  • z_unpend_first_thread():唤醒等待者;
  • z_impl_k_yield():同优先级让出。

典型抢占路径:

1
2
3
4
5
6
7
8
ISR/线程使高优先级线程 ready
→ ready_thread()
→ update_cache()
→ z_reschedule()
→ need_swap()
→ z_swap()
→ next_up()
→ arch_switch()

10. Wait queue 与阻塞

信号量、mutex、queue 等对象包含 wait queue。阻塞流程:

1
2
3
4
5
6
检查资源不可用
→ 持有 object/scheduler spinlock
→ add_to_waitq_locked()
→ 设置 timeout
→ 当前线程退出 ready 状态
→ z_swap()

唤醒流程:

1
2
3
4
5
资源变为可用
→ 选择 wait queue 最高优先级线程
→ 取消 timeout
→ z_ready_thread()
→ 必要时抢占当前线程

11. Scheduler lock、IRQ lock 与 Spinlock

  • k_sched_lock():阻止当前 CPU 上普通抢占,不关闭中断;
  • irq_lock():架构级屏蔽/锁定中断,临界区应极短;
  • k_spin_lock():SMP 互斥并保存 IRQ 状态;
  • mutex:线程级可睡眠锁,支持 priority inheritance。

不能用 scheduler lock 替代 SMP 数据保护;另一个 CPU 仍可能并发访问。

12. 时间片

CONFIG_TIMESLICING 启用同优先级 preemptible threads 的轮转:

  • kernel/timeslicing.c 管理 slice;
  • CONFIG_TIMESLICE_SIZE 设置 tick 数;
  • CONFIG_TIMESLICE_PRIORITY 决定应用范围;
  • thread 可配置自定义 time slice callback。

更高优先级唤醒仍立即抢占,时间片只解决同优先级公平性。

13. Timeout

kernel/timeout.c 管理全局 timeout queue。典型 timeout 来源:

  • k_sleep()
  • semaphore/mutex/poll 等限时等待;
  • timer;
  • delayed work;
  • thread delayed start。

timeout 使用 tick 绝对/相对截止时间,并通过 delta queue 或配置的 timeout 容器管理。系统时钟 driver 到期后调用 sys_clock_announce(),推进 timeout 并唤醒对象。

Tickless 模式会让 timer driver 直接编程到最近 deadline,减少空闲 tick 中断。

14. 中断与调度

ISR 中可以调用标记为 ISR-safe 的 API,使线程 ready。是否在 ISR 退出时切换取决于:

  • 是否有更高优先级 ready thread;
  • scheduler lock;
  • 当前线程 cooperative/preemptible;
  • 架构的 interrupt exit/swap 实现。

ISR 不应调用可能睡眠的 API。k_is_in_isr() 可用于防御检查,但正确做法是按 API 文档设计上下文。

15. SMP

CONFIG_SMP 下:

  • _kernel.cpus[] 保存每 CPU 当前线程;
  • shared ready queue 受 scheduler spinlock 保护;
  • thread 可设置 CPU mask;
  • IPI 通知其他 CPU reschedule;
  • idle thread 每 CPU 一个;
  • next_up() 需避免同一 thread 被多个 CPU 同时选中;
  • meta-IRQ threads 提供接近中断底半部的高优先级语义。

SMP 中即使本 CPU 不发生 switch,也可能需要发送 pending IPI。

16. 实时性注意事项

影响 worst-case latency 的因素:

  • 长时间 IRQ lock/spinlock;
  • cooperative 高优先级线程不阻塞;
  • 大量同优先级 ready threads;
  • logging immediate mode;
  • flash erase/write;
  • cache miss 和外部 memory;
  • driver ISR 工作量;
  • userspace syscall/MPU 切换;
  • SMP lock contention。

实时性评估应测量最大值和尾延迟,而不只看平均 context-switch 时间。

文章互动

阅读 --

留言

0 条留言

正在加载留言…