Linux Kernel printk & Logging – Free Linux Kernel Development Course

Linux Kernel printk & Logging – Part 1 | Free Kernel Programming Course | EmbeddedPathashala

EmbeddedPathashalaFree Linux Kernel Programming Course › printk & Kernel Logging – Part 1
Linux Kernel printk & Logging – Part 1
Kernel Ring Buffer • Log Levels • dmesg • Linux 6.x
Free Linux Kernel Programming & Device Drivers Course | EmbeddedPathashala
🎯 Level
Beginner to Intermediate
🐧 Kernel
Linux 6.x (LTS)
💰 Cost
100% Free
This series: PREV_LEC NEXT_LEC
Topics Covered:
printk API Kernel Log Levels KERN_INFO / KERN_ERR Ring Buffer dmesg CONFIG_LOG_BUF_SHIFT Volatile vs Persistent Logs Kernel Module Debugging

What You Will Learn

When you write a Linux kernel module, you cannot use printf() like you would in a normal C program. The kernel runs in a completely separate world — there is no standard C library, no file descriptors, and no terminal attached. Instead, the kernel provides its own logging API called printk.

In this tutorial, you will deeply understand how printk works internally, where kernel messages go, how to control their priority using log levels, how to read them using dmesg, and the real limitations you must work around as a kernel developer. All examples are verified against Linux 6.x.

1. printk vs printf — The Core Difference

Many beginners assume printk is just a kernel version of printf. The format string syntax is similar, but everything else is fundamentally different. Let’s understand why.

When you call printf() in a user-space C program, here is what happens internally:

printf() Execution Path (User Space)
Your C Program: printf(“Hello\n”)
C Library (glibc) — libc.so
write(2) System Call
stdout → Terminal / Console

Now look at what happens when you call printk() inside a kernel module:

printk() Execution Path (Kernel Space)
Kernel Module: printk(KERN_INFO “Hello\n”)
kernel/printk/printk.c — Kernel Logging Core
① Kernel Ring Buffer
__log_buf[] in RAM
② Log File
/var/log/syslog etc.
③ Console Device
ttyS0 / VGA / UART
Key Point: printf() is a user-space library function. printk() is a kernel-space function with no dependency on any library. It writes to the kernel ring buffer and optionally to a console device — there is no stdout in the kernel.

A basic printk call in a kernel module looks like this:

#include <linux/printk.h>

/* Inside your module's init function */
printk(KERN_INFO "EmbeddedPathashala: module loaded successfully\n");

Notice that KERN_INFO and the string are not separated by a comma. They are two adjacent string literals that the C compiler automatically concatenates into one. This is intentional — the log level is embedded directly into the format string as a prefix.

2. Kernel Log Levels — Controlling Priority

Every printk call must specify a log level. The log level tells the kernel how important or urgent the message is. Think of it like email priority — you can mark an email as low importance or urgent. The kernel uses these levels to decide whether to display the message on the console immediately, or just save it quietly to the log buffer.

Linux 6.x defines 8 log levels, numbered from 0 (most critical) to 7 (most verbose):

MacroNumeric LevelWhen to Use
KERN_EMERG0System is unusable — total crash imminent
KERN_ALERT1Immediate action required by operator
KERN_CRIT2Critical hardware or software failure
KERN_ERR3Error condition — driver/subsystem error
KERN_WARNING4Warning — something unusual, not yet broken
KERN_NOTICE5Normal but significant event to record
KERN_INFO6General informational messages
KERN_DEBUG7Debug-level detail — only useful during development

These macros are defined in <linux/kern_levels.h>, which is included via <linux/printk.h>. In Linux 6.x, each macro expands to a short string like “\001” “6” — a SOH (Start of Header) byte followed by the digit. The kernel’s internal log parser strips this prefix before displaying the message.

/* Correct usage — one printk per severity */
printk(KERN_EMERG  "System is going down NOW!\n");
printk(KERN_ERR    "Failed to allocate DMA buffer\n");
printk(KERN_WARNING "Unexpected GPIO state detected\n");
printk(KERN_INFO   "BLE stack initialized on hci0\n");
printk(KERN_DEBUG  "register value = 0x%08x\n", reg_val);
Modern Shortcut Macros (Linux 6.x preferred style):
Instead of writing printk(KERN_INFO “…”), you can use level-specific wrappers that are shorter and cleaner:

pr_info("Module loaded\n");
pr_err("Failed with error %d\n", ret);
pr_warn("Buffer almost full\n");
pr_debug("Loop counter = %d\n", i);

These are the recommended style in modern kernel code. They map exactly to the corresponding KERN_* levels.

2.1 Console Log Level — What Gets Printed to the Screen

Here is something critical to understand: not all printk messages appear on the console (your terminal or serial port). The kernel maintains a console log level threshold. Only messages whose priority number is lower than (i.e., more critical than) this threshold are printed to the console in real time.

