一直都知道内核printk分级机制,但是没有去了解过,前段时间和一个同事聊到开机启动打印太多,只需要设置一下等级即可;另外今天看驱动源码,也看到类似于Printk(KERN_ERR "....")的打印信息,以前用都是直接printk("...."),今晚回来就把printk这个机制熟悉一下。

转自:http://blog.csdn.net/tangkegagalikaiwu/article/details/8572365

另外/var/log下20个Linux日志文件详解

http://h2appy.blog.51cto.com/609721/781281/

一、printk概述

    对于做Linux内核开发的人来说,printk实在是再熟悉不过了。内核启动时显示的各种信息大部分都是通过她来实现的,在做内核驱动调试的时候大部分时候使用她就足矣。她之所以用得如此广泛,一个是由于她使用方便,还有一个重要的原因是她的健壮性。它使用范围很广,几乎是内核的任何地方都能调用它。你既可以在中断上下文、进程上下中调用她,也可以在任何持有锁时调用她,更可以在SMP系统中调用她,且调用时连锁都不必使用。这样好的适应性来源于她的设计,一个由三个指针控制的简单“ring buffer”。
   注意上面说到的是:“几乎”在内核的任何地方都可以使用。那什么地方使用会有“问题”?那就是在系统启动过程的早期,终端初始化之前的某些地方虽然可以使用,但是在终端和控制台被初始化之前所有信息都被缓存在printk的简单的ring buffer(环形缓冲区)中,直到终端和控制台被初始化之后,所有缓存信息都被一并输出。
 
     如果你要调试的是启动过程最开始的部分(如setup_arch()),可以依靠此时能够工作的硬件设备(如串口)与外界通信,使用printk()的变体early_printk()函数。她在启动过程初期就具有在终端上打印的能力,功能与prink()类似,区别在于:
  • 函数名
  • 能够更早地工作(输出信息)
  • 她有自己的小缓存(一般为512B)
  • 一次性输出到硬件设备,不再以ring buffer的形式保留信息。
      但该函数在一些构架上无法实现,所以这种办法缺少可移植性。(大多数构架都可以,包括x86和arm)。
     所以,除非要在启动初期在终端上输出,否则我们认为printk()在任何情况下都能工作。这点从内核的启动代码中就可以看出,在已进入start_kernel不久就通过printk打印内核版本信息了: printk(KERN_NOTICE "%s", linux_banner);。

二、printk的使用

   printk()和C库中的printf()在使用上最主要的区别就是 printk()指定了日志级别。

(1)日志等级

内核根据日志级别来判断是否在终端(console)上打印消息:内核把级别比某个特定值低的所有消息显示在终端(console)上。但是所有信息都会记录在printk的“ring buffer”中。
 
  printk有8个loglevel,定义在<linux/kernel.h>中:(include/linux/kernel.h)
#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>" /* 调试级别的信息 */
/* 使用默认内核日志级别 */
#define KERN_DEFAULT "<d>"
/*
* 标注为一个“连续”的日志打印输出行(只能用于一个
* 没有用 \n封闭的行之后). 只能用于启动初期的 core/arch 代码
* (否则续行是非SMP的安全).
*/
#define KERN_CONT "<c>"
     如果使用时没有指定日志等级,内核会选用DEFAULT_MESSAGE_LOGLEVEL,这个定义位于kernel/printk.c:
/* printk's without a loglevel use this.. */
#define DEFAULT_MESSAGE_LOGLEVEL CONFIG_DEFAULT_MESSAGE_LOGLEVEL
    可以看出,这个等级是可以在内核配置时指定,这种机制是从2.6.39开始有的,如果你不去特别配置,那么默认为<4>,也就是KERN_WARNING。
  也就是说,如果你在使用printk的时候没有没有指定loglevel,那么该条语句被默认的loglevel为4. 
    内核将最重要的记录等级 KERN_EMERG定为“<0>”,将无关紧要的调试记录等级“KERN_DEBUG”定为“<7>”。
    内核用这些宏指定日志等级和当前终端的记录等级console_loglevel来决定是不是向终端上打印,使用示例如下:
printk(KERN_EMERG "log_level:%s\n", KERN_EMERG);
    当编译预处理完成之后,前例中的代码实际被编译成成如下格式:
printk( "<0>" "log_level:%s\n", KERN_EMERG);
设置

kern_levels.h printk.h printk.c 几个文件里面分别定义了printk和console的printk显示等级

