Reading Linux Kernel Logs: dmesg, journalctl & Rate Limiting – Free Linux Kernel Development Course

Series: PREV_LEC NEXT_LEC
Reading Kernel Logs: dmesg, journalctl and Rate Limiting
Free Linux Kernel Programming Course — EmbeddedPathashala
⏱ 20 min read
🐧 Linux 6.x
🆓 Free Course
Topics Covered
dmesg command journalctl -k kernel logs /proc/sys/kernel/printk console_loglevel pr_err_ratelimited kernel ring buffer linux kernel programming free course

You have written a kernel module and called pr_info(). Now how do you actually read those messages? This part covers the two main tools — dmesg and journalctl — along with the four tunables in /proc/sys/kernel/printk that control what you see. We also cover rate limiting, which prevents a misbehaving driver from flooding the log and making your system unusable.

The Kernel Ring Buffer — Where All Messages Live

Every printk() call — regardless of log level — writes to the kernel ring buffer. This is a fixed-size circular buffer kept in kernel memory. When it fills up, the oldest messages are overwritten. On Linux 6.x the ring buffer is fully lockless, meaning messages can be written even from non-maskable interrupt (NMI) context without any risk of deadlock.

The buffer size is set at build time by CONFIG_LOG_BUF_SHIFT. The default is usually 17, giving a 128 KB buffer. You can increase it at boot time with the kernel parameter log_buf_len=4M if you need more history.

Userspace reads the ring buffer through the character device /dev/kmsg. Both dmesg and journalctl -k ultimately read from this device.

Kernel Ring Buffer — How Messages Are Written and Read
Module code: pr_err()
IRQ handler: pr_warn()
Kernel subsystem: pr_info()
Kernel Ring Buffer ( /dev/kmsg )
Lockless • Always written • Circular • Config: LOG_BUF_SHIFT
dmesg
reads /dev/kmsg
journalctl -k
via systemd-journald
Console device
if level < console_loglevel

The Four Values in /proc/sys/kernel/printk

Reading this file gives you four integers on one line:

$ cat /proc/sys/kernel/printk
7    4    1    7

Each number has a specific meaning:

PositionNameMeaningTypical Value
1st console_loglevel Only messages with a level number less than this value appear on the console. Messages at this level and above are suppressed from the console (but still go to the ring buffer). 7
2nd default_message_loglevel The level assigned to a printk() call that has no explicit level prefix. 4 (WARNING)
3rd minimum_console_loglevel The lowest value you are allowed to set console_loglevel to. Prevents you from making the console completely silent below this floor. 1
4th default_console_loglevel The value console_loglevel resets to on boot. Not often changed. 7
console_loglevel = 7 — What Reaches the Console vs What Is Suppressed
Reaches Console ✔
0 — KERN_EMERG
1 — KERN_ALERT
2 — KERN_CRIT
3 — KERN_ERR
4 — KERN_WARNING
5 — KERN_NOTICE
6 — KERN_INFO
Suppressed from Console ✘
7 — KERN_DEBUG (level 7 is NOT < 7)
To see debug messages on console, set console_loglevel to 8:
echo 8 > /proc/sys/kernel/printk

dmesg — Reading the Ring Buffer

dmesg is the standard command to read kernel messages. On modern systems it reads directly from /dev/kmsg. Here are the most useful options for kernel developers:

