Zephyr 系统启动、线程与调度器
1. 从 Reset 到 z_cstart
各架构负责:
- 设置 CPU mode 和初始 stack;
- 必要时复制
.data、清理早期状态;
- 初始化最基本的 exception/vector 环境;
- 调用通用 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 定义:
EARLY
PRE_KERNEL_1
PRE_KERNEL_2
POST_KERNEL
APPLICATION
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 时间。