free linux device drivers course
free embedded systems course
printk
dmesg
Throughout this free linux kernel development course you have been reading kernel boot logs to debug builds, panics, and init sequences. This lecture finally explains the structure behind those messages: the severity level system the kernel uses for every line it prints, why that system exists, and how to use it correctly in your own driver code.
What You Will Learn
- Why the kernel categorizes every log message by numeric severity
- The full table of
KERN_*levels and what each one actually means - How the modern
pr_*helper macros map onto those same levels - How to filter and control which levels reach your console versus the full log buffer
- A small original driver snippet demonstrating correct level usage
Prerequisites
- A booting kernel you can inspect with
dmesg, from earlier lectures in this series - Basic C familiarity for the driver code example
Why Kernel Messages Need Severity Levels
The kernel and its drivers print an enormous volume of diagnostic text — everything from routine informational notes to genuinely fatal errors. If every one of those lines were treated equally, a serious hardware failure could scroll past unnoticed among hundreds of harmless status messages. To solve this, every kernel log message carries a numeric priority, and both the console and tools like dmesg can filter or highlight messages based on that priority instead of treating the log as one undifferentiated stream.
The Full Severity Table
These levels are defined as the KERN_* macros, ordered from most to least severe. Lower numeric values mean higher severity — level 0 is the most urgent message the kernel can print.
| Level | Value | Meaning |
|---|---|---|
KERN_EMERG |
0 | The system is unusable |
KERN_ALERT |
1 | Action must be taken immediately |
KERN_CRIT |
2 | Critical conditions |
KERN_ERR |
3 | Error conditions |
KERN_WARNING |
4 | Warning conditions |
KERN_NOTICE |
5 | Normal but significant conditions |
KERN_INFO |
6 | Informational messages |
KERN_DEBUG |
7 | Debug-level messages |
1 ALERT ██████████████████
2 CRIT ████████████████
3 ERR ██████████████
4 WARNING ████████████
5 NOTICE ██████████
6 INFO ████████
7 DEBUG ██████ least severe
From KERN_* Macros To pr_* Helpers
Older kernel code, and the raw printk() API itself, expects you to prepend the level macro directly to your format string, like printk(KERN_ERR "device failed to probe\n"). Modern kernel code almost universally prefers the shorter pr_* family of helper macros, which wrap printk() and bake the level in for you, producing identical output with less boilerplate.
| Modern Helper | Equivalent To |
|---|---|
pr_emerg() |
printk(KERN_EMERG ...) |
pr_alert() |
printk(KERN_ALERT ...) |
pr_crit() |
printk(KERN_CRIT ...) |
pr_err() |
printk(KERN_ERR ...) |
pr_warn() |
printk(KERN_WARNING ...) |
pr_notice() |
printk(KERN_NOTICE ...) |
pr_info() |
printk(KERN_INFO ...) |
pr_debug() |
printk(KERN_DEBUG ...), compiled out unless debugging is enabled |
Try It: Correct Level Usage In A Driver
Here is a small original probe function that demonstrates picking the right level for each situation — not defaulting everything to the same severity, which is a very common beginner mistake:
#include <linux/module.h>
#include <linux/platform_device.h>
static int ep_sensor_probe(struct platform_device *pdev)
{
int ret;
pr_info("ep_sensor: probing device %s\n", pdev->name);
ret = ep_sensor_read_id(pdev);
if (ret == -ENODEV) {
pr_warn("ep_sensor: no device present, deferring\n");
return -EPROBE_DEFER;
}
if (ret < 0) {
pr_err("ep_sensor: failed to read chip id: %d\n", ret);
return ret;
}
pr_debug("ep_sensor: chip id read as 0x%02x\n", ret);
return 0;
}
Notice the deliberate choices here: a routine “starting up” message is pr_info, a recoverable condition where the driver will simply retry later is pr_warn rather than an error, a genuine failure is pr_err, and a value only relevant while actively debugging is pr_debug so it does not clutter normal boot logs at all.
Viewing And Filtering Messages With dmesg
All kernel messages, regardless of level, are collected into the kernel’s ring buffer, which dmesg reads. You can filter what you see by level directly:
$ dmesg --level=err,crit,alert,emerg
$ dmesg -l warn
Separately, the console itself has its own threshold, controlled by /proc/sys/kernel/printk, which determines which levels are actually printed to your active console versus only stored in the ring buffer for later inspection with dmesg. A message can exist in the log without ever appearing on your serial console if its level is below that console threshold — a frequent source of confusion when a pr_debug line “does not show up” even though the driver clearly executed that line.
Common Mistakes And Troubleshooting
- Using pr_err for routine status messages: this trains you to ignore real errors, since the log fills with false alarms.
- Expecting pr_debug output without enabling debugging:
pr_debugis compiled out entirely unlessDEBUGis defined for that file or dynamic debug is enabled, so “missing” debug lines are usually working as intended. - Confusing the console threshold with the ring buffer: a message can be sitting safely in
dmesgoutput even if you never saw it scroll past on the console in real time. - Flooding the log from an interrupt handler: uncontrolled
printkcalls in a hot path can itself cause timing problems; use rate-limited variants likepr_err_ratelimitedfor anything that can fire frequently.
Best Practices
- Match the level to the actual severity of the condition, not to how interesting the message feels to you while writing the driver.
- Prefer the modern
pr_*helpers over rawprintk(KERN_* ...)for new code — they are shorter and less error-prone. - Use rate-limited variants for anything that could fire repeatedly in a short time, such as messages inside interrupt handlers or polling loops.
- Reserve
pr_debugfor information only useful while actively debugging that specific subsystem, so production logs stay readable.
Summary And Key Takeaways
- Every kernel message carries a numeric severity level, from
KERN_EMERG(0, most severe) toKERN_DEBUG(7, least severe). - Modern driver code should use the
pr_*helper macros, which map directly onto those same levels with less boilerplate. - The console threshold and the ring buffer are separate concepts — a message can be logged without appearing live on your console.
Conclusion
Kernel log levels are a small piece of API, but understanding them well changes how you read every boot log, panic message, and driver output for the rest of your embedded career. This closes out this stretch of our free linux device drivers course covering modules, booting, panics, init, and logging as one connected picture of what happens between power-on and a working shell prompt.
Frequently Asked Questions
What is the difference between printk and pr_info?
pr_info() is a thin macro wrapper around printk(KERN_INFO ...). They produce identical log output; pr_info is simply less to type and is the preferred style in modern kernel code.
Why don’t my pr_debug messages show up in dmesg?
pr_debug is compiled out entirely by default unless the source file defines DEBUG before including the headers, or dynamic debug is enabled for that module at runtime.
How do I change which levels print to my console?
Write to /proc/sys/kernel/printk or pass the loglevel= kernel command-line parameter to set the console threshold; only messages at or above that severity appear live on the console.
Is KERN_EMERG ever appropriate in ordinary driver code?
Rarely. It is reserved for conditions meaning the system is genuinely unusable, essentially reserved for the kernel’s own core panic and oops-reporting paths rather than typical driver error handling.
What does the rate-limited variant of a pr_* macro do?
Variants like pr_err_ratelimited automatically suppress repeated identical messages within a short window, preventing a fast-firing error condition from flooding the log buffer.
Finish This Section Of Your Kernel Journey
This lecture is part of EmbeddedPathashala’s free linux device drivers course, built for students who want real depth, not shortcuts.
