R0KernelPath
module 072 / 262

Kernel Coding Style & Conventions

Master the unwritten rules that are actually written down: how the Linux kernel community formats, names, and structures code so patches get merged instead of rejected.

~6h focused study10 concepts

Overview

Every serious software project develops a culture of code. The Linux kernel's culture is one of the most refined in existence — forged over three decades, tens of thousands of contributors, and millions of lines of code that must be read, reviewed, and maintained by people who have never met. Kernel coding style is not about aesthetics; it is about communication at scale.

When Linus Torvalds wrote Documentation/process/coding-style.rst, he was not issuing arbitrary decrees. He was encoding hard-won lessons about what makes kernel code readable under pressure — when a maintainer is triaging a 3 AM bug report, when a security researcher is auditing interrupt handlers, when a new contributor is trying to understand what a 15-year-old driver actually does. Every rule in that document has a reason, and this module will teach you both the rule and the reason.

This module covers the full spectrum: whitespace and indentation (which encode control flow visually), naming conventions (which encode scope and type), the 80/100-column rule (which encodes cognitive bandwidth), the goto-for-error-cleanup pattern (which encodes correct resource management), and the hard constraints — no floating point, no userspace libc — that reflect the kernel's unique execution environment. You will also learn to use checkpatch.pl, the automated style checker that runs on every patch submitted upstream.

By the end of this module you will not just know the rules — you will understand them deeply enough to apply them correctly in novel situations, explain them to others, and recognize when a rule is being violated in code you are reviewing.

why this matters for the kernel

Style compliance is the first gate every patch must pass before a maintainer will spend time reviewing its logic. Subsystem maintainers receive dozens of patches per week; a patch that violates obvious style conventions signals that the author has not done their homework, and it gets bounced back immediately with a terse "fix your style". This is not gatekeeping for its own sake — a patch that ignores style conventions is also more likely to contain logical errors, because the author has not internalized the discipline of kernel development. Learning style is learning to think like a kernel developer.

More fundamentally, the constraints in kernel style — no libc, no floating point, specific patterns for error handling — are not arbitrary. They reflect the kernel's execution environment: code that runs with no safety net, at arbitrary privilege levels, potentially in interrupt context, on machines with no memory allocator yet initialized. Understanding why these constraints exist gives you a mental model of the kernel's execution model that will serve you for every subsequent topic in this roadmap.

Kernel Patch Lifecycle: From Edit to Merge

rendering diagram…

A patch must pass automated style checks before a maintainer will spend time on logic review. Style errors cause an immediate bounce; the cycle restarts from the fix step.

The concepts

01The 8-Space Tab: Visual Indentation as Control-Flow Signal

The most controversial rule in kernel style is also the most deliberate: indentation uses real tab characters, and each tab represents 8 spaces of visual width. This is not an accident or a legacy artifact — it is a feature.

At 8-space tab width, deeply nested code becomes visually painful. If you have an if inside a for inside a while inside a function, your code is already indented 24 characters before you write a single statement. This pain is intentional: it forces you to factor out logic into helper functions rather than nesting indefinitely. The visual signal is immediate and automatic — you cannot accidentally write five levels of nesting and not notice it.

Compare this to 2- or 4-space indentation, where you can nest six levels deep and the code still looks reasonable on screen. The cognitive cost of that nesting is hidden. With 8-space tabs, the cost is made visible.

The tab vs. spaces distinction matters for a second reason: tabs in a file are a single character. When you read a kernel source file, you know immediately whether indentation is correct by whether a line starts with a tab. Mixed spaces-and-tabs is trivially detectable by both checkpatch.pl and git diff. This makes style enforcement mechanical and unambiguous.

The practical rule: use a real \t character for each level of indentation. Never use spaces to indent. Never mix tabs and spaces. Set your editor to display tabs as 8 characters wide when reading kernel code (even if your personal projects use 4).

For continuation lines inside a statement (e.g., wrapping a long function argument list), you may use spaces after the initial tab-indentation to align with the opening parenthesis. This is the only place spaces appear in indentation.