Console Log Level Threshold Example (default = 4)
KERN_EMERG (0)
✅ Printed to console + ring buffer
KERN_ERR (3)
✅ Printed to console + ring buffer
KERN_WARNING (4)
⚠️ Threshold boundary — may or may not appear
KERN_INFO (6)
🔇 Ring buffer only — NOT shown on console
KERN_DEBUG (7)
🔇 Ring buffer only — NOT shown on console

You can check and change the console log level at runtime:

# Read current log level settings
# Format: current | default | minimum | boot-time-default
$ cat /proc/sys/kernel/printk
4    4    1    7

# Change console log level to 7 (show all messages including KERN_DEBUG)
$ echo 7 > /proc/sys/kernel/printk

# Or use dmesg -n
$ dmesg -n 7

3. The Kernel Ring Buffer — Where Messages Actually Live

Every printk message is first written to a special area in kernel memory called the kernel ring buffer. This is not a regular file — it is a fixed-size circular memory buffer allocated in the kernel’s own address space when the system boots.

3.1 How the Ring Buffer Works

The ring buffer is implemented as a global character array in the kernel source at kernel/printk/printk.c. It has a fixed maximum size. Once the buffer fills up completely, new messages start overwriting the oldest ones from the beginning. This is why it is called a “ring” — it wraps around in a circle.

Kernel Ring Buffer — Circular Overwrite Behaviour
Ring Buffer
__log_buf[]
256 KB default
Older messages
Recent messages
Newest writes (wraps over oldest)
⚠️ When full, new messages silently overwrite the oldest entries

3.2 Buffer Size and Kernel Configuration

The ring buffer size is controlled by a kernel configuration option called CONFIG_LOG_BUF_SHIFT. The actual size in bytes is calculated as 2 raised to the power of this value.

/* From kernel/printk/printk.c */
#define __LOG_BUF_LEN   (1 << CONFIG_LOG_BUF_SHIFT)
static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
CONFIG_LOG_BUF_SHIFT valueBuffer SizeTypical Use
17128 KBMinimal embedded systems
18 (default)256 KBStandard desktop/server kernels
201 MBSystems with heavy kernel logging
212 MBLarge SMP / debug builds
Multi-CPU systems: The kernel also applies LOG_CPU_MAX_BUF_SHIFT on systems with many CPUs, which increases the effective buffer size so that high-frequency parallel logging from multiple cores does not overflow the buffer too quickly.

3.3 Overriding Buffer Size at Boot Time

You do not need to recompile the kernel to change the ring buffer size. You can pass a kernel boot parameter via the bootloader (GRUB, U-Boot, etc.):

# In GRUB config (/etc/default/grub), add to GRUB_CMDLINE_LINUX:
log_buf_len=4M

# The value must be a power of 2: 1M, 2M, 4M, 8M, etc.
# Apply changes:
sudo update-grub

This boot parameter overrides CONFIG_LOG_BUF_SHIFT completely at runtime, giving you a larger ring buffer without needing to rebuild the kernel.

3.4 The Two Big Limitations of the Ring Buffer

Limitation 1 — Volatile (RAM-only) storage:
The ring buffer lives in RAM. If your system crashes, panics, or loses power at any point, all messages in the buffer are permanently lost. This is a serious problem during kernel debugging because a crash often destroys the very log that would explain what caused it.
Limitation 2 — Limited size and wrap-around:
At only 256 KB by default, the ring buffer fills up quickly when you have verbose printk statements. Once it overflows, older messages are silently overwritten. You lose the early-boot messages that are often the most important for debugging driver initialization.

Both of these limitations are solved by persistent logging — covered in detail in Part 2 of this tutorial.

4. Reading Kernel Logs with dmesg

The dmesg command is the primary tool for reading the kernel ring buffer from user space. It reads the contents of /dev/kmsg (Linux 6.x) and displays all messages currently in the ring buffer.

# Show all messages in the ring buffer
$ dmesg

# Show with human-readable timestamps
$ dmesg -T

# Show only the last 20 lines
$ dmesg | tail -20

# Follow in real-time (like tail -f)
$ dmesg -w

# Filter by log level — show only errors and above
$ dmesg --level=err,crit,alert,emerg

# Clear the ring buffer (requires root)
$ sudo dmesg -C

# Show with color-coded severity
$ dmesg -L
dmesg Output Format (Linux 6.x with -T flag)
[Wed Jun 18 10:23:41 2025] ep_hello: EmbeddedPathashala: module inserted
[Wed Jun 18 10:23:41 2025] ep_hello: Driver version 1.0 — init complete
[Wed Jun 18 10:24:01 2025] ep_hello: ERROR: IRQ registration failed, ret=-16
[Wed Jun 18 10:25:10 2025] ep_hello: EmbeddedPathashala: module removed
Format: [timestamp] module_name: message

4.1 Reading /proc/kmsg directly

An alternative to dmesg is reading /proc/kmsg directly. Unlike dmesg (which reads a snapshot), /proc/kmsg is a blocking read — it waits for new messages and streams them as they arrive, similar to tail -f. Only one process can open it at a time.