echo 8 > /proc/sys/kernel/printk设置console的loglevel

查看
cat /proc/sys/kernel/printk
4 4 1 7
第一个参数表示console的loglevel,第二个参数表示prink的loglevel(只是表示不加参数,printk的默认loglevel),如果设置console的loglevel为8,则printk都会打印在console,否则0/1/2/3打在console。
echo 8 > /proc/sys/kernel/printk设置console的loglevel
使用
给一个printk()什么日志等级完全取决于你。那些正式、且需要保持的消息应该根据信息的性质给出相应的日志等级。但那些为了解决一个问题临时加得到处都是的调试信息可以有两种做法
  • 一种选择是保持终端的默认记录等级不变,给所有调试信息KERN CRIT或更低的等级以保证信息一定会被输出。
  • 另一种方法则相反,给所有调试信息KERN DEBUG等级,而调整终端的默认记录等级为7,也可以输出所有调试信息。
    两种方法各有利弊。
    这里说到了调整内核的默认的日志级别,在我3年半前的学习笔记《 Linux设备驱动程序学习(2)-调试技术 》中有介绍,可以通过 /proc/sys/kernel/printk来改变,或者C程序调用syslog系统调用来实现。但是现在的glibc的函数接口改了,由于 syslog 这个词使用过于广泛,这个函数的名称被修改成 klogctl,所以原来博文中的代码无法使用了,以下是新版的 console_loglevel代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