/* Correct: real tabs (shown as --->) at 8-space width */
static int process_buffer(struct my_device *dev, u8 *buf, size_t len)
{
	int ret;
	size_t i;

	if (!dev || !buf)
		return -EINVAL;

	for (i = 0; i < len; i++) {
		ret = send_byte(dev, buf[i]);
		if (ret < 0)
			return ret;
	}

	return 0;
}

/* Wrong: spaces used for indentation */
static int process_buffer(struct my_device *dev, u8 *buf, size_t len)
{
    int ret;          /* 4 spaces — WRONG */
    if (!dev || !buf)
        return -EINVAL;  /* 8 spaces — WRONG */
}

02Naming Conventions: Descriptive, Lowercase, Underscored

Kernel naming conventions follow a consistent philosophy: names should be descriptive at the appropriate scope, and never use camelCase or Hungarian notation.

Local variables: short names are fine for locals with small scope. i, j, ret, tmp, buf, len are all idiomatic. The key insight is that a local variable name only needs to be understood in the context of a short function — the surrounding code provides meaning. Avoid encoding type information in names (no lpszBuffer, no iCount).

Functions: use lowercase_with_underscores. The name should describe what the function does, not what it is. setup_timer() is good; timerSetup() is wrong style; timer() is too vague.

Macros and constants: ALL_CAPS_WITH_UNDERSCORES. This is a near-universal C convention and the kernel follows it strictly. Macros that look like functions (i.e., are called with arguments) should still be all-caps so callers know they are macros and avoid the subtle pitfalls of macro expansion.

Struct/typedef names: the kernel uses struct foo for structures and does not typedef structs into opaque types unless there is a strong reason (function pointers in structures, for instance). This is a deliberate choice — seeing struct foo *ptr tells a reader immediately that ptr is a pointer to a structure, whereas foo_t *ptr hides that information. The kernel style guide explicitly says: do not typedef structs just to avoid writing struct.

Global symbols: should be prefixed with the subsystem name to avoid namespace pollution. usb_register(), net_device_ops, blk_queue_max_segments(). This is especially important because the kernel is a single address space — there is no linker namespace isolation between modules.

Avoid encoding the type: do not write int count_int or u8 *data_ptr. The type is right there in the declaration. Names like these add noise without adding information.

/* Good naming */
struct usb_device {
	int devnum;
	enum usb_device_state state;
	char *product;
};

static int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
{
	struct usb_hcd *hcd;
	int ret;

	if (!urb || !urb->dev)
		return -EINVAL;

	hcd = bus_to_hcd(urb->dev->bus);
	ret = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
	return ret;
}

/* Bad naming (do NOT do this in kernel patches) */
typedef struct _UsbDevice {   /* camelCase, typedef, leading underscore */
	int iDevNum;              /* Hungarian notation */
	int deviceStateEnum;      /* type encoded in name */
} UsbDevice_t;               /* _t suffix on a struct typedef */

03The 80/100-Column Rule: Cognitive Bandwidth and the Right Limit

The kernel style guide traditionally specified an 80-column line limit, and more recently acknowledged that 100 columns is acceptable for certain cases (particularly when not breaking a line produces clearer code than an awkward wrap). Understanding why these limits exist is more important than memorizing the number.

The reason for a line limit: human short-term memory and visual focus have real limits. When a line of code is very long, a reader must either scroll horizontally (breaking the reading flow entirely) or mentally track characters past the edge of their visual field. Research and decades of programmer experience converge on the same result: lines beyond ~80-100 characters require extra cognitive effort to parse. In a codebase where a reviewer might read hundreds of lines in a single sitting, this overhead accumulates.

The terminal-history argument: the 80-column limit has roots in 80-column terminals, but this is not why it is maintained today. It is maintained because: (1) side-by-side diffs in terminals and web UIs work well at 80 columns; (2) printing code for review (still done) works well at 80 columns; (3) it forces brevity and factoring.

