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.
Lockless • Always written • Circular • Config: LOG_BUF_SHIFT
reads /dev/kmsg
via systemd-journald
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:
| Position | Name | Meaning | Typical 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 |
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:
| Command | What 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
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
✔ No systemd dependency
✔ Works on embedded / minimal systems
✔ Best for live module testing
✘ Lost on reboot
✘ No service log mixing
✘ Restricted by dmesg_restrict
✔ 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) */
to ring buffer + console
suppress counter++
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.
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
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.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.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).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.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.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.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.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.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 →