printk浅析
printk的机制
日志等级
#define KERN_EMERG "<0>" /* 系统不可使用 */
#define KERN_ALERT "<1>" /* 需要立即采取行动 */
#define KERN_CRIT "<2>" /* 严重情况 */
#define KERN_ERR "<3>" /* 错误情况 */
#define KERN_WARNING "<4>" /* 警告情况 */
#define KERN_NOTICE "<5>" /* 正常情况, 但是值得注意 */
#define KERN_INFO "<6>" /* 信息型消息 */
#define KERN_DEBUG "<7>" /* 调试级别的信息 */
printk内核实现
printk 可以在内核的任意上下文中调用。这个调用从 ./linux/kernel/printk.c 中的 printk 函数开始,它会在使用 va_start 解析可变长度参数之后调用 vprintk(在同一个源文件)。
vprintk 函数执行了许多管理级检查(递归检查),然后获取日志缓冲区的锁(__log_buf)。接下来,它会对输入的字符串进行日志级别检查; 如果发现日志级别信息,那么对应的日志级别就会被设置。最后,vprintk
会获取当前时间(使用函数 cpu_clock)并使用 sprintf
(不是标准库版本,而是在 ./linux/lib/vsprintf.c 中实现的内部内核版本)将它转换成一个字符串。这个字符串会被传递给 printk,然后它会被一个管理缓冲边界(emit_log_char)的特殊函数复制到内核日志缓冲区中。这个函数最后将获取和释放执行控制台信号,并将下一条日志消息发送到控制台(在 release_console_sem 中执行)。内核缓冲缓冲区的大小初始值为 4KB,但是最新的内核大小已经升级到 16KB(在不同的体系架构上,这个值最高可以达到 1MB)。
asmlinkage int printk(const char *fmt, ...)
{
va_list args;
int r;
/*
*启动KDB调试
*/
#ifdef CONFIG_KGDB_KDB
if (unlikely(kdb_trap_printk)) {
va_start(args, fmt);
r = vkdb_printf(fmt, args);
va_end(args);
return r;
}
#endif
va_start(args, fmt);
r = vprintk(fmt, args);//printk函数的核心步骤
va_end(args);
return r;
}
asmlinkage int vprintk(const char *fmt, va_list args)
{
int printed_len = 0;
int current_log_level = default_message_loglevel;//printk函数的默认输出级别
unsigned long flags;
int this_cpu;
char *p;
size_t plen;
char special;
boot_delay_msec();
printk_delay();
/* 当我们需要console_sem的持有者的时候 我们会停止这一步 */
local_irq_save(flags);
this_cpu = smp_processor_id();//获得cpu号
/* printk在错误使用下会引发panic */
if (unlikely(printk_cpu == this_cpu)) {
/*
* 如果在这个CPU上printk调用失败了,然后试着得到失败的信息但
* 需要保证我们不会引发死锁。否则直接返回来避免printk的递归
* 但是要标记已经发生递归,并在下一个合适的时刻将信息打印出来
*/
/* oops_in_progress只有在panic函数中才为1 */
if (!oops_in_progress && !lockdep_recursing(current)) {
recursion_bug = 1;
goto out_restore_irqs;
}
/*如果在printk运行时,这个CPU崩溃,确信不能死锁,
*10秒1次初始化锁logbuf_lock和console_sem,
*留时间给控制台打印完全的oops信息
*/
zap_locks();
}
lockdep_off();
raw_spin_lock(&logbuf_lock);
printk_cpu = this_cpu;
/* 因为第一次printk调用失败 所以这次需要把第一次失败的信息打印出来 */
if (recursion_bug) {
recursion_bug = 0;
strcpy(printk_buf, recursion_bug_msg);
printed_len = strlen(recursion_bug_msg);
}
/*将要输出的字符串按照fmt中的格式编排好,
*放入printk_buf中,并返回应该输出的字符个数
*/
printed_len += vscnprintf(printk_buf + printed_len,
sizeof(printk_buf) - printed_len, fmt, args);
p = printk_buf;
/* 读取日志等级 并且处理printk的特殊前缀 */
plen = log_prefix(p, ¤t_log_level, &special);
if (plen) {
p += plen;
switch (special) {
case 'c