When to break the rule: the style guide says explicitly: do NOT break a string literal to satisfy the line limit (because grep needs to find the string), do NOT break a user-visible message, and do NOT introduce an awkward line break that makes code harder to read just to satisfy the column count. The rule is a guideline, not an inviolable law. checkpatch.pl will warn but not error on lines over 80 columns.

How to wrap: when a function call has many arguments, indent the continuation line to align with the opening parenthesis. When a condition is complex, break after a logical operator (&&, ||) and indent the continuation by one extra level.

/* Good: wrapping a long function call */
ret = some_subsystem_long_function_name(device,
					    buffer, length,
					    GFP_KERNEL);

/* Good: wrapping a long condition */
if (condition_one &&
    condition_two &&
    condition_three) {
	do_something();
}

/* Good: do NOT break a string literal */
pr_err("usb_submit_urb: pipe type %d is not valid for urb %p\n",
       usb_pipetype(urb->pipe), urb);
/* Note: the string itself stays on one line even if it exceeds 80 chars */

/* Bad: break that obscures meaning */
if (unlikely(test_bit(FLAG_INITIALIZED,
    &dev->flags) && dev->
    state == STATE_READY)) {   /* confusing and unnecessary */

04Braces and Spacing: The K&R Style and Why It Reduces Visual Noise

The kernel uses a variant of K&R (Kernighan & Ritchie) brace style, with one specific difference for functions. Understanding the rule and the reason prevents the most common style rejections.

For all compound statements (if, for, while, do, switch): the opening brace goes on the same line as the statement. The closing brace goes on its own line. This is the K&R style and it saves a line of vertical space per construct, which matters when you are trying to fit a function on one screen.

Exception — functions: the opening brace of a function body goes on its own line. This is the one place where the kernel deviates from pure K&R. The reason is practical: you can grep for function definitions by searching for a line that starts with { at column 0, and you cannot confuse them with if or for blocks.

Single-statement bodies: if the body of an if, for, or while is a single statement, omit the braces. Write if (cond)\n\treturn -EINVAL; not if (cond) {\n\treturn -EINVAL;\n}. This reduces visual clutter. The exception: if the else branch has braces, the if branch should too (for symmetry).

Spaces around operators: put spaces around binary operators (+, -, *, /, =, ==, &&, etc.). Do NOT put spaces between a function name and its argument list (foo(x) not foo (x)). Do NOT put spaces between a cast and the value ((int)x not (int) x). DO put a space after keywords like if, for, while, switch, return.

No trailing whitespace: trailing spaces or tabs at the end of a line are always wrong. checkpatch.pl and git diff both flag these immediately.

/* Correct brace style */
static int my_function(int x)
{                            /* function: opening brace on its OWN line */
	if (x < 0) {            /* compound: brace on SAME line */
		pr_err("negative\n");
		return -EINVAL;
	}

	if (x == 0)              /* single-statement: NO braces */
		return 0;

	for (int i = 0; i < x; i++) {
		if (some_condition(i))
			continue;
		process(i);
	}

	return x;
}

/* Wrong examples */
if(x < 0) {     /* missing space after 'if' */
if (x < 0)
{               /* brace on new line for non-function — WRONG */
foo (x);        /* space before argument list — WRONG */

05Error Handling with goto: Clean Resource Cleanup Without Repetition

The most misunderstood rule in kernel style is the use of goto for error handling. goto has a terrible reputation in general programming — it was the subject of Dijkstra's famous 1968 letter 'Go To Statement Considered Harmful'. Yet the kernel uses it extensively and correctly. Understanding why requires understanding what problem it solves.

The problem: resource cleanup under error conditions. A typical kernel function might allocate memory, then lock a mutex, then allocate a DMA buffer, then register a device. If any step fails, the function must undo all previous steps in reverse order and return an error. Without goto, you have two bad options: (1) deeply nested if/else that makes the success path hard to follow, or (2) copy-paste cleanup code at every error site (which gets out of sync as the code evolves).

The goto solution: place cleanup labels at the end of the function in reverse order of acquisition. On an error, jump to the appropriate label. This produces a single cleanup path that is easy to read, easy to maintain, and guaranteed to run the right cleanup steps.

The critical constraint: the kernel's goto usage is strictly forward-only. You never goto backwards to retry or loop — that is what while and for are for. You never goto into the middle of a block where a variable might not be initialized. You always goto forward to a label that is further down in the function. This forward-only constraint is what makes kernel goto safe and readable.

Label naming convention: cleanup labels should describe what they undo, not where they are. Use err_free_buf:, err_put_device:, out: — not label1: or cleanup:.

This pattern appears thousands of times in kernel source. Once you recognize it, reading kernel code becomes much easier.

static int probe_device(struct platform_device *pdev)
{
	struct my_priv *priv;
	int irq;
	int ret;

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv) {
		ret = -ENOMEM;
		goto err_out;       /* nothing to clean up yet */
	}

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		ret = irq;
		goto err_free_priv; /* must free priv */
	}

	ret = request_irq(irq, my_isr, 0, "my_device", priv);
	if (ret)
		goto err_free_priv; /* must free priv */

	ret = register_device(&priv->dev);
	if (ret)
		goto err_free_irq;  /* must free irq AND priv */

	platform_set_drvdata(pdev, priv);
	return 0;

/* Cleanup labels in REVERSE order of acquisition */
err_free_irq:
	free_irq(irq, priv);
err_free_priv:
	kfree(priv);
err_out:
	return ret;
}

06No Floating Point: The FPU Context Problem

The kernel must never use floating-point arithmetic in code that runs in kernel context. This is not a matter of style preference — it is a hard correctness constraint rooted in how CPUs manage floating-point state.

The FPU context problem: modern CPUs have a floating-point unit (FPU) with its own set of registers (x87 stack, SSE/AVX registers on x86; VFP/NEON on ARM). The OS manages these registers as part of a process context — when you context-switch from process A to process B, the kernel saves A's FPU registers and restores B's FPU registers. This save/restore is expensive and is therefore done lazily — the kernel does not save FPU registers until a process actually uses the FPU.

Why kernel code cannot use the FPU naively: if kernel code uses floating-point instructions, it will corrupt the FPU registers of whatever userspace process was last running on that CPU. The userspace process has no idea this happened — it will resume with garbage in its floating-point registers, producing silent data corruption that is nearly impossible to debug.

The x86 kernel does provide a mechanism — kernel_fpu_begin() / kernel_fpu_end() — that explicitly saves and restores FPU state, allowing kernel code to use SIMD for performance-critical operations (like AES-NI crypto). But this is a narrow, explicit exception used only in performance-critical paths (crypto, checksumming), not general code.

What to use instead: for fixed-point arithmetic, scale your values by a power of 10 or 2. For example, a temperature sensor that returns millidegrees can store the value as int millidegrees rather than float degrees. The kernel has div_u64(), do_div(), and other helpers for integer division that avoids undefined behavior on division.

The compiler's role: the kernel build system passes -mno-sse, -mno-mmx, -mno-sse2, -mno-3dnow, -mno-avx (and ARM equivalents) to the compiler, which means the compiler will refuse to emit FP instructions even if you accidentally write FP code. You will get a compile error before any runtime corruption can occur.

/* Wrong: floating point in kernel */
static int calculate_load(unsigned long idle, unsigned long total)
{
	float ratio = (float)idle / total;  /* WRONG: FP in kernel */
	return (int)(ratio * 100);
}

/* Correct: fixed-point, scale by 1000 for 3 decimal places */
static int calculate_load_pct(unsigned long idle, unsigned long total)
{
	/*
	 * Returns load as integer percentage 0-100.
	 * Multiply first to avoid losing precision in integer division.
	 */
	if (total == 0)
		return 0;
	return (unsigned int)((total - idle) * 100 / total);
}

/* For cases needing sub-percent precision, scale by 1000: */
static unsigned int calculate_load_permille(u64 idle, u64 total)
{
	if (total == 0)
		return 0;
	return (unsigned int)(div64_u64((total - idle) * 1000, total));
}

07No Userspace libc: The Kernel's Own Standard Library

Kernel code cannot use any function from the C standard library (<stdio.h>, <stdlib.h>, <string.h>, <math.h>, etc.) — not because of a style rule, but because these functions do not exist in kernel space. The C standard library is a userspace library built on top of system calls; in the kernel, there is no layer beneath you to call.

Why libc does not exist in the kernel: printf() calls write(), which is a system call. In the kernel, you are the thing that handles system calls. There is no one to call. malloc() uses mmap() or brk() — again, system calls. exit() calls the exit_group system call. None of these can work from kernel context.

What the kernel provides instead: the kernel has its own implementations of common operations:

  • Memory: kmalloc(), kzalloc(), vmalloc(), kfree() (not malloc/free)
  • Strings: strlen(), strcpy(), strncpy(), strlcpy(), snprintf(), sscanf() — these are reimplemented in lib/string.c with the same signatures as libc, but as kernel functions. Do not confuse availability with origin: the kernel has its own strlen() declared in <linux/string.h>, not from libc.
  • Printing/logging: printk(), pr_info(), pr_err(), dev_err() (not printf())
  • Math: integer arithmetic only; <linux/math64.h> for 64-bit operations
  • Memory copy: memcpy(), memset(), memmove() — provided by the kernel, may be implemented with architecture-specific assembly for performance

The include discipline: in kernel code, you include <linux/kernel.h>, <linux/string.h>, <linux/slab.h>, etc. — never <stdlib.h>, <stdio.h>, or any other standard C header. If you accidentally include a libc header, the build will fail with errors about undefined symbols or conflicting declarations.

Why this matters for your mental model: the absence of libc is a constant reminder that the kernel is not an application. It is the foundation on which applications run. Every service you take for granted in userspace — dynamic memory, formatted output, file I/O — is something the kernel provides to userspace, not something available to the kernel itself.

/* WRONG: using libc in kernel code */
#include <stdio.h>    /* WRONG */
#include <stdlib.h>   /* WRONG */
#include <string.h>   /* WRONG — use <linux/string.h> */

void bad_kernel_code(void)
{
	char *buf = malloc(256);   /* WRONG: use kmalloc */
	printf("hello\n");         /* WRONG: use pr_info or printk */
	free(buf);                 /* WRONG: use kfree */
}

/* CORRECT: kernel equivalents */
#include <linux/kernel.h>   /* pr_info, KERN_INFO, etc. */
#include <linux/slab.h>     /* kmalloc, kfree, kzalloc */
#include <linux/string.h>   /* strlen, strncpy, etc. (kernel versions) */
#include <linux/printk.h>   /* printk levels */

void good_kernel_code(void)
{
	char *buf;

	buf = kmalloc(256, GFP_KERNEL);
	if (!buf)
		return;  /* -ENOMEM handled by caller in real code */

	snprintf(buf, 256, "value=%d\n", 42);
	pr_info("%s", buf);
	kfree(buf);
}

08checkpatch.pl: Automated Style Enforcement

scripts/checkpatch.pl is a Perl script that ships with the Linux kernel source tree. It analyzes patches (in unified diff / git format-patch format) or source files and reports style violations. Every patch submitted to a Linux mailing list is expected to pass checkpatch.pl with no errors, and minimal warnings.

How to run it: the canonical way is to run it on a patch file generated by git format-patch:

git format-patch HEAD~1   # generates 0001-my-change.patch
./scripts/checkpatch.pl 0001-my-change.patch

You can also run it directly on a source file (less precise, but useful for learning):

./scripts/checkpatch.pl --no-tree -f drivers/my/driver.c

The --no-tree flag tells it not to try to infer context from the full kernel tree.

What it checks: trailing whitespace, indentation with spaces instead of tabs, lines over 80 characters (warning) or 100 characters (error in some subsystems), missing blank lines after declarations, printk() without a log level, open-coded arithmetic that should use kernel macros, suspicious use of typedef, missing __user annotations on userspace pointers, use of deprecated APIs, and dozens more.

Understanding the output levels:

  • ERROR: — definite violation, must be fixed before submission
  • WARNING: — probable violation, should be fixed (maintainer may require it)
  • CHECK: — minor suggestion, at author's discretion

Integration into your workflow: run checkpatch.pl before every git send-email. Many developers set up a git pre-commit hook to run it automatically. Some CI systems (like kernel.org's KernelCI) run it on every pull request. Treat its output as the first reviewer of your patch.

Limitations: checkpatch.pl is not perfect. It has false positives (it may flag correct code) and false negatives (it does not catch logic errors or all style violations). It is a necessary but not sufficient condition for a well-formatted patch. Maintainer review catches what checkpatch.pl misses.

# Generate a patch from your last commit
git format-patch HEAD~1

# Run checkpatch on it
./scripts/checkpatch.pl 0001-my-change.patch

# Example output:
# WARNING: line over 80 characters
# #42: FILE: drivers/my/driver.c:127:
# +	dev_err(&pdev->dev, "very long error message that goes well past eighty columns here\n");
#
# ERROR: code indent should use tabs where possible
# #68: FILE: drivers/my/driver.c:153:
# +    if (ret < 0)      <-- spaces used instead of tab
#
# total: 1 errors, 1 warnings, 45 lines checked

# Run on a file directly (useful during development)
./scripts/checkpatch.pl --no-tree --terse -f drivers/my/driver.c

# Check all commits in a topic branch against main
git format-patch main..HEAD | xargs ./scripts/checkpatch.pl

09Comments: What to Write, What to Never Write

The kernel has a specific philosophy about comments that differs from many style guides: comment the WHY, never the WHAT. A comment that says /* increment i */ above i++ is worse than no comment — it is noise that makes reading slower, and it will inevitably become incorrect as the code evolves.

Multi-line comments: use /* ... */ style, not // C99 single-line comments. The kernel was written in C89 for a long time, and multi-line comment style is still standard. The comment block format should look like:

/*
 * This is a multi-line comment explaining why
 * this function does something non-obvious.
 */

Do not document obvious things: /* lock the mutex */ above mutex_lock(&lock) is useless. But /* Must hold dev_lock before calling — see comment in probe() */ above mutex_lock(&dev->lock) is valuable, because it documents a non-obvious constraint.

Document data structures: struct fields that have non-obvious semantics, units, or constraints should be commented. int temp_mc; /* temperature in millidegrees Celsius */ is exactly the right level of documentation for a field that could be misinterpreted.

Kernel-doc: functions exported from a subsystem should use kernel-doc format — a /** comment immediately before the function definition with @param, @return descriptions. This is parsed by make htmldocs to generate the kernel documentation. Kernel-doc is required for exported symbols (functions declared in headers). For static/internal functions, regular comments suffice.

Do not use comments to disable code: never leave /* foo(); */ commented-out code in a patch. Remove the dead code. The git history preserves it if you need it back.

/**
 * usb_submit_urb - issue an asynchronous transfer request for an endpoint
 * @urb: pointer to the urb describing the request
 * @mem_flags: the type of memory to allocate, see kmalloc() for a full list
 *             for "normal" HCD-specific allocations use GFP_ATOMIC,
 *             for "normal" non-HCD allocations use GFP_KERNEL.
 *
 * This submits a transfer request, and transfers control of the URB
 * describing that request to the USB subsystem. Request completion will
 * be indicated later, asynchronously, by calling the completion handler.
 * The three types of completion are success, error, and unlink
 * (a software-induced fault, also called "request cancellation").
 *
 * Return: 0 on successful submission. A negative error number otherwise.
 */
int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
{
	/* ... */
}

/* Good inline comment: explains non-obvious constraint */
static void process_event(struct work_struct *work)
{
	struct my_dev *dev = container_of(work, struct my_dev, event_work);

	/*
	 * We must re-enable interrupts before calling into the hardware,
	 * because the firmware expects to deliver a completion interrupt
	 * synchronously during the command. See firmware spec section 4.2.
	 */
	enable_irq(dev->irq);
	hw_send_command(dev);
}

10Function Length and Complexity: The One-Screen Rule

The kernel style guide states that functions should be short — ideally fitting on one or two screens — and should do one thing. This is not a rigid character-count rule but a strong cultural norm backed by reasoning.

Why short functions: a function that fits on one screen can be understood in its entirety without scrolling. You can see its inputs, its outputs, and all the paths it can take. You can audit it for correctness without losing context. A 400-line function that handles initialization, data processing, error recovery, and cleanup all in one place is a maintenance nightmare — each path interacts with every other path, and a change anywhere can break anything.

The one-variable-per-declaration rule: declare one variable per line, not int i, j, k;. This makes the code easier to read, easier to annotate with comments, and easier to debug (you can see which variable is which in a debugger without squinting).

Inline functions: use static inline for short functions that are called in hot paths. Never write inline alone — it is a hint to the compiler but without static it affects symbol visibility. Do not use inline for anything more than a few lines; the kernel generally respects the compiler's judgment about inlining for longer functions.

The 80% of variables at top rule: the kernel style (from C89 roots) prefers declaring all local variables at the beginning of a block, not inline where they are first used (C99 style). While modern kernel code increasingly allows mid-block declarations (since the kernel switched to requiring C11), the traditional and still-common style is variables-at-top. New code in modern subsystems (bpf, io_uring) uses C11 mid-block declarations freely, but do not mix styles within a file.

/* Good: short, focused function */
static bool device_is_ready(struct my_device *dev)
{
	return dev->state == DEV_STATE_READY &&
	       !test_bit(FLAG_ERROR, &dev->flags);
}

/* Good: all locals at top (traditional kernel style) */
static int configure_device(struct my_device *dev, struct config *cfg)
{
	unsigned long flags;
	int ret;
	u32 reg;

	if (!dev || !cfg)
		return -EINVAL;

	spin_lock_irqsave(&dev->lock, flags);
	reg = read_register(dev, REG_CONTROL);
	reg |= cfg->enable_mask;
	write_register(dev, REG_CONTROL, reg);
	spin_unlock_irqrestore(&dev->lock, flags);

	ret = wait_for_completion_timeout(&dev->ready, HZ);
	if (!ret)
		return -ETIMEDOUT;

	return 0;
}

Key insights

  • ✦The 8-space tab is a feature, not a quirk: it makes over-nesting visually painful, which forces better factoring. Pain is the signal.
  • ✦The goto-for-error-cleanup pattern is the safest way to handle multi-resource acquisition in C — it produces a single, auditable cleanup path that is immune to copy-paste drift.
  • ✦No floating point and no libc are not style choices — they are hard correctness constraints imposed by the kernel's execution environment. Understanding them gives you a mental model of what 'kernel context' actually means.
  • ✦checkpatch.pl is your first reviewer. A patch that fails checkpatch will not get a human review; fix the machine problems before asking for human attention.
  • ✦Comment the WHY, never the WHAT. Code explains itself; only the reasoning behind non-obvious choices needs annotation.
  • ✦The 80-column rule is about cognitive bandwidth, not terminal width. A line limit forces brevity, which forces clarity.
  • ✦Kernel naming conventions (lowercase_underscore, no typedef for structs, subsystem-prefixed globals) reflect a global namespace with no isolation — every symbol in the kernel competes with every other symbol.

Common pitfalls

  • ⚠Using spaces for indentation instead of tabs. Your editor probably defaults to 4-space indent; override it for kernel work. checkpatch.pl will catch this immediately.
  • ⚠Forgetting the function-brace exception: the opening brace of a function body goes on its own line, but the brace of an if/for/while goes on the same line as the statement.
  • ⚠Writing cleanup code at every error site instead of using goto labels. This produces divergent cleanup paths that get out of sync during maintenance.
  • ⚠Including libc headers (<stdlib.h>, <stdio.h>) in kernel code. The build will fail, but the error messages can be confusing — trace them back to the bad include.
  • ⚠Using floating-point in a context that looks safe but runs in an interrupt handler or with preemption disabled. The FPU state corruption is silent and catastrophic.
  • ⚠Commenting the obvious ('increment counter') while leaving the non-obvious unexplained ('must be called with dev->lock held'). This is backwards from what reviewers need.
  • ⚠Running checkpatch.pl on the source file directly and missing context-dependent warnings that only appear when checking an actual patch.

Practice

  1. 01
    easy

    Clone the Linux kernel source (or use a mirror at github.com/torvalds/linux). Find any driver in drivers/char/ or drivers/misc/ that is fewer than 500 lines. Read it from top to bottom. For each style rule in this module, find at least one concrete example of that rule being applied in the file. Write down the line numbers and what rule each demonstrates.

  2. 02
    medium

    Write a kernel-style C function (compilable outside the kernel with stubbed types) that opens a hypothetical device: it allocates a private struct with kzalloc, requests two IRQs, registers a character device, and adds the device to a bus. Use the goto-for-cleanup pattern correctly. Then deliberately introduce three style violations (e.g., use spaces for indentation, add a floating-point variable, use printf). Run checkpatch.pl --no-tree -f yourfile.c and confirm it catches all three violations.

  3. 03
    medium

    Find a real open-source Linux driver on GitHub (search for 'linux driver platform_driver' in a recent repository that is NOT in the mainline kernel). Run checkpatch.pl on it. Document every ERROR and WARNING it produces. For each finding, explain in your own words: (a) what rule is violated, (b) why that rule exists, and (c) what the correct code would look like.

  4. 04
    hard

    Implement a kernel-style fixed-point temperature conversion module (plain C, no kernel build needed). The module should: (1) store temperature as integer millidegrees Celsius, (2) provide conversion functions between mC, Fahrenheit-millidegrees, and Kelvin-millidegrees, (3) use no floating point whatsoever, (4) follow all kernel naming and style conventions. Verify with checkpatch.pl --no-tree.

Resources

📄Linux Kernel Coding Style (official documentation)
docsfree

The primary source — Linus Torvalds' original document, updated over the years. Read it first, then come back after this module to appreciate the nuances.

📄Submitting Patches: the essential guide to getting your code into the kernel
docsfree

Covers the full patch lifecycle that checkpatch.pl fits into. Essential context for why style matters in the submission process.

✎KernelNewbies: First Kernel Patch
articlefree

Step-by-step walkthrough of creating and submitting your first patch, including checkpatch.pl usage in real context.

📕Linux Device Drivers, 3rd Edition (Corbet, Rubini, Kroah-Hartman)
bookfree

The classic reference — chapters on driver structure show kernel style in real, working code. Free PDF available from LWN.

✎LWN.net: Kernel development articles
articlefree

The canonical source for kernel development news, policy discussions, and deep technical articles. Style and process discussions appear regularly.

📄checkpatch.pl source and documentation
docsfree

Official documentation for checkpatch.pl flags and output interpretation. Read this alongside the tool itself.

🎓Bootlin Kernel Development Training Materials
coursefree

Free slide decks from professional Linux kernel training; the coding style and patch submission sections are excellent visual references.

📕The C Programming Language, 2nd Edition (Kernighan & Ritchie)
bookpaidPrentice Hall, by Brian W. Kernighan and Dennis M. Ritchie

The origin of K&R brace style. Understanding where the conventions came from makes them easier to remember and apply.

Checkpoints — you can now…

  • ✓You can open any kernel C file, read it fluently, and identify at least 5 style conventions being applied (tab width, brace placement, naming, comment style, goto cleanup).
  • ✓You can write a kernel-style function from scratch — with correct indentation, brace placement, error-handling goto pattern, and kernel API calls — without referring to notes.
  • ✓You can run checkpatch.pl on a patch or source file, interpret every ERROR and WARNING in its output, and fix each one correctly.
  • ✓You can explain to another programmer why the kernel forbids floating-point in kernel context, what the FPU state corruption problem is, and how to replace FP arithmetic with fixed-point.
  • ✓You can explain why goto is used for error cleanup in the kernel (and not for any other purpose), and write a multi-resource acquisition function with the correct cleanup label pattern.
  • ✓You can name the kernel equivalents of malloc, free, printf, strlen, and memcpy, and explain why the libc versions cannot be used.