# Research note: Zephyr RTOS kernel **Kind:** Read-only source analysis, 2026-10-07, on a sparse shallow clone of zephyrproject-rtos/zephyr at commit `d175d3bfb6c2` (v4.5.0-rc1; kernel/, include/zephyr/, arch/arm/core/cortex_m/, arch/riscv/core/, doc/kernel/, doc/services/logging/, doc/services/tracing/, lib/os/, subsys/logging/, subsys/tracing/). Paths relative to the repository root. Line numbers drift; paths and symbols are the durable reference. Synthesized in `R-001`. --- ## Q1 Scheduler - Three ready-queue backends chosen by `choice SCHED_ALGORITHM` (kernel/Kconfig:271-320): SCHED_SIMPLE is a priority-sorted dlist, O(N) insert (kernel/include/priority_q.h:114-126); SCHED_SCALABLE is an rbtree with a monotonically increasing `order_key` for FIFO within priority, renumbered on wrap (priority_q.h:207-237); SCHED_MULTIQ is per-priority dlists plus a bitmap, UP caches the best index (priority_q.h:291-371). MULTIQ `depends on !SCHED_DEADLINE` (Kconfig:307). Dispatch via `_priq_run_*` macros (priority_q.h:14-46); wait queues share SIMPLE and SCALABLE (priority_q.h:49-58). - Priorities: cooperative are negative (`-NUM_COOP..-1`), preemptible `0..N-1`, idle lowest; lower number is higher priority (Kconfig:26-64; ksched.h:185-193). Preemptibility is a merged `preempt` field versus `_PREEMPT_THRESHOLD` (kthread.h:66-70, kernel_structs.h:86-89). - `z_sched_prio_cmp` compares `prio`, then under SCHED_DEADLINE the 32-bit modular difference of `prio_deadline` (priority_q.h:81-112): EDF only orders threads within one priority. Deadlines are `k_cycle_get_32` relative, clamped to INT32_MAX/2, and updates must dequeue and requeue to keep rbtree invariants (kernel/deadline.c:259-300). - Time slicing: one `_timeout` per CPU (kernel/timeslicing.c:14-19); applies only to preemptible threads at or below `TIMESLICE_PRIORITY` (timeslicing.c:44-64; Kconfig:600-608); per-thread slices plus an expiry callback (timeslicing.c:137-148); rotation via `move_current_to_end_of_prio_q` (timeslicing.c:198). - `k_sched_lock` decrements a per-thread `sched_locked` counter under the scheduler lock and asserts not in ISR (kernel/scheduler.c:282-306); meta-IRQs bypass it (Kconfig:110-124). - `struct z_kernel {cpus[], ready_q, threads, pending_ipi}` and `struct _cpu {nested, irq_stack, current, idle_thread, metairq_preempted, swap_ok}` (include/zephyr/kernel_structs.h:147-248). UP keeps `ready_q.cache` as the next thread (kernel_structs.h:130-134; sched.c:167-182); `_current` on SMP reads `_current_cpu->current` with IRQs masked (kernel_structs.h:262-306; kernel/smp/smp.c:276-287). - SMP: a single global run queue under `_sched_spinlock`; per-CPU queues only with `SCHED_CPU_MASK_PIN_ONLY` (kernel/include/run_q.h:17-49). `_current` is never in the queue on SMP and is re-added only on the switch path (run_q.h:77-98; kswap.h:420-431). `next_up` does the SMP choice (sched.c:57-158). IPIs: `flag_ipi` and `ipi_mask_create` with IPI_OPTIMIZE and idle-CPU reservations; `signal_pending_ipi` must run under the sched lock (kernel/smp/ipi.c:414-542); `z_sched_ipi` runs time slicing and IPI work (ipi.c:627-649). Affinity masks are changeable only while the thread cannot run (kernel/smp/cpu_mask.c:666-693). Cross-CPU abort: flag plus IPI plus spin (sched.c:285-374). - Meta-IRQ: the top `NUM_METAIRQ_PRIORITIES` cooperative levels preempt even cooperative and sched-locked threads; `metairq_preempted` records the victim and `next_up` returns to it (kernel/include/metairq.h:431-478; sched.c:76). Intent: interrupt bottom halves (Kconfig:110-124; doc/kernel/services/threads/index.rst:290-300). - Idle: computes the next expiry, calls `pm_system_suspend(_kernel.idle)` with IRQs locked, else `k_cpu_idle` (kernel/idle.c:376-400); spins when SMP lacks IPI (idle.c:364-369); a "legacy mess" yield for `!PREEMPT_ENABLED` (idle.c:406-421). ## Q2 Context switch and interrupts - Two models: legacy `arch_swap(key)` (kswap.h:473-509) and handle-based `arch_switch` under `USE_SWITCH`, required for SMP (Kconfig:1085-1101; smp/Kconfig:8). `do_swap` releases the caller's lock, takes the sched lock, spins on `switch_handle` (the documented SMP chicken-and-egg race, kswap.h:318-351), re-queues the old thread, then `arch_switch` (kswap.h:361-456). - Cortex-M legacy path: PendSV at lowest priority (cortex_m/exception.h:55-57) saves r4-r11 plus PSP, raises BASEPRI to `_EXC_IRQ_DEFAULT_PRIO`, loads `ready_q.cache` into `current`, clears PendSV, restores the thread's saved BASEPRI key, reprograms the MPU (`z_arm_configure_dynamic_mpu_regions`) and CONTROL.nPRIV, handles lazy FP (arch/arm/core/cortex_m/swap_helper.S:48-375). `_isr_wrapper` demuxes `_sw_isr_table` then `z_arm_exc_exit` pends PendSV iff `ready_q.cache != current` (isr_wrapper.c:104-169; exc_exit.c:55-71). The new USE_SWITCH Cortex-M port synthesises frames and hijacks the ISR's stacked LR to run exit code after return (arm-m-switch.c:10-131; include/zephyr/arch/arm/arm-m-switch.h:157-187). Because PendSV sits below IRQs, `SWAP_NONATOMIC` workarounds exist (`pending_current`, timeslicing.c:21-28, 166-173; kthread.h:249-257; Kconfig:710-720). - `arch_irq_lock` returns the prior BASEPRI (v7-M, via `BASEPRI_MAX`) or PRIMASK (v6-M); `arch_irq_unlocked` is key == 0 (include/zephyr/arch/arm/asm_inline_gcc.h:44-121). On SMP `irq_lock` becomes a global recursive spinlock (include/zephyr/irq.h:288-321; smp.c:58-94). - Zero-latency IRQs: priority levels above `_IRQ_PRIO_OFFSET` are never masked (exception.h:40-53; irq_manage.c:272-303). Rules: must use `IRQ_DIRECT_CONNECT`, return 0, no kernel calls, no `ISR_DIRECT_PM` (include/zephyr/arch/arm/irq.h:136-196; cortex_m/Kconfig:331-354; include/zephyr/irq.h:159-231). `arch_zli_lock` uses PRIMASK (asm_inline_gcc.h:141-164). - Direct ISRs: `ISR_DIRECT_DECLARE` emits an `__attribute__((interrupt))` wrapper; the body's return controls `z_arm_int_exit` (arm/irq.h:205-252); dynamic direct dispatchers (irq_manage.c:445-471). - Stacks and nesting: `z_interrupt_stacks[CPUs]` of `ISR_STACK_SIZE` (kernel/init.c:142, 394-396; Kconfig:166-171). Cortex-M relies on hardware MSP and PSP; RISC-V counts `_current_cpu->nested`, switches to `irq_stack` at depth 0 and reschedules only at depth 0 via `z_get_next_switch_handle(NULL)` (arch/riscv/core/isr.S:755-783, 834-910). - `k_is_in_isr` is `arch_is_in_isr` (thread.c:71-75; the ARM inline lives in arch/arm/include, outside the clone). - Misuse detection: `z_pend_curr` panics on an ISR pend in every build (sched.c:531-534); sem, msgq, and mutex assert (sem.c:154, msg_q.c:326, mutex.c:179, 385). `SPIN_VALIDATE` stores owner|cpu in the lock, tracks the outermost held lock and count, and `z_assert_can_swap` forbids switching with extra spinlocks or irq_lock held (kernel/spinlock_validate.c:12-20, 146-185). `__ASSERT` is `(void)0` when off (sys/__assert.h:105-111); `CHECKIF` is error, assert, or nothing (sys/check.h:46-56). ## Q3 Time - `kernel/timeout.c` is a backend-independent front end: 64-bit `curr_tick`, `timeout_lock`, `announce_remaining`, `announcing_cpu`, and an `inflight_timeout` pointer whose low bit means "superseded" (timeout.c:19-70). The backend is from `choice TIMEOUT_BACKEND`: delta dlist by default; minheap, wheel, bucket, skiplist experimental (Kconfig:788-855), included as a header (timeout.c:108-118). The delta list stores `dticks` relative to the predecessor; abort folds the delta into the successor (kernel/timeout_list.h:44-77). - `z_add_timeout`: a relative add gets +1 tick round-up unless this CPU is inside announce (timeout.c:196-218); absolute uses `Z_TICK_ABS(t) = K_TICKS_FOREVER-1-t` (sys/clock.h:148), minimum 1 tick (timeout.c:220-222); programs `sys_clock_set_timeout(next_timeout())` capped at `SYS_CLOCK_MAX_WAIT = UINT32_MAX/2` (timeout.c:126-157; drivers/timer/system_timer.h:27-37). - Driver API: the ISR takes `sys_clock_lock()` and announces with key transfer; `sys_clock_elapsed` and `set_timeout` are called with the lock held (system_timer.h:57-163). The announce loop advances ticks, pops due nodes, drops the lock around each handler; a second SMP announcer just folds its ticks in (timeout.c:343-417). - Race handling: `z_try_abort_timeout` returns -EAGAIN while a handler runs on another CPU and marks it superseded; handlers check `z_timeout_inflight_superseded` (timeout.c:239-286; timeout_q.h:258-284; sched.c:502-519). - `k_timeout_t` is `{k_ticks_t ticks}`, compared with `K_TIMEOUT_EQ`; `k_ticks_t` is int64 under `TIMEOUT_64BIT` (default y) and absolute timeouts need it (sys/clock.h:45-80, 151-158; Kconfig:777-786). - `k_timer`: expiry runs in timer-ISR context with `timer_lock` dropped around `expiry_fn` (kernel/timer.c:72-153); periodic rearm uses `K_TIMEOUT_ABS_TICKS(k_uptime_ticks()+period)` to avoid drift (timer.c:100-112); `k_timer_status_sync` pends on the timer wait_q (timer.c:367-426); `k_timer_cleanup` spins out in-flight handlers (156-216). - `k_uptime_get` is the ms conversion of `curr_tick + elapsed` (kernel.h:2199-2217; timeout.c:438-446); `k_cycle_get_32` and `_64` are raw `arch_k_cycle_get` (kernel.h:2286-2313); `timeutil` offers `timegm` and `timeutil_sync` skew estimation (sys/timeutil.h:14-19, 120-170). ## Q4 Blocking and waiting - `_wait_q_t` is a sorted dlist or rbtree (kernel_structs.h:308-328; kernel/include/wait_q.h:23-98), inserted by `z_sched_prio_cmp` so the head is the best waiter. - `z_pend_curr` does a "lock swap": take the sched lock, `add_to_waitq_locked` (unready, mark pending, set `pended_on`), arm the thread timeout, release the caller's lock, `z_swap_locked` (sched.c:437-464, 522-553). The default `swap_retval` is -EAGAIN (kswap.h:375); wakers set retval and data then ready under the sched lock (`z_sched_wake`, ksched.h:250-310). - Timeout versus wake: both run under `_sched_spinlock`; the waker aborts the timeout, which marks an in-flight handler superseded so `z_thread_timeout` bails (sched.c:502-519, 812-820); `z_unpend_thread` retries -EAGAIN while dropping the lock (sched.c:570-582). - Deletion with waiters: no generic object teardown; thread abort unpends joiners and waits for in-flight timeouts before storage reuse (sched.c:856-983); `k_timer_cleanup` returns -EAGAIN if waiters exist (timer.c:169-184); pipe close and reset wake all with -EPIPE or -ECANCELED (kernel/pipe.c:290-313); sem reset wakes with -EAGAIN (sem.c:184-202). - `k_poll`: events are registered on per-object `poll_events` lists sorted by poller priority (kernel/poll.c:110-168); `is_condition_met` is evaluated under `poll_lock` during registration so a ready object is never missed (poll.c:228-257); signalers clear `is_polling` and ready the thread (poll.c:259-280, 443-482); pend on a static wait_q (poll.c:327-329); only NOTIFY_ONLY mode (poll.c:45). Triggered work submits a `k_work_poll` instead (poll.c:574-645). - `k_event`: post walks the wait queue under the sched lock via `z_sched_waitq_walk`, two-phase for the rbtree (kernel/events.c:138-219); ANY, ALL, RESET, CLEAR options (events.c:282-322). - `k_futex`: one global wait_q filtered by `futex_pointer`; the header admits O(N) scaling and no PI (kernel/userspace/futex.c:14-40, 98-140). - `k_condvar`: unlock the mutex under `condvar_lock`, pend, always re-lock the mutex even on timeout (kernel/condvar.c:105-138). ## Q5 Synchronization and PI - `k_mutex` PI is transitive: lock walks the owner chain through `mutex_pended_on`, boosting each owner, capped at `MUTEX_CHAIN_WALK_MAX_HOPS=16`, with K_FOREVER deadlock detection under `MUTEX_DEADLOCK_DETECT` (kernel/mutex.c:112-118, 227-308; Kconfig:83-91). Each thread keeps `held_mutexes`; `orig_prio` is captured on first acquisition (mutex.c:195-204). Unlock recomputes priority as the highest remaining waiter across all held mutexes and hands off to the best waiter atomically under the sched lock (mutex.c:424-470). Ceiling via `PRIORITY_CEILING` (Kconfig:99-108; ksched.h:160-168). - Documented limitations: a bounded walk; deferred priority drop on timeout (done in the waiter after it resumes, not in the timer ISR, because `mutex_lock` then `_sched_spinlock` ordering forbids it); chain priority-down not propagated (mutex.c:29-49). The timeout path re-takes `mutex_lock`, clears `mutex_pended_on`, recalculates the owner priority (mutex.c:325-363). One global `mutex_lock` spinlock (mutex.c:68-75). - `k_sem`: give wakes one waiter else bumps the count and signals poll; take has a fast path or pends; ISR-safe give; optional `SEM_LOCK_STRIPES` on SMP (sem.c:116-182; Kconfig:353-358). - Spinlocks: `k_spin_lock` is `arch_irq_lock` plus CAS or a ticket lock (spinlock.h:45-73, 194-224); the `K_SPINLOCK` scope macro (spinlock.h:463-465); `LOCK_SCHED_SPINLOCK` compiles out on UP (kspinlock.h:14-26). `SPIN_VALIDATE` catches recursive lock, foreign unlock, and switching with locks held (spinlock_validate.c:27-99, 146-185). There is no general lock-order checker; order is documented per file. ## Q6 IPC and memory - `k_msgq`: a ring buffer with copy; `get` hands the freed slot to a pending sender under the sched lock (kernel/msg_q.c:324-370); user mode validates the message buffer (msg_q.c:286-304). - `k_queue`: an intrusive `sys_sflist`, first word reserved; `_alloc` variants allocate an `alloc_node` from the thread resource pool and flag the node (kernel/queue.c:33-59, 151-181); get passes data via `swap_data` (queue.c:345-375). - `k_pipe` (2024 rewrite): `ring_buf`, `data` and `space` wait queues, direct copy into pending readers' stack `pipe_buf_spec` under the sched lock, partial transfers with timepoints, close and reset (pipe.c:39-150, 152-313); direct copy disabled under `KERNEL_COHERENCE` (pipe.c:180-190). - `k_mbox`: zero-copy, descriptors live on sender and receiver stacks, matched by thread ids, dispose wakes the sender (kernel/mailbox.c:119-208, 254-290, 457-496); async sends via a dummy-thread pool (`NUM_MBOX_ASYNC_MSGS`, Kconfig:649-657). - `k_mem_slab`: a free list threaded through blocks, alignment and overflow checks, free hands the block to a waiter (kernel/mem_slab.c:109-139, 218-296). `k_heap` is `sys_heap` plus a spinlock and a wait_q with retry until timepoint (kernel/kheap.c:26-91); `sys_heap` is segregated-fit, power-of-two buckets, 8-byte chunks, 4-byte header, bounded constant-time operations (sys/sys_heap.h:22-48). - Static definitions: `STRUCT_SECTION_ITERABLE` places objects in `__list` sections, the linker emits `_list_start` and `_end`, `STRUCT_SECTION_FOREACH` iterates (sys/iterable_sections.h:50-73, 244-299); used by `K_SEM_DEFINE`, `K_MSGQ_DEFINE`, `K_THREAD_DEFINE` (kernel.h:3874-3879, 5351-5356, 1029-1034); static heaps init at PRE_KERNEL_1 (kheap.c:35-49); static threads start at boot (thread.c:69). - Work queue v2: a single `work_lock`; state bits RUNNING, CANCELING, QUEUED, DELAYED, FLUSHING (kernel.h:4577-4588); resubmit-while-running pins to the same queue to avoid handler re-entrancy (kernel/work.c:320-348); `cancel_sync` waits on a `pending_cancels` record (work.c:80-151, 533-547); main loop (work.c:671-699). ## Q7 Isolation - Syscalls: `__syscall` is a `static inline` marker (toolchain/common.h:207); `scripts/build/gen_syscalls.py` emits the inline wrapper that traps when `z_syscall_trap()`, `z_mrsh_*` unmarshallers taking 6 `uintptr_t` (extra args via an untrusted `more` array checked with `K_SYSCALL_MEMORY_READ`), 64-bit returns via an untrusted stack slot (gen_syscalls.py:236-266, 358-389; doc/kernel/usermode/syscalls.rst:218-233). Missing verifiers alias a weak "unimplemented" one (syscalls.rst:162-167). Cortex-M SVC copies the frame onto the privileged stack and returns into `z_arm_do_syscall` (svc.S:175-373). - Kernel objects: `gen_kobject_list.py` scans the DWARF of the prebuilt ELF and emits a gperf hash address to `struct k_object {name, perms[MAX_THREAD_BYTES], type, flags, data}` (script docstring; sys/internal/kobject_internal.h:58-64); flags INITIALIZED, PUBLIC, ALLOC, DRIVER (sys/kobject.h:74-80). `k_object_find` tries gperf then dynamic objects (kernel/userspace/userspace.c:593-613); `k_object_validate` checks type, permission bit, init state (userspace.c:909-952); grant, revoke, inherit via thread-index bitmaps (userspace.c:749-907). Verifier macros `K_SYSCALL_OBJ`, `K_SYSCALL_MEMORY_READ` and `WRITE` (calls `arch_buffer_validate`), `K_OOPS` to `arch_syscall_oops` (internal/syscall_handler.h:378-500, 653-670). - Memory domains: partitions with overlap and W^X checks, maximum from `arch_mem_domain_max_partitions_get` (kernel/userspace/mem_domain.c:22-120, 207-232, 402); threads inherit the parent's domain (mem_domain.c:351-362); `arch_mem_domain_*` hooks (arch_interface.h:928-1042); MPU regions reprogrammed inside PendSV (swap_helper.S:224-231, 301-307). - Two stacks: user threads get a privileged stack (`priv_stack_start`, `PRIVILEGED_STACK_SIZE`) used on SVC (svc.S:260-286); `arch_user_mode_enter` sets it up then `z_arm_userspace_enter` (cortex_m/thread.c:277-318); `k_thread_user_mode_enter` sets `K_USER`, clears essential, asserts a user-capable stack (thread.c:1148-1168). - Not protected: supervisor code is fully trusted and `z_impl_` does no checks (syscalls.rst:239-241); supervisor stack overflow "integrity cannot be guaranteed" (usermode/overview.rst:126-127); kernel objects on stacks are invisible to user mode (userspace.c:968-972); `K_THREAD_DEFINE` threads cannot drop to user (kernel.h:1049-1050). - SMP plus userspace: no explicit incompatibility in the docs; `ARCH_MEM_DOMAIN_SYNCHRONOUS_API` and `MEM_DOMAIN_ISOLATED_STACKS` handle live updates (userspace/Kconfig.mem_domain:27-64); `USERSPACE depends on ARCH_HAS_USERSPACE` (arch/Kconfig:412-417). ## Q8 Observability - Logging v2 modes: DEFERRED (mpsc_pbuf), IMMEDIATE, MINIMAL, FRONTEND (subsys/logging/Kconfig.mode:11-71); a processing thread with a trigger threshold (Kconfig.processing:56-64; log_core.c:169-189); drop-oldest overflow (Kconfig.processing:21-26). A log call builds a cbprintf package in place with `CBPRINTF_STATIC_PACKAGE` (logging/log_msg.h:344-377) and commits it with a timestamp or processes inline (log_core.c:720-747). - Dictionary logging: format strings live in the `log_strings` iterable section; with `LOG_FMT_SECTION_STRIP` they link into a DEVNULL region and are decoded on the host from the ELF (linker/common-rom/common-rom-logging.ld:5-26; Kconfig.misc:53-68; Kconfig.formatting:99-107). Wire format: a packed header plus package plus hexdump, the source identified by address (log_output_dict.c:15-67; log_output_dict.h:39-53). - Tracing: `SYS_PORT_TRACING_*` macros are no-ops without `CONFIG_TRACING` (tracing/tracing_macros.h:17-28); format choice CTF, SystemView, Percepio (external module), test, user (subsys/tracing/Kconfig:73-114); sync versus async ring buffer plus a tracing thread (Kconfig:151-163; tracing_core.c:55-74, 164-177); CTF events assembled under `irq_lock` (ctf/ctf_top.h:61-71); backends iterable (tracing_core.c:92-112). - The thread analyzer API reports stack used and utilization (debug/thread_analyzer.h:26-40, 103-126); `k_thread_stack_space_get` (thread.c:1311-1322); runtime stats stamp `usage0` on each switch (kernel/usage.c:21-120; Kconfig:460-491). Thread names (`THREAD_NAME`, thread.c:149-220); the `_kernel.threads` monitor list (thread.c:786-790). - Fatal: `z_fatal_error` prints, coredumps, calls the weak `k_sys_fatal_error_handler` (halt or reboot), then aborts the thread unless panic or ISR (kernel/fatal.c:38-53, 92-206); Cortex-M `z_arm_fault` captures an ESF copy and recovers user faults via fixup tables (fault.c:1106-1136, 147-171). `FIXME #17656`: nested-exception detection unreliable (fatal.c:114-119). - Debugger: `debug/thread_info.c` is outside the clone; object-core links objects per type for tooling (kernel/obj_core.h:160-165; Kconfig.obj_core:4-14). ## Q9 Configuration, portability, quality - Kconfig: `kernel/Kconfig` plus `Kconfig.{device,init,obj_core,vm}`, `smp/Kconfig`, `userspace/Kconfig*`; three algorithm choices (sched, waitq, timeout) and many hidden `select` and `depends` chains. - Arch contract: about 112 `arch_*` entry points in groups timing, threads, pm, smp, irq, mmu, userspace, gdbstub, stackwalk (include/zephyr/arch/arch_interface.h:8-1554) plus the private `kernel_arch_interface.h` (`arch_new_thread`, `arch_switch` or `arch_swap`, `arch_is_in_isr`, `arch_mem_map`, coredump, TLS; kernel/include/kernel_arch_interface.h:39-762). - Toolchain: `toolchain.h` dispatches to xcc, mwdt, armclang, iar, llvm, gcc (include/zephyr/toolchain.h:49-65); attributes `__aligned`, `__packed`, `__weak`, `__used`, `__fallthrough`, `__must_check`, `BUILD_ASSERT` to `static_assert` or `_Static_assert` (toolchain/gcc.h:76-89, 253-325); `TOOLCHAIN_HAS_*` capability flags (toolchain.h:103-139). - Guidelines: AGENTS.md routes to `doc/contribute/coding_guidelines` (a MISRA-C subset), checkpatch, and the compliance script; `MISRA_SANE` and "Enforce coding guideline rules" (Kconfig.zephyr:410, 597); MISRA justifications appear inline (userspace.c:602-605); per-subsystem `hardening.yaml`. Tests are not in the clone; the twister layout is described in AGENTS.md. `CHECKIF` gives a single API-validation idiom (sys/check.h:46-56). ## Q10 Noteworthy and weak points - Devicetree: a pure token-pasting layer, `devicetree.h` about 6,400 lines plus about 13,000 lines under `devicetree/*.h`, node ids as macro names and `DT_CAT` up to 8 arguments (devicetree.h:197, 873, 6429-6441). - Tiny targets: `MULTITHREADING=n` compiles `nothread.c` with `k_sleep` via a timer and `k_cpu_idle` (kernel/nothread.c:12-81; Kconfig:12-24); `SYS_CLOCK_EXISTS=n` stubs timeouts (timeout_q.h:307-323). - Comments admitting awkwardness: "legacy mess" idle yield (idle.c:83), "Kludgey global initialization" (arm-m-switch.c:465), duplicated lock-swap code because `z_swap` is a "legacy API" (sched.c:541-548), an IRQ-lock-only reschedule violating the IPI locking rule (sched.c:652-656), poll locking "subtle" (poll.c:28-34), "Mystic arts for cycle nerds" (ksched.h:70-79), the skiplist `_timeout` grows to 88 bytes (Kconfig:847-852). ## Ideas to borrow 1. Pluggable ready-queue and timeout backends behind tiny macro and header seams (priority_q.h:14-58; timeout.c:108-118). 2. Opaque `k_timeout_t` with absolute encoding in the sign space and `K_TIMEOUT_EQ` (sys/clock.h:65-80, 148-158); drift-free periodic timers (timer.c:100-112). 3. The in-flight timeout "superseded" bit plus -EAGAIN retry protocol for safe cancel and free on SMP (timeout.c:51-70, 239-286). 4. The lock-swap pend (`z_pend_curr`) and swap-return-value plus `swap_data` handoff (sched.c:522-553; ksched.h:293-310). 5. Meta-IRQ threads with "return to the preempted cooperative thread" bookkeeping (metairq.h). 6. Spinlock validation with owner|cpu packing and `z_assert_can_swap` (spinlock_validate.c). 7. Iterable sections for zero-runtime-registration static objects (iterable_sections.h). 8. A build-time kernel-object table from DWARF plus gperf and generated syscall marshallers (gen_kobject_list.py; gen_syscalls.py). 9. cbprintf packaging with host-side dictionary decoding and DEVNULL-linked strings (log_msg.h:344-377; common-rom-logging.ld:5-26). 10. Deferred ISR-exit work by patching the stacked LR on Cortex-M (arm-m-switch.h:157-187). 11. `CHECKIF` tri-mode argument validation (check.h:46-56). ## Weaknesses 1. A global scheduler spinlock and global run queue on SMP; per-CPU queues only with PIN_ONLY (run_q.h:17-49); `SCHED_CPU_MASK` makes the best-thread search O(N) (smp/Kconfig:54-90). 2. Legacy `irq_lock` on SMP is a global recursive spinlock reacquired on switch (irq.h:288-321; doc/kernel/services/smp/smp.rst:55-74). 3. Cortex-M PendSV below IRQ priority forces `SWAP_NONATOMIC` workarounds spread across the scheduler (Kconfig:710-720; timeslicing.c:21-28; kthread.h:249-257). 4. Mutex PI: a bounded chain walk, priority drop deferred on timeout, chain drop not propagated, a global `mutex_lock` (mutex.c:29-49, 68-75, 112-118). 5. Futex uses one global wait queue (futex.c:14-33); `k_poll` has one global lock and NOTIFY_ONLY only (poll.c:28-35, 45). 6. Delta-list timeout insert is O(n); alternatives are experimental, capacity-bounded (minheap panics on overflow), or fatten every `_timeout` (Kconfig:806-852). 7. Deadline scheduling is EDF only within one priority and documented as "simple" (Kconfig:131-138); incompatible with MULTIQ. 8. Configuration explosion: three queue choices, five timeout backends, SMP, IPI, metairq, userspace permutations; many comments describe edge cases only reachable in some permutations (sched.c:93-96, 652-656). 9. The userspace permission bitmap is per thread index (`MAX_THREAD_BYTES`), objects on stacks cannot be shared with user threads, and supervisor code is fully trusted (kobject_internal.h:58-64; userspace.c:968-972; syscalls.rst:239-241). 10. Fatal-error nested-exception detection is unreliable across architectures (fatal.c:114-119, 164). 11. The devicetree macro layer is huge and opaque to tooling (devicetree.h, about 19,000 lines of macros).