# Stream kernel messages live (only one reader allowed)
$ sudo cat /proc/kmsg

# On Linux 6.x, /dev/kmsg is preferred:
$ sudo cat /dev/kmsg
Linux 6.x Tip: On modern kernels, the preferred interface for userspace log readers is /dev/kmsg. It provides structured records with metadata (timestamp, log level, facility) instead of just raw text. Systemd’s journald reads from /dev/kmsg internally.

5. Writing Effective printk Messages

Kernel logs are read by many people — maintainers, users, embedded engineers debugging crashes. Writing clear, informative messages is a skill. Here are the patterns used in production kernel code:

#include <linux/module.h>
#include <linux/printk.h>
#include <linux/init.h>

#define DRIVER_NAME "ep_uart"
#define DRIVER_VER  "1.0.0"

static int __init ep_driver_init(void)
{
    /* Always identify your module and version */
    pr_info("%s: EmbeddedPathashala UART driver v%s loaded\n",
            DRIVER_NAME, DRIVER_VER);

    /* Report errors with meaningful context */
    int ret = setup_hardware();
    if (ret < 0) {
        pr_err("%s: Hardware setup failed, errno=%d\n",
               DRIVER_NAME, ret);
        return ret;
    }

    /* Warnings for non-fatal abnormal conditions */
    if (buffer_size < MIN_BUFFER) {
        pr_warn("%s: Buffer size %d is below recommended minimum %d\n",
                DRIVER_NAME, buffer_size, MIN_BUFFER);
    }

    /* Debug info — only visible when KERN_DEBUG level is active */
    pr_debug("%s: IRQ=%d, base_addr=0x%lx\n",
             DRIVER_NAME, irq_num, base_address);

    return 0;
}
Kernel Coding Style for printk:
• Always include the module/driver name as a prefix so the log message is identifiable
• Always end with \n — the kernel does not add newlines automatically
• Never use printk without a log level — the default fallback level is KERN_WARNING, which can be confusing
• Use pr_fmt macro to set a module-wide prefix so you don’t repeat the driver name in every printk call
/* Define pr_fmt BEFORE including linux/printk.h */
/* This prefix is automatically prepended to all pr_* calls */
#define pr_fmt(fmt)  KBUILD_MODNAME ": " fmt

#include <linux/printk.h>

/* Now you don't need to manually add the module name */
pr_info("Initialized successfully\n");
/* Produces: "ep_uart: Initialized successfully" */

pr_err("DMA mapping failed: %d\n", ret);
/* Produces: "ep_uart: DMA mapping failed: -12" */

Interview Questions & Answers

Q1. What is the difference between printf and printk in Linux?
printf is a user-space C library function (from glibc) that internally uses the write() system call to write output to the stdout file descriptor, which typically connects to a terminal. printk is a kernel-space function with no dependency on any user-space library. It writes to the kernel ring buffer in RAM, and optionally to a console device and a log file. You cannot use printf inside a kernel module — there is no C library available in kernel space.
Q2. What happens when the kernel ring buffer is full?
The ring buffer is a fixed-size circular buffer. When it fills completely, new incoming printk messages overwrite the oldest entries starting from the beginning of the buffer. There is no error or warning when this happens — old log data is simply lost silently. This is why the default 256 KB size can be insufficient during intensive debugging sessions, and why persistent log files via journald or syslog are essential.
Q3. What does CONFIG_LOG_BUF_SHIFT=18 mean?
It means the kernel ring buffer size is calculated as 218 bytes = 262,144 bytes = 256 KB. The shift value acts as an exponent for a power-of-2 calculation. This is a compile-time default that can be overridden at boot time by passing log_buf_len= as a kernel boot parameter.
Q4. How do you control which kernel messages appear on the console?
The kernel maintains a console log level threshold. Only printk messages with a log level number lower than (more critical than) this threshold are printed to the console in real time. The current console log level is readable and writable via /proc/sys/kernel/printk. You can also use dmesg -n <level> to change it dynamically. All messages regardless of level are always written to the ring buffer.
Q5. What is the preferred way to write printk in Linux 6.x kernel modules?
The preferred approach is to use the pr_* convenience macros: pr_info(), pr_err(), pr_warn(), pr_debug(), etc. Combined with a module-level pr_fmt() definition, this eliminates repetitive module name prefixes and makes the code cleaner. Direct printk(KERN_INFO “…”) is still valid but the pr_* style is considered modern best practice in kernel 6.x code.
Q6. Why is the kernel ring buffer considered volatile storage?
The ring buffer is allocated in RAM (kernel virtual address space) and exists only as long as the system is running. On any power loss, system reset, kernel panic, or watchdog reset, the entire RAM content including the ring buffer is wiped. This means that the printk messages from right before a crash are typically lost unless the system has been configured with persistent logging (journald writing to disk) or a pstore/ramoops mechanism that saves the buffer to non-volatile storage.

Leave a Reply

Your email address will not be published. Required fields are marked *