Subsystem code depends on infrastructure spread across generic headers, architecture overrides, generated files, and core implementation directories. The following notes organize several mechanisms that do not belong to one device or protocol subsystem: diagnostics, error-valued pointers, branch hints and patchable branches, one-copy accesses, and SLUB allocation paths.
Diagnostics and Error Reporting #
Kernel diagnostics use severity-specific pr_*() macros and device-aware dev_*() macros:
if (revision < minimum_revision) {
dev_err(dev, "unsupported revision %#x\n", revision);
return -ENODEV;
}
pr_debug("queue depth: %u\n", depth);dev_err() includes device context; pr_err() is appropriate when no device exists. Dynamic debug can enable selected pr_debug() and dev_dbg() call sites without promoting permanent log noise. Rate-limited variants such as pr_warn_ratelimited() protect the log from recurring conditions.
Return expected failures as negative error numbers. WARN_ON() and WARN_ON_ONCE() report violated invariants and continue, so callers must still handle the resulting state. BUG() terminates the current execution path and can destabilize the system; kernel guidance restricts it to cases where continued execution is impossible. dump_stack() records a call chain when diagnostic context is needed without asserting a condition.
Error-Valued Pointers #
Functions that otherwise return pointers can encode a negative error number with ERR_PTR():
struct object *object_create(unsigned long flags)
{
if (flags & ~OBJECT_VALID_FLAGS)
return ERR_PTR(-EINVAL);
/* Allocate and initialize the object. */
}
struct object *object = object_create(flags);
if (IS_ERR(object))
return PTR_ERR(object);include/linux/err.h defines ERR_PTR(), PTR_ERR(), IS_ERR(), IS_ERR_OR_NULL(), and PTR_ERR_OR_ZERO(). Current generic code reserves error values through -MAX_ERRNO, where MAX_ERRNO is 4095, but callers must treat the representation as opaque. A function’s contract must distinguish among a valid pointer, NULL, and an error pointer; testing only !ptr misses encoded errors.
Branch Prediction, Prefixes, and Architecture Hooks #
The generic likely(x) and unlikely(x) macros use __builtin_expect() when branch profiling is disabled:
if (unlikely(!object))
return -ENOMEM;The hint communicates an expected value to the compiler. It can influence block layout and which edge becomes fall-through, but source code does not require an x86 branch-hint prefix to be emitted. Modern compiler and processor behavior makes a specific prefix an implementation detail, so inspect the generated object before making an instruction-level claim:
objdump -drS path/to/object.oWith CONFIG_TRACE_BRANCH_PROFILING, include/linux/compiler.h expands the macros through __branch_check__(). The expansion records file, function, and line data in a dedicated section and calls ftrace_likely_update(), while likely_notrace() retains the noninstrumented form. Generated metadata, rather than a hand-written logging call at every branch, enables branch-hit diagnostics.
Static keys solve a different problem. static_branch_likely() and related APIs describe conditions that change infrequently but execute on hot paths. Generic jump-label code calls architecture hooks such as arch_static_branch(). On x86, arch/x86/include/asm/jump_label.h emits a patch site and an __jump_table entry; runtime patching changes a NOP into a jump or the reverse. Thus the disabled path can avoid a repeatedly evaluated load and conditional branch.
Do not substitute static keys for arbitrary mutable booleans. Updates patch executable text and are intentionally more expensive than evaluating the optimized read path.
Generic Interfaces and Architecture Overrides #
Kernel headers often expose a stable generic API while <asm/...> supplies an architecture implementation. A common arrangement is:
- a subsystem includes a generic Linux header;
- that header includes an architecture-selected header through
arch/<arch>/include/asm; - the architecture header implements the hook or includes an
asm-genericfallback.
Examples include barriers, atomic operations, spin locks, jump labels, and one-copy access helpers. Search both the public macro and its architecture hook; stopping at a wrapper can conceal the instruction sequence that matters.
Generated headers and tables form another layer. Kconfig produces configuration headers, syscall and offset machinery generates ABI-dependent constants or tables, and the linker collects specially named sections used for initcalls, trace metadata, jump labels, and exported symbols. A generated artifact in include/generated/ is evidence of a build result, not the authoritative source. Locate the script, input table, Kconfig symbol, or linker script that produced it before editing.
One-Copy Accesses #
READ_ONCE() and WRITE_ONCE() are defined through architecture and generic rwonce headers. They prevent the compiler from merging, inventing, or refetching the marked access and preserve compiler-visible ordering between suitable one-copy operations:
if (READ_ONCE(state->ready))
consume(READ_ONCE(state->value));They do not by themselves provide inter-CPU ordering, mutual exclusion, or an atomic read-modify-write operation. Publishing data commonly needs release/acquire operations such as smp_store_release() and smp_load_acquire(), or a lock. Select a primitive from the concurrency protocol rather than adding READ_ONCE() merely to silence a race detector.
The generic source in include/asm-generic/rwonce.h documents two central use cases: communication between process code and interrupt/NMI code on one CPU, and accesses whose ordering is supplied by an explicit barrier or atomic instruction.
Following SLUB Allocation Paths #
SLUB is implemented primarily in mm/slub.c, with common slab interfaces in mm/slab.h and public allocation APIs in <linux/slab.h>. Callers request objects through interfaces such as:
object = kmem_cache_alloc_node(cache, GFP_KERNEL, node);
if (!object)
return -ENOMEM;
/* Use object. */
kmem_cache_free(cache, object);The public call passes through inlines, allocation hooks, debugging or sanitizer instrumentation, and fast or slow paths. Exact internals depend on configuration symbols such as SLUB debugging and KASAN. Current mm/slub.c describes low-overhead per-CPU caches, largely lockless slow-path freeing through compare-and-exchange where available, and centralized management for pools of partial slabs. Historical comments from another kernel version should not be used as a current algorithm specification.
Allocation flags are part of the call-path contract. GFP_KERNEL permits reclaim and sleeping; atomic contexts require an appropriate non-sleeping allocation strategy. Node selection is a NUMA preference subject to policy and availability, not a promise that allocation can never fall back.
A Source-Location Workflow #
For an unfamiliar internal, follow the compiled path:
- Start from the call site and record its build conditions in the local
Makefileand Kconfig. - Use
git grepfor the public symbol, then follow macro aliases, static inlines, function pointers, and architecture hooks. - Check generated configuration and the target architecture, because inactive branches can look authoritative in source.
- Use
make path/to/file.ior.swhen preprocessing or instruction generation is the question. - Inspect the linked object with
nm,readelf, orobjdump; use ftrace, tracepoints, orperf probeonly after identifying the relevant run-time path. - Compare against the documentation and source from the same kernel revision.
Source paths are useful coordinates, not permanent API promises. Internal structures and helper names can change between releases, so articles and debugging notes should link to a documented interface or a versioned source revision when exact layout matters.
Comments
Read or comment on GitHub (sign-in required to comment)