Kernel C
You know C. The kernel's C has different rules, a different standard library, a different error convention, and a type system extended by annotations a separate tool checks. None of it is arbitrary; every rule below exists because the alternative broke something.
This chapter covers four concepts in the six-part treatment — the freestanding environment, the stack, errors without exceptions, and types and annotations — then a closing section on the idioms you must be able to read without stopping.
Concept 1: The Freestanding Environment
1. What problem it solves
There is no C library beneath the kernel. There cannot be: libc is a user-space program's interface to the kernel, and the kernel has nothing under it but the hardware. So the kernel provides its own — a smaller, stricter, differently-named standard library, plus a set of prohibitions on language features that assume runtime support the kernel does not have.
2. Where it exists in the kernel
lib/, include/linux/, and per-architecture optimized versions in arch/<arch>/lib/.
ls lib/*.c | head -20
rg -n "^ssize_t strscpy|^int kstrtoint|^int scnprintf" lib/string.c lib/kstrtox.c lib/vsprintf.c
rg -n "kernel_fpu_begin" arch/x86/include/asm/fpu/api.h
The build enforces the freestanding-ness: -ffreestanding, -nostdinc, and a curated include path.
If you #include <stdio.h> in a kernel file it will not be found, and that is deliberate.
rg -n "nostdinc|ffreestanding" Makefile scripts/Makefile.lib | head
3. Who owns or interacts with it
| Actor | Interaction |
|---|---|
lib/ maintainers | Own the string, sorting, and conversion helpers |
| The compiler | Enforces -ffreestanding; some builtins are still emitted (memcpy, memset) and the kernel provides them |
checkpatch.pl | Flags deprecated functions by name |
Coccinelle (make coccicheck) | Automatically finds and can fix whole classes of misuse |
4. The replacements you actually need
| You want | Do not use | Use |
|---|---|---|
| Allocate | malloc, calloc, realloc, free | kmalloc, kzalloc, kcalloc, kvmalloc, krealloc, kfree, kvfree |
printf, fprintf | pr_info/pr_err/pr_debug; in a driver, dev_info/dev_err/dev_dbg | |
| Format into a buffer | sprintf (unbounded), snprintf (returns what it would have written) | scnprintf (returns what it did write); in a sysfs show(), sysfs_emit() |
| Copy a string | strcpy, strncpy, strlcpy | strscpy — always NUL-terminates, returns -E2BIG on truncation |
| Parse a number | atoi, strtol, simple_strtoul | kstrtoint, kstrtoul, kstrtou32, … — return 0 or -EINVAL/-ERANGE |
| Duplicate a string | strdup | kstrdup, kstrndup, kmemdup; from user space, strndup_user |
| Compare/search memory | memcmp, memchr | Same names, kernel implementations. memcmp is not constant-time; for secrets use crypto_memneq. |
| Sort | qsort | sort() from <linux/sort.h>; list_sort() for lists |
| Assert | assert | WARN_ON/WARN_ON_ONCE (recoverable), BUG_ON (panics — almost never justified) |
Verify the current state of any of those rather than trusting the table:
rg -n "strlcpy|strscpy" include/linux/string.h
rg -n "deprecated" scripts/checkpatch.pl | head -20
$EDITOR Documentation/process/deprecated.rst # the canonical list, maintained
Two prohibitions with no replacement:
No floating point. The FPU/SIMD register state is not saved on kernel entry, so touching it
corrupts the interrupted user program's registers. Code that genuinely needs SIMD (crypto, RAID
parity) brackets it with kernel_fpu_begin()/kernel_fpu_end(), which is expensive and disables
preemption. Ordinary kernel code uses fixed-point arithmetic.
No 64-bit division on 32-bit architectures. The compiler emits a call to a libgcc helper
(__udivdi3, __aeabi_uldivmod) that the kernel does not link. It builds fine on x86-64 and fails
to link on 32-bit ARM — which is exactly the kind of breakage the 0-day bot will mail you about.
u64 total = ...;
u32 count = ...;
/* WRONG on 32-bit: undefined reference to __udivdi3 at link time. */
u64 avg = total / count;
/* Right: */
u64 avg = div_u64(total, count);
/* or, when you also want the remainder — note do_div MODIFIES its first argument: */
u64 n = total;
u32 rem = do_div(n, count); /* n becomes the quotient */
5. Experiment
CLAIM. snprintf and scnprintf differ in a way that turns a truncation into an out-of-bounds
write in the classic accumulate-into-a-buffer loop.
METHOD. Reason it out on paper first, then confirm in user space where it is safe:
cat > /tmp/sn.c <<'EOF'
#include <stdio.h>
int main(void) {
char buf[16];
/* snprintf returns the length it WOULD have written. */
int n = snprintf(buf, sizeof(buf), "%s", "a-string-far-longer-than-sixteen");
printf("buf=%zu snprintf returned %d\n", sizeof(buf), n);
printf("if you now do buf+n, you are %d bytes PAST the end\n", n - (int)sizeof(buf) + 1);
return 0;
}
EOF
gcc -o /tmp/sn /tmp/sn.c && /tmp/sn
PREDICT FIRST: in the loop below, how far past the end of buf does the second call write?
/* The bug, in the shape it actually appears in drivers: */
int len = 0;
for (i = 0; i < n; i++)
len += snprintf(buf + len, size - len, "%s ", item[i]);
/* ^^^^^^^^^ len can exceed size ^^^^^^^^^^
* → pointer past the end → size - len underflows
* to a huge size_t */
/* The fix is one character: */
len += scnprintf(buf + len, size - len, "%s ", item[i]);
scnprintf returns what it actually wrote, so len can never exceed size. This is why
checkpatch and reviewers care, and it is a real CVE pattern.
6. Failure mode
| Mistake | Symptom |
|---|---|
#include <stdio.h> | "No such file or directory" — and the right reaction is not to find a path for it |
| Floating point in kernel code | Silent corruption of a user program's FPU registers; on some architectures, an oops |
| 64-bit division on 32-bit | undefined reference to __udivdi3 at link time — on an architecture you do not build |
snprintf accumulation loop | Out-of-bounds write; size - len underflows to a huge size_t |
strcpy/strlcpy | checkpatch rejection at minimum; a truncation bug at worst |
sprintf into a sysfs buffer | Overflow of the PAGE_SIZE buffer. Use sysfs_emit. |
BUG_ON for a recoverable condition | You crashed a machine to report something a WARN_ON and an error return would have handled |
Warning:
BUG_ON()kills the machine.Documentation/process/coding-style.rstand years of review agree: do not add newBUG_ONs. If the condition is recoverable, return an error. If you want to be loud,WARN_ON_ONCE()produces a stack trace and continues, and syzbot and the CI bots treat aWARNas a failure — so it will be noticed.
Concept 2: The Kernel Stack
1. What problem it solves
Every task needs somewhere to put its kernel-mode locals and call frames. Giving each of the thousands of tasks on a machine a growable multi-megabyte stack is not affordable, so the kernel gives each one a small, fixed allocation — and then imposes the discipline that makes that affordable.
2. Where it exists in the kernel
Per-architecture, allocated at task creation.
rg -n "define THREAD_SIZE" arch/x86/include/asm/page_64_types.h arch/arm64/include/asm/memory.h
grep -E 'CONFIG_(VMAP_STACK|FRAME_WARN|THREAD_SHIFT)' ~/kernel/build/.config
rg -n "vla|Wvla" Makefile scripts/Makefile.extrawarn | head
Typically 16 KB on 64-bit architectures (it was 8 KB historically and was raised because deep
call chains were overflowing). It does not grow. There is no guard mechanism at all unless
CONFIG_VMAP_STACK is on, in which case there is a guard page and an overflow faults instead of
quietly scribbling on the adjacent allocation.
3. Who owns or interacts with it
| Actor | Interaction |
|---|---|
| The scheduler | Saves and restores the stack pointer on every context switch |
| Interrupts | Land on the current task's stack (or a dedicated IRQ stack, per architecture) |
| The compiler | Emits -Wframe-larger-than=N when one function's frame exceeds CONFIG_FRAME_WARN |
objtool | Validates stack frame bookkeeping so the unwinder can produce correct traces |
4. The rules that follow
| Rule | Reason |
|---|---|
| No large locals. A 4 KB buffer is a quarter of the budget. | 16 KB total, shared with every frame beneath you |
| No recursion. Ever. | Nothing bounds the depth |
No variable-length arrays. The kernel removed all of them and -Wvla is on. | An attacker-influenced length becomes an attacker-chosen stack offset |
No alloca(). | Same |
| Do not DMA to or from the stack. | The DMA API requires cache-line-aligned memory that is not on a stack; see Memory |
| Watch what the compiler inlines. | Three inlined functions with 1 KB frames each is a 3 KB frame in one function |
The idiom when you need a real buffer:
/* NOT this: */
char buf[PAGE_SIZE]; /* 4 KB of a 16 KB budget, in one frame */
/* This: */
char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
...
kfree(buf);
/* Or, for a buffer whose size is only known at runtime and may be large: */
void *buf = kvmalloc(len, GFP_KERNEL); /* kmalloc, falling back to vmalloc */
...
kvfree(buf);
5. Experiment
CLAIM. The build system enforces the stack budget statically, and CONFIG_VMAP_STACK turns a
runtime overflow from silent corruption into a diagnosable fault.
METHOD. In a scratch module, add a function with an oversized frame and build it:
static noinline int hog(void)
{
char big[8192]; /* half the stack, in one frame */
memset(big, 0xAA, sizeof(big));
return big[get_random_u32() % sizeof(big)];
}
PREDICT FIRST: does this (a) fail to compile, (b) compile with a warning, (c) compile silently and crash at runtime, or (d) compile and work? Write your answer, then:
make -C ~/kernel/build M=$PWD modules 2>&1 | grep -i "frame size"
grep CONFIG_FRAME_WARN ~/kernel/build/.config
Then check what real functions in the tree cost:
cd ~/kernel/build
# The functions with the largest stack frames in your build:
objdump -d vmlinux 2>/dev/null | \
awk '/^[0-9a-f]+ </{f=$2} /sub .*0x[0-9a-f]+,%rsp/{print $NF, f}' | \
sort -rn | head -10
6. Failure mode
| Mistake | Symptom |
|---|---|
| A large stack local | Build warning at best; with VMAP_STACK, a "kernel stack overflow" oops; without it, corruption of an unrelated allocation |
| Recursion in kernel code | Overflow whose depth depends on input — i.e. attacker-controllable |
| A VLA | -Wvla error; historically, a stack-clash vulnerability class |
| Deep call chains through function pointers | Overflow that only happens with one particular driver stack loaded |
| DMA to a stack buffer | Silent data corruption, or an IOMMU fault, depending on the platform |
Note:
CONFIG_VMAP_STACKdoes not prevent overflow — it makes it detectable. The difference between "kernel stack overflow atfoo+0x2c" and "some unrelated structure was corrupted, and three seconds later something else crashed" is the difference between a ten-minute fix and a week. Leave it on.
Concept 3: Errors Without Exceptions
1. What problem it solves
There is no unwinding mechanism in the kernel: no exceptions, no destructors, no RAII, no
defer. Every function that can fail must communicate that in its return value, and every caller
must handle it — including releasing everything acquired so far, in the right order.
The kernel's answer is a strict convention plus one structural idiom (goto unwinding) that makes
the correct thing also the readable thing.
2. Where it exists in the kernel
$EDITOR include/uapi/asm-generic/errno-base.h # 1-34: the classic errnos
$EDITOR include/uapi/asm-generic/errno.h # 35+
$EDITOR include/linux/errno.h # 512+: KERNEL-INTERNAL ONLY
rg -n "define MAX_ERRNO|define IS_ERR\b|define ERR_PTR\b" include/linux/err.h
3. Who owns or interacts with it
| Actor | Interaction |
|---|---|
| Every kernel function | Returns int (0/negative) or a pointer wrapped with ERR_PTR |
| The syscall exit path | Passes the negative value out; libc negates it into errno |
| The driver core | Interprets -EPROBE_DEFER specially — retry later |
checkpatch/smatch/coccinelle | Find unchecked returns and leaked-on-error resources |
4. The conventions
Integer returns. 0 or a positive value on success; a negative errno on failure. Pick the errno
that already means what you mean:
| Errno | Means |
|---|---|
-EINVAL | The caller passed something that cannot be right |
-ENOMEM | Allocation failed |
-EFAULT | A user pointer was bad |
-EBUSY | Right request, wrong time |
-EAGAIN | Try again; on a non-blocking fd, "nothing right now" |
-ENODEV / -ENXIO | No such device |
-EOPNOTSUPP / -ENOTSUPP | Not implemented for this case (-EOPNOTSUPP is the one user space should see) |
-EPERM / -EACCES | Not permitted (capability) / not allowed (permission bits) |
-EPROBE_DEFER | Kernel-internal. A dependency is not ready; the driver core will retry. |
-ERESTARTSYS | Kernel-internal. A signal interrupted a sleep; restart the syscall. |
Warning: The values at 512 and above in
include/linux/errno.h—ERESTARTSYS,ENOIOCTLCMD,EPROBE_DEFER— must never reach user space. Returning-EPROBE_DEFERout of anioctlgives the caller a nonsenseerrnoof 517. Some of these are caught by the syscall exit path; not all are. This is a real and recurring review comment.
Pointer returns. A function returning a pointer encodes an error in the pointer itself, using the fact that the top 4096 addresses are never valid kernel pointers:
struct thing *thing_create(int n)
{
struct thing *t;
if (n < 0)
return ERR_PTR(-EINVAL);
t = kzalloc(sizeof(*t), GFP_KERNEL);
if (!t)
return ERR_PTR(-ENOMEM);
return t;
}
/* At the call site — pick the right test: */
t = thing_create(n);
if (IS_ERR(t))
return PTR_ERR(t); /* an error was encoded */
/* If the function can return NULL *or* an error pointer (many lookups do): */
if (IS_ERR_OR_NULL(t))
return t ? PTR_ERR(t) : -ENOENT;
/* If you only care whether it failed: */
ret = PTR_ERR_OR_ZERO(t);
/* If you must return an error pointer of a DIFFERENT pointer type: */
return ERR_CAST(t); /* keeps sparse quiet, no cast */
The goto unwind. This is the single most important idiom in the chapter, and it is the one
place the kernel uses goto freely and on purpose.
static int mylab_probe(struct platform_device *pdev)
{
struct mylab *ml;
int ret;
ml = kzalloc(sizeof(*ml), GFP_KERNEL);
if (!ml)
return -ENOMEM; /* nothing acquired yet */
ml->buf = kmalloc(BUF_SZ, GFP_KERNEL);
if (!ml->buf) {
ret = -ENOMEM;
goto err_free_ml;
}
ml->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(ml->clk)) {
ret = PTR_ERR(ml->clk);
goto err_free_buf;
}
ret = clk_prepare_enable(ml->clk);
if (ret)
goto err_put_clk;
ret = request_irq(ml->irq, mylab_isr, 0, "mylab", ml);
if (ret)
goto err_disable_clk;
platform_set_drvdata(pdev, ml);
return 0;
/* Labels are named for WHAT THEY UNDO, and they fall through in
* exact reverse order of acquisition. Each error path jumps to the
* label that undoes the step BEFORE the one that failed. */
err_disable_clk:
clk_disable_unprepare(ml->clk);
err_put_clk:
clk_put(ml->clk);
err_free_buf:
kfree(ml->buf);
err_free_ml:
kfree(ml);
return ret;
}
Three rules that make this reliable:
- Name labels after the action they perform, not
err1/err2/out. When you insert a step in the middle, numbered labels all become wrong and nobody notices. - Fall through, in reverse order. Each label undoes one thing and falls into the next.
- Jump to the label for the step before the one that failed. The failed step acquired nothing.
The devres API in the device model chapter removes most of this from driver
probe paths — but you must be able to write it by hand first, because the moment your teardown order
is not simply "reverse of acquisition", devres cannot express it and you are back here.
5. Experiment
CLAIM. The error paths in your code are never executed by normal testing, and the kernel gives you a way to execute them all.
METHOD. Fault injection makes allocations fail on demand.
# In the guest, with CONFIG_FAULT_INJECTION and CONFIG_FAILSLAB (lab-paranoid):
F=/sys/kernel/debug/failslab
echo 10 > $F/probability # 10% of allocations fail
echo 1 > $F/times # ...this many times (-1 for unlimited)
echo 0 > $F/verbose
echo Y > /sys/kernel/debug/failslab/ignore-gfp-wait
insmod /mnt/host/modules/02-chardev/chardev.ko # over and over
PREDICT FIRST: with a 10% allocation failure rate and four allocations in your probe path, what fraction of load attempts should fail cleanly? And of those, how many will leak — i.e. how confident are you in your unwind?
Then check with kmemleak:
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak
RESULT. Record how many leaks you found. Most people find at least one on their first module, and that is the point of the experiment.
6. Failure mode
| Mistake | Symptom |
|---|---|
| Ignoring a return value | The thing you needed did not happen; the failure surfaces somewhere unrelated |
if (ret) where ret is a byte count | See the boundary chapter |
Early return after acquiring something | A leak, on a path nothing tests |
Numbered goto labels | Someone inserts a step and the unwinding is now off by one |
| Unwinding in the wrong order | Freeing a structure while an IRQ handler that uses it is still registered → use-after-free |
| Leaking an error pointer to user space | errno 517 in a program, and a confused bug report |
IS_ERR on a pointer that returns NULL for failure | The NULL sails through IS_ERR and is dereferenced |
| Testing only the happy path | Every one of the above ships |
Concept 4: Types, Annotations, and Sparse
1. What problem it solves
C's type system cannot express "this pointer is in another address space", "this integer is little-endian regardless of the CPU", or "this pointer may only be read inside an RCU read-side critical section". Those are exactly the distinctions whose violation causes the kernel's worst bugs.
The kernel extends the type system with annotations that the compiler ignores and a separate
checker, sparse, enforces.
2. Where it exists in the kernel
rg -n "__user|__iomem|__rcu|__percpu|__kernel" include/linux/compiler_types.h | head
$EDITOR Documentation/dev-tools/sparse.rst
make C=1 M=$PWD # check the files being (re)compiled
make C=2 M=$PWD # check everything, even unchanged files
3. Who owns or interacts with it
| Actor | Interaction |
|---|---|
sparse | Enforces address-space and endianness annotations |
smatch | A deeper flow-sensitive checker built on sparse's parser (make CHECK=smatch C=1) |
| The compiler | Sees the annotations expand to nothing (or to a GCC attribute) |
| The 0-day bot | Runs sparse on your patch and mails you the new warnings |
4. The vocabulary
Fixed-width integers. Two spellings, and using the wrong one in the wrong place is a review comment:
| Context | Spelling |
|---|---|
| Kernel-internal code | u8 u16 u32 u64, s8 s16 s32 s64 |
include/uapi/ headers | __u8 __u16 __u32 __u64, __s8 … — user space has no u32 |
Semantic types, which exist so a mismatch is visible: size_t, ssize_t, loff_t (file
offsets), pid_t, gfp_t (allocation flags), dma_addr_t (a device's view of memory — not a
physical address), phys_addr_t, resource_size_t, cycles_t, atomic_t, refcount_t.
Address-space annotations, checked by sparse:
| Annotation | Means | Access it with |
|---|---|---|
__user | A user-space pointer | copy_*_user, get_user, put_user |
__iomem | A pointer to device MMIO | readb/w/l/q, writeb/w/l/q, ioread32, memcpy_fromio |
__rcu | Only valid inside an RCU read-side critical section | rcu_dereference, rcu_assign_pointer |
__percpu | A per-CPU pointer, not a real address | this_cpu_ptr, per_cpu_ptr |
Endianness types: __le16/32/64, __be16/32/64. Sparse tracks these and flags implicit
conversions.
struct on_disk_header {
__le32 magic; /* the FORMAT is little-endian, whatever the CPU is */
__le64 size;
__be16 port; /* network byte order */
};
/* Every access converts explicitly. sparse rejects `if (h->magic == MAGIC)`. */
if (le32_to_cpu(h->magic) != MYFS_MAGIC)
return -EINVAL;
h->size = cpu_to_le64(new_size);
port = be16_to_cpu(h->port);
/* And for a possibly-unaligned buffer (a packet, a firmware blob): */
u32 v = get_unaligned_le32(ptr);
Function and variable attributes you will see constantly:
| Attribute | Effect |
|---|---|
__init / __exit | Placed in a section freed after boot / after module unload |
__initdata / __initconst | Same, for data |
__ro_after_init | Writable during init, read-only forever after — a hardening measure |
__must_check | The caller is warned if it discards the return value |
__packed | No padding. Slow on some architectures; use only for on-wire/on-disk layouts. |
__aligned(n) | Force alignment (e.g. cache line, DMA requirement) |
__printf(a, b) | Format-string checking for a printk-like function |
__maybe_unused | Suppress an unused warning in a #ifdef-heavy file |
noinline / __always_inline | Control inlining, usually for stack or tracing reasons |
printk format specifiers — the kernel's are not libc's, and the differences matter:
| Specifier | Prints |
|---|---|
%p | A hashed pointer. Since the pointer-leak hardening work, raw addresses are not printed by default. |
%px | The raw address. A deliberate security decision — justify it in review. |
%pK | Hashed or raw depending on kptr_restrict and the reader's privilege |
%pS / %ps | A symbol name for a code address: vfs_read+0x2c/0x180 |
%pe | An error pointer, by name: -ENOMEM instead of fffffffffffffff4 |
%pI4 / %pI6 / %pM | An IPv4 / IPv6 address / a MAC address |
%pOF / %pfw | A device-tree node / a fwnode path |
$EDITOR Documentation/core-api/printk-formats.rst # the full list, in your tree
5. Experiment
CLAIM. sparse finds a whole class of bug that the compiler compiles without complaint.
METHOD. In a scratch module, write three deliberate annotation errors:
static int deliberate(void __user *up, void __iomem *io, __le32 *disk)
{
char c = *(char *)up; /* 1. user deref, cast away __user */
u32 reg = *(u32 *)io; /* 2. MMIO deref instead of readl() */
if (*disk == 0x1234) /* 3. __le32 compared as native */
return 1;
return c + reg;
}
PREDICT FIRST: for each of the three, does gcc warn? Does gcc -Wall -Wextra warn? Does
sparse warn? Does it crash at runtime, and on which architectures?
make M=$PWD modules # gcc alone
make W=1 M=$PWD modules # extra warnings
make C=2 M=$PWD modules # sparse
RESULT. Note which tool caught which. Error 3 in particular is invisible on a little-endian
machine and produces a filesystem that cannot be read on a big-endian one — the exact bug class
sparse was written for.
6. Failure mode
| Mistake | Symptom |
|---|---|
Casting away __user | Oops on SMAP/PAN hardware; a security hole on older hardware |
Dereferencing __iomem | Works on x86 by accident (MMIO is memory-mapped); fails or reorders badly on other architectures |
Reading __rcu without rcu_dereference | A use-after-free that appears only under load |
| Ignoring endianness types | Works on your machine; corrupts data on the other half of the world's machines |
u32 in a uapi header | The header does not compile in user space |
%p where you needed a real address | You debug for an hour with a hashed value that changes every boot |
%px in code you ship | An address leak that defeats KASLR |
__packed on a hot struct | Unaligned accesses; a large, silent performance loss on some architectures |
Calling an __init function after boot | modpost section-mismatch warning; a jump into freed memory if you ignore it |
The Idioms You Must Read Fluently
Not concepts — vocabulary. You will hit every one of these in the first file you open.
container_of: the kernel's inheritance
There are no base classes. Instead, a generic structure is embedded in yours, and
container_of() recovers the outer structure from a pointer to the inner one.
struct my_device {
int my_field;
struct device dev; /* embedded, not a pointer */
struct list_head node; /* embedded, not a pointer */
};
/* Given a `struct device *d` that the driver core handed you: */
struct my_device *md = container_of(d, struct my_device, dev);
/* ^ ^ ^
* the inner ptr─┘ │ └─ the member name
* └─ the outer type
* It is pointer arithmetic: d minus offsetof(struct my_device, dev). */
rg -n -A 12 "define container_of" include/linux/container_of.h
struct list_head: intrusive lists
The list node lives inside your object, so a list operation never allocates and cannot fail.
struct my_device *md;
LIST_HEAD(devices); /* a head, on the stack or static */
list_add_tail(&md->node, &devices);
list_for_each_entry(md, &devices, node) /* md is set to each container */
pr_info("%d\n", md->my_field);
list_for_each_entry_safe(md, tmp, &devices, node) /* safe against deletion */
list_del(&md->node);
rg -n "list_for_each_entry\b" include/linux/list.h
rg -n "hlist_for_each_entry\b" include/linux/list.h # hash tables: 1-word heads
The helper macros
| Macro | Does |
|---|---|
ARRAY_SIZE(a) | Element count, with a build-time check that a is really an array |
min_t(type, a, b) / max_t / clamp | Compare after an explicit cast — avoids signed/unsigned surprises |
DIV_ROUND_UP(n, d), ALIGN(x, a), PAGE_ALIGN(x) | Arithmetic without off-by-ones |
BIT(n), GENMASK(hi, lo) | Bit and bitfield masks, readable |
FIELD_GET(mask, reg), FIELD_PREP(mask, val) | Extract/insert a register bitfield without shifts. From <linux/bitfield.h>. Use these. |
BUILD_BUG_ON(cond), static_assert(cond) | Fail the build on a bad assumption (struct size, ABI layout) |
likely(x) / unlikely(x) | Branch hints. Only in genuinely hot paths; measure. |
READ_ONCE(x) / WRITE_ONCE(x) | Stop the compiler tearing, reordering, or re-reading. Required for lockless access. |
struct_size(p, member, n) | Overflow-safe size of a struct with a trailing flexible array |
swap(a, b) | What it says |
/* Register bitfields, before and after: */
#define CTRL_MODE GENMASK(5, 3)
val = (reg & CTRL_MODE) >> 3; /* shift constants: bug bait */
val = FIELD_GET(CTRL_MODE, reg); /* same thing, checked */
reg = (reg & ~CTRL_MODE) | (val << 3);
reg = (reg & ~CTRL_MODE) | FIELD_PREP(CTRL_MODE, val);
Coding style, in one table
Documentation/process/coding-style.rst is short, opinionated, and worth reading in full once.
checkpatch.pl enforces most of it. The parts people get wrong:
| Rule | Note |
|---|---|
| Tabs, 8 columns wide | Not spaces. If your code needs more than three levels of indentation, that is the actual message. |
| Line length | 80 columns is the guideline; up to 100 is tolerated when it genuinely helps readability. Do not split a printk string literal to fit — grep-ability wins. |
| Braces | Omitted for a single statement — but if any branch of an if/else needs them, all do |
| One declaration per line, no assignments in declarations of anything non-trivial | — |
No typedef for structs | With narrow exceptions listed in the style doc |
| Function names | lower_snake_case, prefixed with the subsystem or driver name |
| Comments | /* … */. net/ and drivers/net/ use a different block-comment opening style from the rest of the tree — match the file you are in. |
net/ also uses "reverse Christmas tree" | Local declarations sorted longest line first. Only in networking. |
| SPDX identifier on line 1 | // SPDX-License-Identifier: GPL-2.0 in .c, /* … */ in headers |
Tip: Do not fight style. Run
./scripts/checkpatch.pl --strict -f yourfile.candclang-formatwith the tree's.clang-formaton your own new code only — never reformat surrounding code in a patch that does something else. A style change mixed into a functional change is one of the fastest ways to have a patch bounced.
Validation / Self-check
- Name the kernel replacement for each of:
malloc,printf,snprintf,strlcpy,atoi,assert. - Why is
scnprintfsafer thansnprintf? Write the buggy accumulation loop from memory and fix it. - Why can kernel code not use floating point, and what does
kernel_fpu_begin()cost? - Your code builds on x86-64 and fails to link on 32-bit ARM with
undefined reference to __udivdi3. What did you write, and what should you have written? - How large is the kernel stack, and name four language features it forbids as a consequence.
- What does
CONFIG_VMAP_STACKactually change? (It does not prevent overflow.) - Explain
ERR_PTR/IS_ERR/PTR_ERRin terms of the address space. When do you needIS_ERR_OR_NULLinstead? - Give the three rules of
goto-based unwinding, and say what goes wrong when labels are numbered. - Which errno values must never reach user space, and where are they defined?
- What do
__user,__iomem,__rcu, and__percpumean, and which tool enforces them? - Why is
%phashed, when would you use%px, and what does%pesave you? - What does
container_ofcompute? Write it out as arithmetic. - Why do the kernel's lists put the node inside the object rather than allocating a node?
- What does
FIELD_GETbuy over a shift and a mask? - Name three things
sparsecatches thatgcc -Wall -Wextradoes not.
Next: Context and Atomicity — the chapter to read twice, and the one whose bugs never appear at the line that caused them.