Linux Kernel Core API:printk
简单介绍
printk()是Linux Kernel最广为认知的函数之一。它是我们打印消息的标准工具,通常也是追踪和调试的最基本方法。printk()是基于printf(3)实现的,有如下功能差异:
- printk()消息可以指定级别
- 格式字符串虽然很大程度上与C99基本兼容,但不遵守完全相同的规范。它有一些扩展和限制(如:没有%n或浮点转换说明符)。
所有的printk()消息会被打印到内核的一个通过/dev/kmsg暴露给用户空间的环形日志缓存。使用dmesg命令可以读取这个日志缓存。
printk()典型使用:
printk(KERN_INFO "Message: %s\n", arg);
其中KERN_INFO是日志级别(注意,它与格式字符串连在一起,日志级别不是一个单独的参数)。
一条日志能不能打印出来、是不是立即打印出来、是不是同行打印出来等,受两方面影响:
- 日志记录方式。就是你使用哪个接口输出日志、使用哪个级别输出日志。
- printk当前控制。
printk实现
日志级别说明消息的重要性。内核决定是否立即显示某条消息(打印它到当前控制台)取决于这条消息的log level和当前控制台console_loglevel(一个内核变量):
- 消息的优先级越高表示日志级别的值越低。
- 优先级高于console_loglevel的消息将被打印到console。
- 如果消息省略了log level,这条消息将按KERN_DEFAULT级别打印。
日志级别
如下表,printk()消息有8个级别(0-7),对应每个级别有一个"别名函数"。
查看当前console_loglevel
如上4个数值分别表示:current, default, minimum, boot-time-default log level
修改当前console_loglevel
方法1.向/proc/sys/kernel/printk写期望的值
如上,current log level将被设置为8,即打印所有消息到控制台。
方法2.使用dmesg设置
如上,current log level将被重新设置为4,即打印KERN_ERR或更验证的消息到控制台。
日志输出接口
除了printk()以外,可以使用pr_*()这些别名打印日志。pr_*()这族宏在宏内部嵌入了log level.
printk
如果printk indexing使能,_printk()由printk_index_wrap调用,否则printk简单定义为no_printk。
printk()调用的时候尝试霸占console_lock。如果成功,将记录输出并调用console drivers;如果获取信号量失败,将防止输出内容到日志缓存并返回。当前持有console_sem的进程在console_unlock中通知新的输出;并在释放锁前发送这个信号给console。
这个推迟的打印的一个影响是代码调用printk(),然后修改console_loglevel会被打断。这是因为实际打印发生时检查console_loglevel。
pr_emerg、pr_alert、pr_crit、pr_err、pr_warn、pr_notice、pr_info
//include/linux/printk.h
#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_warn(fmt, ...) \printk(KERN_WARN pr_fmt(fmt), ##__VA_ARGS)#define pr_notice(fmt, ...) \printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS)#define pr_info(fmt, ...) \printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS)
pr_cont
用于在同一行继续之前的消息输出,即没有换行('\n')
#define pr_cont(fmt, ...) \printk(KERN_CONT pr_fmt(fmt), ##__VA_ARGS)
pr_devel、pr_debug
该接口是有条件的打印debug level消息.
pr_devel打印日志:
- DEBUG定义,消息以debug_level级别打印
- DEBUG未定义,消息将被"过滤",即不会打印到console
#ifdef DEBUG
#define pr_devel(fmt, ...) \printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS)
#else
#define pr_devel(fmt, ...) \no_print(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS)
#endif
pr_debug打印日志:
- CONFIG_DYNAMIC_DEBUG定义或者CONFIG_DYNAMIC_DEBUG_CORE和DYNAMIC_DEBUG_MODULE同时定义,宏展开为dynamic_pr_debug()
- DEBUG定义,宏展开为printk()
- 其他情况,宏展开为no_printk()
#if defined(CONFIG_DYNAMIC_DEBUG) || \(defined(CONIFG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE))
#include <linux/dynamic_debug.h>
#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
其他
void early_printk(const char *fmt, ...) 系统启动早期使用的打印日志接口。
void vprintk(const char *fmt, va_list args)
void vprintk_emit(int facility, int level, const struct dev_printk_info *dev_info, const char *fmt, va_list args)
....
日志格式化
可以使用pr_fmt宏为格式化字符串使用共同的定义。
pr_fmt
#ifndef pr_fmt
#define pr_fmt(fmt) fmt
#endif
例子:为格式化串加前缀
在源文件顶部(任何#include指令之前)定义如下:
#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
会在该文件中的每个 pr_*() 消息前加上发出该消息的模块和函数的名称
printk使用
Linux Kernel中printk接口使用,