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 increasingorder_keyfor 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). MULTIQdepends 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), preemptible0..N-1, idle lowest; lower number is higher priority (Kconfig:26-64; ksched.h:185-193). Preemptibility is a mergedpreemptfield versus_PREEMPT_THRESHOLD(kthread.h:66-70, kernel_structs.h:86-89).z_sched_prio_cmpcomparesprio, then under SCHED_DEADLINE the 32-bit modular difference ofprio_deadline(priority_q.h:81-112): EDF only orders threads within one priority. Deadlines arek_cycle_get_32relative, clamped to INT32_MAX/2, and updates must dequeue and requeue to keep rbtree invariants (kernel/deadline.c:259-300).Time slicing: one
_timeoutper CPU (kernel/timeslicing.c:14-19); applies only to preemptible threads at or belowTIMESLICE_PRIORITY(timeslicing.c:44-64; Kconfig:600-608); per-thread slices plus an expiry callback (timeslicing.c:137-148); rotation viamove_current_to_end_of_prio_q(timeslicing.c:198).k_sched_lockdecrements a per-threadsched_lockedcounter 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}andstruct _cpu {nested, irq_stack, current, idle_thread, metairq_preempted, swap_ok}(include/zephyr/kernel_structs.h:147-248). UP keepsready_q.cacheas the next thread (kernel_structs.h:130-134; sched.c:167-182);_currenton SMP reads_current_cpu->currentwith 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 withSCHED_CPU_MASK_PIN_ONLY(kernel/include/run_q.h:17-49)._currentis 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_updoes the SMP choice (sched.c:57-158). IPIs:flag_ipiandipi_mask_createwith IPI_OPTIMIZE and idle-CPU reservations;signal_pending_ipimust run under the sched lock (kernel/smp/ipi.c:414-542);z_sched_ipiruns 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_PRIORITIEScooperative levels preempt even cooperative and sched-locked threads;metairq_preemptedrecords the victim andnext_upreturns 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, elsek_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-basedarch_switchunderUSE_SWITCH, required for SMP (Kconfig:1085-1101; smp/Kconfig:8).do_swapreleases the caller’s lock, takes the sched lock, spins onswitch_handle(the documented SMP chicken-and-egg race, kswap.h:318-351), re-queues the old thread, thenarch_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, loadsready_q.cacheintocurrent, 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_wrapperdemuxes_sw_isr_tablethenz_arm_exc_exitpends PendSV iffready_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_NONATOMICworkarounds exist (pending_current, timeslicing.c:21-28, 166-173; kthread.h:249-257; Kconfig:710-720).arch_irq_lockreturns the prior BASEPRI (v7-M, viaBASEPRI_MAX) or PRIMASK (v6-M);arch_irq_unlockedis key == 0 (include/zephyr/arch/arm/asm_inline_gcc.h:44-121). On SMPirq_lockbecomes a global recursive spinlock (include/zephyr/irq.h:288-321; smp.c:58-94).Zero-latency IRQs: priority levels above
_IRQ_PRIO_OFFSETare never masked (exception.h:40-53; irq_manage.c:272-303). Rules: must useIRQ_DIRECT_CONNECT, return 0, no kernel calls, noISR_DIRECT_PM(include/zephyr/arch/arm/irq.h:136-196; cortex_m/Kconfig:331-354; include/zephyr/irq.h:159-231).arch_zli_lockuses PRIMASK (asm_inline_gcc.h:141-164).Direct ISRs:
ISR_DIRECT_DECLAREemits an__attribute__((interrupt))wrapper; the body’s return controlsz_arm_int_exit(arm/irq.h:205-252); dynamic direct dispatchers (irq_manage.c:445-471).Stacks and nesting:
z_interrupt_stacks[CPUs]ofISR_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 toirq_stackat depth 0 and reschedules only at depth 0 viaz_get_next_switch_handle(NULL)(arch/riscv/core/isr.S:755-783, 834-910).k_is_in_isrisarch_is_in_isr(thread.c:71-75; the ARM inline lives in arch/arm/include, outside the clone).Misuse detection:
z_pend_currpanics 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_VALIDATEstores owner|cpu in the lock, tracks the outermost held lock and count, andz_assert_can_swapforbids switching with extra spinlocks or irq_lock held (kernel/spinlock_validate.c:12-20, 146-185).__ASSERTis(void)0when off (sys/__assert.h:105-111);CHECKIFis error, assert, or nothing (sys/check.h:46-56).
Q3 Time¶
kernel/timeout.cis a backend-independent front end: 64-bitcurr_tick,timeout_lock,announce_remaining,announcing_cpu, and aninflight_timeoutpointer whose low bit means “superseded” (timeout.c:19-70). The backend is fromchoice 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 storesdticksrelative 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 usesZ_TICK_ABS(t) = K_TICKS_FOREVER-1-t(sys/clock.h:148), minimum 1 tick (timeout.c:220-222); programssys_clock_set_timeout(next_timeout())capped atSYS_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_elapsedandset_timeoutare 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_timeoutreturns -EAGAIN while a handler runs on another CPU and marks it superseded; handlers checkz_timeout_inflight_superseded(timeout.c:239-286; timeout_q.h:258-284; sched.c:502-519).k_timeout_tis{k_ticks_t ticks}, compared withK_TIMEOUT_EQ;k_ticks_tis int64 underTIMEOUT_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 withtimer_lockdropped aroundexpiry_fn(kernel/timer.c:72-153); periodic rearm usesK_TIMEOUT_ABS_TICKS(k_uptime_ticks()+period)to avoid drift (timer.c:100-112);k_timer_status_syncpends on the timer wait_q (timer.c:367-426);k_timer_cleanupspins out in-flight handlers (156-216).k_uptime_getis the ms conversion ofcurr_tick + elapsed(kernel.h:2199-2217; timeout.c:438-446);k_cycle_get_32and_64are rawarch_k_cycle_get(kernel.h:2286-2313);timeutilofferstimegmandtimeutil_syncskew estimation (sys/timeutil.h:14-19, 120-170).
Q4 Blocking and waiting¶
_wait_q_tis a sorted dlist or rbtree (kernel_structs.h:308-328; kernel/include/wait_q.h:23-98), inserted byz_sched_prio_cmpso the head is the best waiter.z_pend_currdoes a “lock swap”: take the sched lock,add_to_waitq_locked(unready, mark pending, setpended_on), arm the thread timeout, release the caller’s lock,z_swap_locked(sched.c:437-464, 522-553). The defaultswap_retvalis -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 soz_thread_timeoutbails (sched.c:502-519, 812-820);z_unpend_threadretries -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_cleanupreturns -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-objectpoll_eventslists sorted by poller priority (kernel/poll.c:110-168);is_condition_metis evaluated underpoll_lockduring registration so a ready object is never missed (poll.c:228-257); signalers clearis_pollingand 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 ak_work_pollinstead (poll.c:574-645).k_event: post walks the wait queue under the sched lock viaz_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 byfutex_pointer; the header admits O(N) scaling and no PI (kernel/userspace/futex.c:14-40, 98-140).k_condvar: unlock the mutex undercondvar_lock, pend, always re-lock the mutex even on timeout (kernel/condvar.c:105-138).
Q5 Synchronization and PI¶
k_mutexPI is transitive: lock walks the owner chain throughmutex_pended_on, boosting each owner, capped atMUTEX_CHAIN_WALK_MAX_HOPS=16, with K_FOREVER deadlock detection underMUTEX_DEADLOCK_DETECT(kernel/mutex.c:112-118, 227-308; Kconfig:83-91). Each thread keepsheld_mutexes;orig_priois 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 viaPRIORITY_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_lockthen_sched_spinlockordering forbids it); chain priority-down not propagated (mutex.c:29-49). The timeout path re-takesmutex_lock, clearsmutex_pended_on, recalculates the owner priority (mutex.c:325-363). One globalmutex_lockspinlock (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; optionalSEM_LOCK_STRIPESon SMP (sem.c:116-182; Kconfig:353-358).Spinlocks:
k_spin_lockisarch_irq_lockplus CAS or a ticket lock (spinlock.h:45-73, 194-224); theK_SPINLOCKscope macro (spinlock.h:463-465);LOCK_SCHED_SPINLOCKcompiles out on UP (kspinlock.h:14-26).SPIN_VALIDATEcatches 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;gethands 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 intrusivesys_sflist, first word reserved;_allocvariants allocate analloc_nodefrom the thread resource pool and flag the node (kernel/queue.c:33-59, 151-181); get passes data viaswap_data(queue.c:345-375).k_pipe(2024 rewrite):ring_buf,dataandspacewait queues, direct copy into pending readers’ stackpipe_buf_specunder the sched lock, partial transfers with timepoints, close and reset (pipe.c:39-150, 152-313); direct copy disabled underKERNEL_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_heapissys_heapplus a spinlock and a wait_q with retry until timepoint (kernel/kheap.c:26-91);sys_heapis 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_ITERABLEplaces objects in_<type>_listsections, the linker emits_list_startand_end,STRUCT_SECTION_FOREACHiterates (sys/iterable_sections.h:50-73, 244-299); used byK_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_syncwaits on apending_cancelsrecord (work.c:80-151, 533-547); main loop (work.c:671-699).
Q7 Isolation¶
Syscalls:
__syscallis astatic inlinemarker (toolchain/common.h:207);scripts/build/gen_syscalls.pyemits the inline wrapper that traps whenz_syscall_trap(),z_mrsh_*unmarshallers taking 6uintptr_t(extra args via an untrustedmorearray checked withK_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 intoz_arm_do_syscall(svc.S:175-373).Kernel objects:
gen_kobject_list.pyscans the DWARF of the prebuilt ELF and emits a gperf hash address tostruct 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_findtries gperf then dynamic objects (kernel/userspace/userspace.c:593-613);k_object_validatechecks type, permission bit, init state (userspace.c:909-952); grant, revoke, inherit via thread-index bitmaps (userspace.c:749-907). Verifier macrosK_SYSCALL_OBJ,K_SYSCALL_MEMORY_READandWRITE(callsarch_buffer_validate),K_OOPStoarch_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_entersets it up thenz_arm_userspace_enter(cortex_m/thread.c:277-318);k_thread_user_mode_entersetsK_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_DEFINEthreads cannot drop to user (kernel.h:1049-1050).SMP plus userspace: no explicit incompatibility in the docs;
ARCH_MEM_DOMAIN_SYNCHRONOUS_APIandMEM_DOMAIN_ISOLATED_STACKShandle 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_stringsiterable section; withLOG_FMT_SECTION_STRIPthey 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 withoutCONFIG_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 underirq_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 stampusage0on each switch (kernel/usage.c:21-120; Kconfig:460-491). Thread names (THREAD_NAME, thread.c:149-220); the_kernel.threadsmonitor list (thread.c:786-790).Fatal:
z_fatal_errorprints, coredumps, calls the weakk_sys_fatal_error_handler(halt or reboot), then aborts the thread unless panic or ISR (kernel/fatal.c:38-53, 92-206); Cortex-Mz_arm_faultcaptures 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.cis 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/KconfigplusKconfig.{device,init,obj_core,vm},smp/Kconfig,userspace/Kconfig*; three algorithm choices (sched, waitq, timeout) and many hiddenselectanddependschains.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 privatekernel_arch_interface.h(arch_new_thread,arch_switchorarch_swap,arch_is_in_isr,arch_mem_map, coredump, TLS; kernel/include/kernel_arch_interface.h:39-762).Toolchain:
toolchain.hdispatches to xcc, mwdt, armclang, iar, llvm, gcc (include/zephyr/toolchain.h:49-65); attributes__aligned,__packed,__weak,__used,__fallthrough,__must_check,BUILD_ASSERTtostatic_assertor_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_SANEand “Enforce coding guideline rules” (Kconfig.zephyr:410, 597); MISRA justifications appear inline (userspace.c:602-605); per-subsystemhardening.yaml. Tests are not in the clone; the twister layout is described in AGENTS.md.CHECKIFgives a single API-validation idiom (sys/check.h:46-56).
Q10 Noteworthy and weak points¶
Devicetree: a pure token-pasting layer,
devicetree.habout 6,400 lines plus about 13,000 lines underdevicetree/*.h, node ids as macro names andDT_CATup to 8 arguments (devicetree.h:197, 873, 6429-6441).Tiny targets:
MULTITHREADING=ncompilesnothread.cwithk_sleepvia a timer andk_cpu_idle(kernel/nothread.c:12-81; Kconfig:12-24);SYS_CLOCK_EXISTS=nstubs 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_swapis 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_timeoutgrows to 88 bytes (Kconfig:847-852).
Ideas to borrow¶
Pluggable ready-queue and timeout backends behind tiny macro and header seams (priority_q.h:14-58; timeout.c:108-118).
Opaque
k_timeout_twith absolute encoding in the sign space andK_TIMEOUT_EQ(sys/clock.h:65-80, 148-158); drift-free periodic timers (timer.c:100-112).The in-flight timeout “superseded” bit plus -EAGAIN retry protocol for safe cancel and free on SMP (timeout.c:51-70, 239-286).
The lock-swap pend (
z_pend_curr) and swap-return-value plusswap_datahandoff (sched.c:522-553; ksched.h:293-310).Meta-IRQ threads with “return to the preempted cooperative thread” bookkeeping (metairq.h).
Spinlock validation with owner|cpu packing and
z_assert_can_swap(spinlock_validate.c).Iterable sections for zero-runtime-registration static objects (iterable_sections.h).
A build-time kernel-object table from DWARF plus gperf and generated syscall marshallers (gen_kobject_list.py; gen_syscalls.py).
cbprintf packaging with host-side dictionary decoding and DEVNULL-linked strings (log_msg.h:344-377; common-rom-logging.ld:5-26).
Deferred ISR-exit work by patching the stacked LR on Cortex-M (arm-m-switch.h:157-187).
CHECKIFtri-mode argument validation (check.h:46-56).
Weaknesses¶
A global scheduler spinlock and global run queue on SMP; per-CPU queues only with PIN_ONLY (run_q.h:17-49);
SCHED_CPU_MASKmakes the best-thread search O(N) (smp/Kconfig:54-90).Legacy
irq_lockon SMP is a global recursive spinlock reacquired on switch (irq.h:288-321; doc/kernel/services/smp/smp.rst:55-74).Cortex-M PendSV below IRQ priority forces
SWAP_NONATOMICworkarounds spread across the scheduler (Kconfig:710-720; timeslicing.c:21-28; kthread.h:249-257).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).Futex uses one global wait queue (futex.c:14-33);
k_pollhas one global lock and NOTIFY_ONLY only (poll.c:28-35, 45).Delta-list timeout insert is O(n); alternatives are experimental, capacity-bounded (minheap panics on overflow), or fatten every
_timeout(Kconfig:806-852).Deadline scheduling is EDF only within one priority and documented as “simple” (Kconfig:131-138); incompatible with MULTIQ.
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).
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).Fatal-error nested-exception detection is unreliable across architectures (fatal.c:114-119, 164).
The devicetree macro layer is huge and opaque to tooling (devicetree.h, about 19,000 lines of macros).