//#define __LIBRARY__ /* _syscall3 and friends are only available through this */
//#include <linux/unistd.h>
/* define the system call, to override the library function */
//_syscall3(int, syslog, int, type, char *, bufp, int, len);
int main(int argc, char **argv)
{
int level;
if (argc == ) {
level = atoi(argv[]); /* the chosen console */
} else {
fprintf(stderr, "%s: need a single arg\n", argv[]);
exit();
}
if (klogctl(, NULL, level) < ) {
fprintf(stderr, "%s: syslog(setlevel): %s\n",
argv[], strerror(errno));
exit();
}
exit();

(2)相关辅助宏

    如果确定printk所需要的日志等级,每次都要在其中添加以上宏,似乎有点麻烦了。所以内核黑客们定义了一些宏来方便printk的使用,这些宏在内核代码中也是随处可见:
#ifndef pr_fmt
#define pr_fmt(fmt) fmt
#endif
#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__)
#define pr_cont(fmt, ...) \
printk(KERN_CONT fmt, ##__VA_ARGS__)
/* 除非定义了DEBUG ,否则pr_devel()不产生任何代码 */
#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
/* 如果你在写一个驱动,请使用dev_dbg */
#if defined(DEBUG)
#define pr_debug(fmt, ...) \
printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#elif defined(CONFIG_DYNAMIC_DEBUG)
/* dynamic_pr_debug() uses pr_fmt() internally so we don't need it here */
#define pr_debug(fmt, ...) \
dynamic_pr_debug(fmt, ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...) \
no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif
     从上面的代码大家应该就可以知道这些宏的使用了。值得注意的是:pr_devel和 pr_debug这些宏只有在定义了DEBUG之后才会产生实际的printk代码,这样方便了内核开发:在代码中使用这些宏,当调试结束,只要简单地#undef DEBUG就可以消除这些调试使用的代码,无需真正地去删除调试输出代码。

(3)输出速率控制

在调试的时候,有时某些部分可能printk会产生大量输出, 导致系统无法正常工作,并可能使系统日志ring buffer溢出(旧的信息被快速覆盖)。特别地,当使用一个慢速控制台设备(如串口), 过量输出也能拖慢系统。这样反而难于发现系统出问题的地方。所以你应当非常注意:正常操作时不应当打印任何东西,打印的输出应当是指示需要注意的异常,并小心不要做过头。

   在某些情况下, 最好的做法是设置一个标志变量表示:已经打印过这个了,以后不再打印任何这个信息。而对于打印速率的控制内核已经提供了一个现成的宏:
#define printk_ratelimit() __printk_ratelimit(__func__)

这个函数应当在你认为打印一个可能会出现大量重复的消息之前调用,如果这个函数返回非零值, 继续打印你的消息, 否则跳过它。典型的调用如这样:

if (printk_ratelimit())
printk(KERN_NOTICE "The printer is still on fire\n");
  printk_ratelimit通过跟踪发向控制台的消息的数量和时间来工作。当输出超过一个限度, printk_ratelimit 开始返回 0 使消息被丢弃。我们可以通过修改 :
  • /proc/sys/kern/printk_ratelimit( 可以看作一个监测周期,在这个周期内只能发出下面的控制量的信息)
  • /proc/sys/kernel/printk_ratelimit_burst(以上周期内的最大消息数,如果超过了printk_ratelimit()返回0)
来控制消息的输出.
   在<linux/kernel.h>中还定义了其他的宏,比如printk_ratelimited(fmt, ...)等,有兴趣的朋友就去文件中看看便知,很好理解的

(4)最后特别提醒

1、虽然printk很健壮,但是看了源码你就知道,这个函数的效率很低:做字符拷贝时一次只拷贝一个字节,且去调用console输出可能还产生中断。所以如果你的驱动在功能调试完成以后做性能测试或者发布的时候千万记得尽量减少printk输出,做到仅在出错时输出少量信息。否则往console输出无用信息影响性能。我刚开始学驱动的时候就犯过这样的白痴错误,在测试CAN驱动性能的时候居然printk出信息来核对,结果直接宕机。
2、printk的临时缓存printk_buf只有1K,所有一次printk函数只能记录<1K的信息到log buffer,并且printk使用的“ring buffer”

三、printk的内核实现

    对于Linux的printk内核日志,常常被称为kernel ring buffer,这是由于printk的缓存实现就是使用了一个简单的ring buffer(环形缓冲区)。但是这里需要注意的是,不要和内核trace系统ring buffer混淆,虽然他们都是为了跟踪调试,但是trace系统的ring buffer实现更加完善复杂,而printk使用的ring buffer则非常简单,其实就定义了一个字符数组:
static char __log_buf[__LOG_BUF_LEN];
并使用了一套指针来管理:
/*
* 在指向log_buf时并没有用log_buf_len做限制 - 所以他们
* 在作为下标使用前必须用掩码处理(去除CONFIG_LOG_BUF_SHIFT以上的高位)
*/
static unsigned log_start; /* log_buf中的索引: 指向由syslog()读取的下一个字符 */
static unsigned con_start; /* log_buf中的索引: 指向发送到console的下一个字符 */
static unsigned log_end; /* log_buf中的索引:最近写入的字符地址 + 1 */ 
    具体的实现CU中已经有一位博友写过了,我这里就不再啰嗦了,我转载备份了一下:《printk实现分析》

四、用户空间访问内核日志

    用户空间访问和控制内核日志有两个接口:
  • 通过glibc的klogctl函数接口调用内核的syslog系统调用
  • 通过fs/proc/kmsg.c内核模块中导出的procfs接口:/proc/kmsg文件。
    他们其实最终都调用了/kernel/printk.c中的do_syslog函数,实现对__log_buf的访问及相关变量的修改。
    但值得注意的是:从/proc/kmsg中获取数据,那么__log_buf中被读取过的数据就不再保留(也就是会修改log_start指针), 然而 syslog 系统调用返回日志数据并保留数据给其他进程。读取/proc文件是 klogd的默认做法。dmesg命令可用来查看缓存的内容并保留它,其实它是将__log_buf中的所有内容返回给stdout,并不管它是否已经被读取过。
 
    这里我还是推荐大家 RTFSC – Read The Fucking Source Code,自己看这些代码比什么都强,我这里就只引个路。
    在用户空间有专门用于记录系统日志的程序,统称为“syslog守护进程”。早期及现在的大部分嵌入式系统使用的是klogd+syslogd组合,现在大多数发行版都使用rsyslogd或者syslogd-ng了。这些用户空间的程序我这里就不分析了,我不擅长,运维的可能比较清楚。我只知道一下他们大致的调用关系就好。
 
    这里我用一张图来总结一下内核printk和日志系统的总体结构:

[kernel]内核日志及printk结构分析的更多相关文章

  1. [linux-内核][转]内核日志及printk结构浅析

    这段时间复习了一下内核调试系统,注意看了一下printk的实现以及内核日志的相关知识,这里做一下总结. 1.问题的引出: 做DPDK项目时,调试rte_kni.ko时,发现printk并不会向我们想想 ...

  2. 内核日志及printk结构浅析

      作者:tekkamanninja 鸣谢:感谢ChinaUnix技术社区的tekkamanninja提供稿件 ,如需转载,请注明出处. 这段时间复习了一下内核调试系统,注意看了一下printk的实现 ...

  3. ECS实例中的应用偶尔出现丢包现象并且内核日志(dmesg)存在“kernel: nf_conntrack: table full, dropping packet”的报错信息

    问题描述 连接ECS实例中的应用时偶尔出现丢包现象.经排查,ECS实例的外围网络正常,但内核日志(dmesg)中存在"kernel: nf_conntrack: table full, dr ...

  4. Linux内核日志开关

    Linux内核日志开关 1.让pr_debug能输出 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -59,7 +59,7 ...

  5. 深入linux kernel内核配置选项

    ============================================================================== 深入linux kernel内核配置选项 ...

  6. tiny4412 串口驱动分析八 --- log打印的几个阶段之内核启动阶段(printk tiny4412串口驱动的注册)

    作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...

  7. Ubuntu系统---系统驱动丢失、Kernel内核卸载、禁止更新

    Ubuntu系统---系统驱动丢失.Kernel内核卸载.禁止更新 一早开机发现,ubuntu字体异常,字体很大,直接反应是驱动坏了.一查,确实丢失英伟达驱动,为什么呢?莫名的消失.想知道:1.英伟达 ...

  8. linux内核源码目录结构分析

    原文地址 /arch.arch是architecture的缩写.arch目录下是好多个不同架构的CPU的子目录,譬如arm这种cpu的所有文件都在arch/arm目录下,X86的CPU的所有文件都在a ...

  9. Linux 内核日志——dmesg

    有时Linux系统或者系统上运行的mysqld或者其它进程,会发生一些莫名其妙的问题,比如突然挂掉了,比如突然重启等等.在软件上找不到问题所在,此时我们应该怀疑硬件或者内核的问题,此时我们就可以使用 ...

随机推荐

  1. SQL 增删字段

    摘自: http://www.cnblogs.com/yiwd/archive/2012/12/25/2831822.html 下面为您介绍使用SQL语句如何增加.删除.修改字段,并判断字段是否存在的 ...

  2. scala lambda 小括号与大括号

    看akka源码的时候看到这样的一个用法: 作为接触scala两天半的我有些看不明白了.好一番搜索看到这样的答案: <scala雾中风景(2): 小括号与花括号> 下面的问题,表面上看是小括 ...

  3. Activiti 流程启动及节点流转源代码分析

    作者:jiankunking 出处:http://blog.csdn.net/jiankunking 本文主要是以activiti-study中的xiaomage.xml流程图为例进行跟踪分析 详细的 ...

  4. Cocos2d-x 处理双击事件的两种方法

    在cocos2d-x的开发过程中有些时候也是需要用到双击的事件处理,那么由于在cocos2d-x中没有实现对双击的事件的处理,那么我们就需要自己用代码实现. 下面介绍两种方式实现双击事件的处理. (一 ...

  5. linux中Oops信息的调试及栈回溯

    Oops 信息来源及格式 Oops 这个单词含义为“惊讶” ,当内核出错时(比如访问非法地址)打印出来的信息被 称为 Oops 信息. Oops 信息包含以下几部分内容. 1 一段文本描述信息. 比如 ...

  6. Myeclipse2013下载,安装,破解,介绍(CSDN首发)

    MyEclipse 2013新特性 根据官方最新消息,MyEclipse 2013已经正式发布!MyEclipse 2013支持HTML5.JQuery和主流的Javascript 库. 随着MyEc ...

  7. UNIX网络编程学习笔记:值-结果(value-result)参数

    前言 当把套接口地址结构传递给套接口函数时,总是通过指针来传递的,即传递的是一个指向结构的指针.结构的长度也作为参数来传递,其传递的方式取决于结构的传递方向:从进程到内核,还是从内核到进程. 1.从进 ...

  8. codechef The Ball And Cups题解

    The Ball And Cups At the end of a busy day, The Chef and his assistants play a game together. The ga ...

  9. if-else用法

      CreateTime--2016年10月31日14:22:25Author:Marydonif-else的多种用法: //方式一 function test1 (t) { var bl = t | ...

  10. LeetCode 110 Balanced Binary Tree(平衡二叉树)(*)

    翻译 给定一个二叉树,决定它是否是高度平衡的. (高度是名词不是形容词-- 对于这个问题.一个高度平衡二叉树被定义为: 这棵树的每一个节点的两个子树的深度差不能超过1. 原文 Given a bina ...