printk概述
对于做Linux内核开发的人来说,printk实在是再熟悉不过了。内核启动时显示的各种信息大部分都是通过printk来实现的,编写驱动时也经常使用printk来作为一种调试手段。printk的设计是通过一个ring buffer(环形缓冲区)实现的。
printk使用限制:在系统启动过程的早期,例如终端和控制台初始化之前,虽然可以使用printk,但是并不能立即输出而是将信息缓存在printk的简单ring buffer中,待终端和控制台初始化之后,将所有缓存信息一并输出。
在终端上,可以通过dmesg
和cat /proc/kmsg
命令查看ring buffer的内容,通过内核CONFIG_LOG_BUF_SHIFT配置可以修改这个环形缓冲区的大小。
日志的使用
日志级别
在调试内核模块时,有些调试信息无法打印的控制台上,这是由日志等级来控制的。内核根据日志级别来判断是否在终端(console)上打印消息。
printk有8个loglevel,定义在include/linux/kern_levels.h中:
#define KERN_EMERG KERN_SOH "0" /* system is unusable 系统不可使用 */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
/* 需要立即采取行动 */
#define KERN_CRIT KERN_SOH "2" /* critical conditions 严重情况 */
#define KERN_ERR KERN_SOH "3" /* error conditions 错误情况 */
#define KERN_WARNING KERN_SOH "4" /* warning conditions 警告情况 */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition 正常情况 */
#define KERN_INFO KERN_SOH "6" /* informational 信息型消息 */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages 调试级别的信息 */
/* 使用默认内核日志级别 */
#define KERN_DEFAULT KERN_SOH "d" /* the default kernel loglevel */
/*
* Annotation for a "continued" line of log printout (only done after a
* line that had no enclosing \n). Only to be used by core/arch code
* during early bootup (a continued line is not SMP-safe otherwise).
* 标注一个"连续"的日志打印输出行
*/
#define KERN_CONT KERN_SOH "c"
内核用这些宏指定日志等级,并和当前终端的日志等级console_loglevel来决定是不是向终端打印信息,printk的使用如下:
printk(KERN_EMERG "log_level:%s\n", KERN_EMERG);
如果使用printk时没有指定日志等级,内核会选用MESSAGE_LOGLEVEL_DEFAULT,这个定义在include/linux/printk.h中:
#define MESSAGE_LOGLEVEL_DEFAULT CONFIG_MESSAGE_LOGLEVEL_DEFAULT
相关辅助宏:如果每次使用printk时都要指定日志级别,似乎有点麻烦了。所有内核中定义了一些宏来方便使用printk,如下:
//include\linux\printk.h
#ifndef pr_fmt
#define pr_fmt(fmt) fmt
#endif
/*
* These can be used to print at the various log levels.
* All of these will print unconditionally, although note that pr_debug()
* and other debug macros are compiled out unless either DEBUG is defined
* or CONFIG_DYNAMIC_DEBUG is set.
*/
#define pr_emerg(fmt, ...) \
printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
#define pr_alert(fmt, ...) \
printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_crit(fmt, ...) \
printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_err(fmt, ...) \
printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warning(fmt, ...) \
printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warn pr_warning
#define pr_notice(fmt, ...) \
printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
#define pr_info(fmt, ...) \
printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
/*
* Like KERN_CONT, pr_cont() should only be used when continuing
* a line with no newline ('\n') enclosed. Otherwise it defaults
* back to KERN_DEFAULT.
*/
#define pr_cont(fmt, ...) \
printk(KERN_CONT fmt, ##__VA_ARGS__)
/* pr_devel() should produce zero code unless DEBUG is defined */
#ifdef DEBUG
#define pr_devel(fmt, ...) \
printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#else
#define pr_devel(fmt, ...) \
no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif
/* If you are writing a driver, please use dev_dbg instead */
/* 如果你在写一个驱动,请使用dev_dbg代替 */
#if defined(CONFIG_DYNAMIC_DEBUG)
#include <linux/dynamic_debug.h>
/* dynamic_pr_debug() uses pr_fmt() internally so we don't need it here */
#define pr_debug(fmt, ...) \
dynamic_pr_debug(fmt, ##__VA_ARGS__)
#elif defined(DEBUG)
#define pr_debug(fmt, ...) \
printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...) \
no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif
需要注意的是,pr_deve
l和pr_debug
这些宏只有在定义了DEBUG之后才会产生实际的printk代码,而在写驱动程序时,经常使用dev_dbg
来代替printk
控制台日志级别
内核会把级别比某个特定值低的所有消息显示在终端上,但是所有信息都会记录在printk的ring buffer中。通过cat /proc/sys/kernel/printk
可以查看内核设置的这个日志等级的特定值。
# cat /proc/sys/kernel/printk
7 4 1 7
#上面四个值分别对应:
控制台日志级别:级别高于该值的消息将被打印到控制台
默认的消息日志级别:用该等级打印没有优先级的消息
最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级)
默认的控制台日志级别:控制台日志级别的缺省值
#注:数值越小,优先级越高
上述四个值在kernel/printk/printk.c中被定义,如下:
int console_printk[4] = {
CONSOLE_LOGLEVEL_DEFAULT, /* console_loglevel */
MESSAGE_LOGLEVEL_DEFAULT, /* default_message_loglevel */
CONSOLE_LOGLEVEL_MIN, /* minimum_console_loglevel */
CONSOLE_LOGLEVEL_DEFAULT, /* default_console_loglevel */
};
当然我们可以手动控制printk打印,例如,屏蔽掉所有的内核printk打印,使用echo 1 4 1 7 > /proc/sys/kernel/printk
将控制台日志级别设置到0或1的较高优先级。
使用cmdline设置控制日志打印
一个简单的应用,使用cmdline的关键字控制内核日志打印
/* 直接添加到kernel/printk/printk.c中 */
//根据kdbg的值设置console_loglevel
static int __init kdbg_config(char *str)
{
/*get the value from kdbg parameter*/
if(str) {
console_loglevel = *str-'0';
}
else {
console_loglevel = 0;
}
return 0;
}
//解析cmdline,处理kdbg
__setup("kdbg=",kdbg_config);