因为图片比较大,加上微信公众号的压缩比较厉害,所以很多细节都看不清楚。我上传了一个单独的副本到github。我要原图,可以点击下方阅读原文,或者直接使用以下链接访问github:https://github.com/wangyuntao/linux-kernel-illustrated另外精美全景系列文章,后续linux内核分析文章,我会整理到这个github仓库,欢迎大家star收藏。熟悉linux内核或者看过linux内核源码的同学都会知道,在内核中,有一个类似于c语言的输出函数,叫做printk。使用它,我们可以打印各种我们想要的信息,比如内核当前的运行状态,或者我们自己的调试日志等等,非常方便。那么当我们调用printk函数的时候,输出的信息到哪里去了呢?我们在linux下如何在用户态查看这些信息呢?为了回答这些问题,我画了一张printk的全景图,放在了文章的开头,这张图不仅包含了printk在内核态的实现,还包含了如何在用户态查看它的输出信息。我们可以根据这张图来理解printk的整体架构。在对内核进行编码时,如果要输出一些信息,通常不会直接使用printk,而是使用它的派生函数,如pr_err/pr_info/pr_debug等。这些派生函数自带日志级别和其他信息模块。友好,但它最终还是调用了printk。printk函数会将每次输出的日志放入内核专门分配的一个名为ringbuffer的槽中。ringbuffer其实就是一个数组实现的环形队列,但是既然是环形队列,就会有一个问题,就是当ringbuffer满了的时候,下一条新的日志会覆盖原来的旧日志。环形缓冲区的大小可以通过内核参数修改。printk将日志放入ringbuffer后,会调用系统控制台的相关方法,将没有输出到系统控制台的消息继续输出到控制台。这个后面会详细介绍,这里就不赘述了。以上是内核态下printk的实现。在用户态,我们有几种方法可以查看printk输出的内核日志,比如使用dmesg命令,cat/proc/kmsg文件,或者使用klogctl函数等。这些方法分别对应orange,Green,和蓝色部分。默认情况下,dmesg命令会读取/dev/kmsg文件以查看内核日志。运行该命令时,dmesg将首先调用open函数打开/dev/kmsg文件。内核中open操作的逻辑会分配一个文件实例给dmesg。在这个文件实例中,会有一个seq变量,该变量记录下一个要读取的内核日志在ringbuffer中的位置。当/dev/kmsg文件刚打开时,这个seq指向ringbuffer中的第一条日志。之后dmesg会以打开的/dev/kmsg文件为媒介,不断调用read函数从内核读取日志信息。每读到一个,seq的值就会加一,指向下一条日志的位置。依次往复,直到读取完所有内核日志,dmesg退出。以上就是dmesg的主要实现。第二种查看内核日志的方法是通过cat/proc/kmsg命令。该命令的实现机制与dmesg命令基本类似,都是读取文件,只是cat读取的是/proc/kmsg文件,而dmesg读取的是/dev/kmsg文件。读取这两个文件最大的区别在于每次打开/dev/kmsg文件时,内核都会为其分配一个单独的seq变量,而每次打开/proc/kmsg文件时,同样是全局静态seq变量,称为syslog_seq。syslog_seq也指向下一个要读取的内核日志在ringbuffer中的位置,但是由于是全局静态变量,当有多个进程读取/proc/kmsg文件时,会出现比较严重的问题就是内核日志会被这些进程随机读取,也就是说每个进程读取的都是整个内核日志的一部分,是不完整的。这就是默认情况下dmesg命令不使用/proc/的原因。kmsg文件。第三种查看内核日志的方式是通过klogctl函数。该函数是glibc对syslog系统调用的简单封装。具体用法可以参考全景图中用户态蓝色部分。klogctl函数可以指定很多命令。在上面的示例中,我们使用SYSLOG_ACTION_READ命令来模拟cat/proc/kmsg行为。实际上,在内核层面,cat/proc/kmsg命令使用的是klogctl对应的syslog系统调用的SYSLOG_ACTION_READ命令的处理逻辑,所以例子中klogctl函数相关的代码其实相当于cat/proc/kmsg命令。也就是说,klogctl函数也使用了内核中的syslog_seq变量,它也和/proc/kmsg文件存在同样的问题。其实还有一种查看内核日志的方法,就是通过系统控制台。但是这种方法不同于上面提到的三种方法。它是完全被动的。内核调用printk函数,将日志信息放入环形缓冲区,然后通知系统控制台可以输出这些日志。.系统控制台还使用一个console_seq变量来记录下一个要输出的内核日志的位置。这里所说的系统控制台是指我们开机时黑屏输出的内容,但是当我们进入图形界面时,我们是看不到系统控制台的输出的,除非我们再次使用ctrl+alt+f1/f2/f3等切换到系统控制台。系统控制台输出的内容是按日志级别过滤的。内核默认的日志过滤级别是7,即会输出debug级别以上的日志,如info/err等,但是debug级别的不会输出。日志过滤级别可以通过多种方式改变,比如通过内核参数loglevel,所以如果发现系统控制台没有输出想要的日志信息,首先要检查是否被过滤掉了。以上就是printk生态的完整实现。理解printk函数的实现对于内核开发者或者研究者来说意义重大,但是对于普通的应用开发者又有什么帮助呢?其实随着技术的深入,我们不应该再只关心应用层面的Behavior,更应该关注系统层面的Behavior,这样才能更好的定位问题,更好的保证我们应用的健康运行。例如,当我们的应用程序需要内存时,它会向操作系统申请。这个时候操作系统给我们的其实是虚拟内存。只有当我们的进程真正在使用这块内存的时候,比如读/写,操作系统才会为其分配物理内存。但是假设此时物理内存没有了,操作系统会怎么办呢?对于linux内核来说,它会选择一个使用内存最多的进程,然后kill它来释放内存,保证后续的内存分配操作能够成功。为什么我的进程被kill,我在之前的文章中已经详细提到了。我们应该多注意内核的这种行为,注意的方式就是查看内核日志。比如Linux内核kill一个进程时,会使用pr_err来记录一行日志:如果我们发现有一个进程正在运行并且正在运行,我们可以使用dmesg命令查看是否有这样的日志。如果是,说明该进程是由于系统内存不足,被操作系统杀掉了。同样的,内核中还有很多错误级别,甚至更高级别的日志也需要我们注意。通过这些日志,我们可以及时发现系统的异常情况,必要时可以进行人工干预。总之,对系统的了解越深,内核日志对我们的帮助就越大。就这些,希望你喜欢。本文转载自微信公众号“猫食猫客”,可通过以下二维码关注。转载本文请联系猫猫猫客公众号。
