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 _<type>_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).