CommandWhat It Does
dmesg Print all messages in the ring buffer with raw timestamps (seconds since boot)
dmesg -T Human-readable timestamps like [Wed Jun 25 09:12:04 2025] instead of [12.345678]
dmesg -w Follow mode — wait for and print new messages as they arrive (like tail -f)
dmesg -wT Follow mode with human-readable timestamps — most useful during module development
dmesg -l err Show only KERN_ERR messages
dmesg -l err,crit,emerg Show only errors and above — quick triage for serious problems
dmesg -l debug Show only KERN_DEBUG messages
dmesg -x Prefix each line with facility and level name (e.g. kern :err 🙂
dmesg -H Human mode — combines -T, colors by level, and pipes through pager
dmesg -n 8 Set console_loglevel to 8 (same as echo 8 > /proc/sys/kernel/printk)
dmesg -C Clear the ring buffer (root only)

Practical workflow when testing a module load:

# Terminal 1: watch the log live with timestamps
dmesg -wT

# Terminal 2: load your module
sudo insmod ep_uart.ko

# You will see your pr_info/pr_err messages appear in Terminal 1 immediately
Permission error? If dmesg returns “Operation not permitted”, the system has kernel.dmesg_restrict=1. Run with sudo dmesg or read logs via sudo journalctl -k. Many modern distributions (Ubuntu, Debian, Fedora) enable this by default for security.

journalctl — Kernel Logs via systemd

On any system running systemd, systemd-journald collects kernel messages alongside service and application logs. You query all of it through journalctl. The advantage over dmesg is that journal logs persist across reboots — essential for debugging crashes.

# Show all kernel messages from the current boot
journalctl -k

# Same thing, written out:
journalctl --dmesg

# Kernel messages from the previous boot — invaluable after a crash
journalctl -k -b -1

# List all stored boot sessions
journalctl --list-boots

# Follow kernel messages live (like dmesg -w)
journalctl -k -f

# Show only error level and above (levels 0-3)
journalctl -k -p err

# Show a range: warning through alert (levels 1-4)
journalctl -k -p warning..alert

# Filter by time range
journalctl -k --since "1 hour ago"
journalctl -k --since "2025-06-25 09:00:00" --until "2025-06-25 10:00:00"

# Combine filters: kernel errors in the last hour
journalctl -k -p err --since "1 hour ago"

# Follow messages from a specific service
journalctl -f -u apache2.service
dmesg vs journalctl — When to Use Which
dmesg
✔ Fast, minimal overhead
✔ No systemd dependency
✔ Works on embedded / minimal systems
✔ Best for live module testing
✘ Lost on reboot
✘ No service log mixing
✘ Restricted by dmesg_restrict
journalctl -k
✔ Persists across reboots
✔ Mix kernel + service logs
✔ Rich time and priority filtering
✔ Best for crash post-mortem
✘ Requires systemd
✘ Slightly more overhead
✘ Not on all embedded distros

Live Monitoring with journalctl -f

The -f flag on journalctl works like tail -f — it continuously prints new messages as they arrive. This is very useful during driver development to watch what your module produces in real time:

# Watch all kernel messages live
journalctl -k -f

# Watch a specific service (e.g. your custom daemon) live
journalctl -f -u my-driver-daemon.service

# Watch live AND filter by level — only warnings and above
journalctl -k -f -p warning

Rate Limiting — Preventing Log Floods

Imagine a driver that detects a hardware error 10,000 times per second and calls pr_err() each time. The kernel log fills up instantly, the console freezes trying to scroll text, and the system may become unresponsive. Rate limiting solves this by capping how many messages a call site can produce in a time window.

The Modern Way: pr_*_ratelimited()

Each of the pr_* macros has a rate-limited version. The naming pattern is pr_LEVEL_ratelimited():

pr_emerg_ratelimited(fmt, ...)
pr_alert_ratelimited(fmt, ...)
pr_crit_ratelimited(fmt, ...)
pr_err_ratelimited(fmt, ...)
pr_warn_ratelimited(fmt, ...)
pr_notice_ratelimited(fmt, ...)
pr_info_ratelimited(fmt, ...)

The default rate limit per call site is 10 messages per 5 seconds. Once exceeded, messages are silently dropped until the window resets. After the window resets and the next message goes through, the kernel automatically prints how many messages were suppressed:

/* Example usage in an IRQ error path */
static irqreturn_t ep_uart_irq(int irq, void *dev_id)
{
    if (hw_error_detected()) {
        /* Safe in hot path — will not flood the log */
        pr_err_ratelimited("RX FIFO overflow on port %d\n", port_num);
        clear_hw_error();
    }
    return IRQ_HANDLED;
}

/* What you see in dmesg when the error fires 1000 times in 5 seconds: */
/* [  45.123] ep_uart: RX FIFO overflow on port 0   (message 1) */
/* [  45.124] ep_uart: RX FIFO overflow on port 0   (message 2) */
/* ...up to message 10...                                        */
/* [  50.001] ep_uart: 990 callbacks suppressed      (auto summary) */
/* [  50.001] ep_uart: RX FIFO overflow on port 0   (window reset) */
How Rate Limiting Works Per Call Site
pr_err_ratelimited(“RX overflow\n”) called
Is count < burst limit (10) within window (5 sec)?
YES — allow
Message written
to ring buffer + console
NO — suppress
Message dropped
suppress counter++
At next window reset: kernel auto-prints “N callbacks suppressed”

Print Only Once: pr_*_once()

For messages that should appear at most one time for the lifetime of the running kernel, use the _once variants:

pr_err_once("legacy hardware detected, some features disabled\n");
pr_warn_once("firmware version is outdated, consider upgrading\n");

These use a static boolean to ensure the message fires exactly once per boot, no matter how many times the code path is reached.

Which to use? Use pr_err_ratelimited() for errors in interrupt handlers, timer callbacks, or any path that can be called many times per second. Use pr_err_once() for one-time capability or hardware detection messages. Use plain pr_err() only in paths that cannot possibly be called frequently, such as module init and exit.

Quick Reference: Common Developer Workflows

# ===== DURING MODULE DEVELOPMENT =====

# 1. Open a terminal and follow the kernel log live
dmesg -wT

# 2. In another terminal, load your module
sudo insmod my_module.ko

# 3. See your pr_info/pr_err output appear immediately in terminal 1

# 4. Remove module
sudo rmmod my_module

# ===== AFTER A SYSTEM CRASH OR HANG =====

# Show kernel messages from the boot before the last one
journalctl -k -b -1

# Show only errors from that boot
journalctl -k -b -1 -p err

# ===== ADJUSTING CONSOLE OUTPUT =====

# Show debug messages on console (temporary)
sudo echo 8 > /proc/sys/kernel/printk

# Quiet the console (show only errors and above)
sudo echo 4 > /proc/sys/kernel/printk

# Restore default
sudo echo 7 > /proc/sys/kernel/printk

# ===== FILTER MESSAGES FROM YOUR MODULE =====

# dmesg: grep for your module name
dmesg | grep ep_uart

# journalctl: filter by syslog identifier
journalctl -k | grep ep_uart

Interview Questions — dmesg, journalctl and Rate Limiting

Q1. What is the kernel ring buffer and where does it live?
The kernel ring buffer is a fixed-size circular buffer allocated in kernel memory that stores all printk() messages. It lives in kernel virtual address space. Its size is configured by CONFIG_LOG_BUF_SHIFT at build time (default 128 KB) and can be increased with the log_buf_len= boot parameter. Userspace reads it through /dev/kmsg.
Q2. What does the command “cat /proc/sys/kernel/printk” show and what do the four numbers mean?
It shows four integers on one line. In order: (1) console_loglevel — only messages with a level number strictly less than this value appear on the console; (2) default_message_loglevel — the level given to a printk() with no explicit level; (3) minimum_console_loglevel — the floor below which you cannot lower console_loglevel; (4) default_console_loglevel — the value console_loglevel resets to at boot.
Q3. How do you see kernel messages from the boot before the last one (e.g. after a crash)?
Use journalctl -k -b -1. The -b -1 means “previous boot”. For the boot before that, use -b -2. You can list all available boot sessions with journalctl --list-boots. This only works if journald is configured to persist logs across reboots (Storage=persistent in journald.conf).
Q4. What is the difference between dmesg and journalctl -k?
Both show kernel messages backed by the same ring buffer. The key differences are: (1) dmesg shows only the current boot’s buffer, which is lost on reboot; journalctl -k can show logs from previous boots. (2) journalctl integrates kernel logs with service and application logs in one tool. (3) dmesg requires no systemd and works on minimal or embedded systems. For live module testing during development, dmesg -wT is faster. For post-crash investigation, journalctl -k -b -1 is essential.
Q5. Why should you use pr_err_ratelimited() in an interrupt handler instead of pr_err()?
An interrupt handler can fire thousands of times per second. If a hardware error occurs repeatedly, using plain pr_err() would flood the kernel ring buffer, fill any log files, and potentially freeze the console scrolling on the terminal. pr_err_ratelimited() limits output to 10 messages per 5 seconds per call site, keeping the system stable and the log readable. After suppression it also automatically prints how many messages were dropped.
Q6. How do you filter dmesg to show only error messages and above?
Use dmesg -l err,crit,alert,emerg or more concisely dmesg -l err — however note that -l err shows only exactly KERN_ERR, not levels above it. To get all levels 0 through 3, list them explicitly: dmesg -l emerg,alert,crit,err. With journalctl it is simpler: journalctl -k -p err means “err and more severe” (levels 0–3) automatically.
Q7. What does “N callbacks suppressed” mean in the kernel log?
It is an automatic message printed by the rate limiting mechanism. When a rate-limited call site (pr_err_ratelimited() etc.) suppresses messages because the burst limit was exceeded, it counts how many were dropped. At the next window reset when a message is allowed through, the kernel prepends a line saying how many messages were suppressed during the previous window. This tells you a flood occurred without preserving every duplicate message.
Q8. How would you temporarily make all kernel debug messages appear on the console?
Run echo 8 > /proc/sys/kernel/printk as root (or sudo sysctl -w kernel.printk=8). Setting the console_loglevel to 8 means messages at levels 0 through 7 (all levels including DEBUG) will appear on the console. Remember to also enable pr_debug() call sites via the dynamic debug control file if CONFIG_DYNAMIC_DEBUG is set. This change is temporary and reverts on reboot.
🎉 Series Complete — printk Logging in Linux Kernel

You now understand log levels, pr_ macros, pr_fmt(), dynamic debug, the console loglevel, dmesg, journalctl, and rate limiting. These skills are used every day in Linux kernel and device driver development.

More Free Kernel Courses →

Leave a Reply

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