klogd:内核日志守护进程
klogd 读取内核日志消息,并帮助处理这些消息,然后将它们发送到适当的文件、套接字或用户。本月我们将讨论内存地址解析,以及如何使用命令行开关修改 klogd 的默认行为。
syslog 守护进程接收通过 syslog 应用程序编程接口(openlog、syslog 和 closelog 库调用)发送的消息,并将它们分发到各种文件、套接字、用户、管道或位桶(意思是“丢弃”——任何与 syslog.conf 文件中的选择器不匹配的日志消息都会被丢弃)。但是,内核消息不能使用 syslog API。
这背后的基本原因是 syslog API 由内核本身提供。可以由自身调用的代码被称为“可重入”的。为了可重入,辅助调用不得对先前的调用产生任何影响。例如,如果代码使用了任何全局或静态变量,则它不能是可重入的,因为辅助调用中对静态变量的更改将更改主调用中的变量。
这样想:如果一个程序正在进行 syslog 调用,而此时内核需要调用 syslog 函数来报告某些内核事件,会发生什么?除非 syslog 调用是完全可重入的,否则内核调用将覆盖用户空间调用。
内核的许多部分是可重入的,要么是真正可重入的,要么是通过使用选择性锁定的方式。如果用户空间程序仅仅因为卡在系统调用中就可能阻塞内核执行,那将是非常糟糕的消息。出于这个原因(以及其他原因),在内核空间代码可以使用的函数和用户空间代码可以使用的代码之间保持了非常清晰的分离。没有内核 API 函数依赖于任何用户空间调用的状态(或者,更精确地说,存在的那些依赖关系是可预测且被充分理解的)。因此,内核不必担心用户空间程序在每时每刻都在调用什么用户空间 API 函数。内核代码可以简单地继续处理业务。
然而,内核确实需要一种发送消息的方式来报告异常情况,并且在调试内核层代码时,需要“看看我们走了多远”。因此,内核有自己的日志记录 API。
(对于好奇的人,用户空间 syslog 调用由内核中名为 sys_syslog 的函数提供。内核 syslog 调用被称为 printk。您可以在您自己的 Linux 内核源代码副本中找到两者的源代码,很可能在您系统上的 /usr/src/linux 目录中。去看看吧。记住,这是 您的 源代码!)
内核的构建是为了简洁和速度。因此,内核对日志记录的概念比 syslog 更基本。内核消息是简单的文本,约定优先级 0 到 7 将编码在 <n> 字符中(其中 n 是优先级,从 0 到 7),前缀于消息文本的其余部分。内核日志记录 API 没有 syslogd 那样的“facility(设施)”概念。级别 7 是最低优先级,级别 0 是最高优先级。
有时,例如当发生保护错误时,内核日志包含内存地址。来自 Linux 内核的保护错误报告对于任何人调试您的问题都没有太大用处,因为内核几乎肯定是本地编译的。即使您从未重新编译过您的内核,但大量的发行版和发行版版本使得某人通过原始保护错误日志来帮助您变得不可能。
幸运的是,自 1.3.43 以来的 Linux 内核以标准格式报告地址。klogd 程序识别该格式并尝试将地址解析为符号名称,以便人们可以实际找到地址引用的对象或代码。稍后,我们将介绍 klogd 是如何做到这一点的。
内核消息的这些方面(单独的 API、非 syslog 属性和内存地址解析)是内核消息需要单独守护进程的原因。曾经(并且现在仍然有)syslog 的修补版本可以接收内核消息,但这种做法不再流行,并且有充分的理由。用户空间和内核空间功能的清晰分离是有道理的。如果 syslogd 完成了其中一些事情,它将使 syslogd 与内核版本紧密绑定。当然,klogd 也相当紧密地绑定,但这被认为更可以接受,因为它“封装”了这种“依赖”代码,然后将其传递给标准日志记录机制 (syslogd)。
因此,klogd 基本上所做的就是读取内核日志消息,稍微转换它们(通过将内核内存地址解析为符号),然后使用内核 facility 和内核消息中编码的优先级来调用用户空间 syslog API。这是守护进程的默认行为。让我们看一下如何修改默认行为。
首先,klogd 没有像 syslogd 那样的配置文件。它的行为只能通过命令行开关和信号来修改。我们将首先介绍开关,然后我们将讨论地址解析。最后,我们将介绍 klogd 响应的信号。
-c 默认控制台日志记录级别。内核不仅将日志消息写入内核消息缓冲区,还写入系统控制台(通常是 /dev/console)。内核的默认级别为 7,这意味着值低于 7(更高优先级)的消息将写入控制台。通常,您希望在 klogd 运行后更改此设置,以便控制台不会总是滚动显示大量低优先级消息。与简单地将内核消息转储到屏幕相比,klogd/syslogd 组合为您提供了更多对内核消息的控制。您可以在此处指定一个数字 n(例如 -c 4),其中值低于但不等于 n 的消息将转到控制台。请注意,klogd 本身不会将消息路由到控制台。它只是提供此接口来更改内核的控制台日志记录级别设置。请记住,n 的值越低,优先级越高。
-d 调试模式。这会在 stderr 上生成大量输出。如果您好奇,可以试一试,尽管我不建议长时间以这种方式运行。
-f 将消息记录到文件。此开关允许您绕过 syslogd 接口并将内核消息直接记录到文件中。您将失去 syslogd 的所有能力,例如按 facility 和优先级分隔消息、将消息路由到多个目的地以及路由到管道、套接字和用户。但是,如果由于某种原因您没有运行 syslogd,它具有明显的价值!(例如 -f /var/log/kernel.log)
-i、-I 向当前正在运行的 klogd 发送信号。我们将在关于内存地址解析的部分中介绍这两个开关(它们是不同的!)。
-n 不要自动后台运行。您可以通过三种方式运行守护进程:在控制台通过命令、通过启动脚本或直接使用 System V init 模型 (/etc/inittab)。当您使用 init 运行时,您不希望进程“fork and die”(这就是 *nix 进程如何将自己置于后台;如果这些对您来说没有任何意义,请参阅 W. Richard Stevens 的优秀著作 UNIX 网络编程 的第 2.6 章),就像您在其他两种情况下所做的那样。通常,如果您的机器上已经运行了 klogd,则无需担心这一点。
-o 单次模式。当使用此选项启动时,klogd 将读取内核日志缓冲区中当前的所有消息,然后退出。
-p 偏执模式。这会更改 klogd 加载内核符号数据的时间。我们将在关于内存地址解析的部分中更详细地介绍这一点。
-s 强制系统调用模式。通常,klogd 在启动时检查 /proc/kmsg 文件是否存在。如果存在,则将其打开作为读取内核消息的位置。如果不存在,klogd 将通过系统调用轮询内核以获取内核消息。首选 /proc/kmsg 是因为它具有较低的开销,尤其是在没有内核消息时(这是一种常见情况)。您可以使用此开关覆盖对 /proc/kmsg 接口的偏好,并强制 klogd 改用系统调用。
-k 内核符号文件。请参阅关于内存地址解析的部分。
-v 打印版本并退出。本文档基于 klogd 1.3-3。
-x 不解析地址。有关更多信息,请参阅关于内存地址解析的部分。
(以下讨论假设 Linux 运行在 x86 处理器上。我认为其他处理器类似,但我没有检查它们的代码,因此我没有准备好声明以下内容适用于这些处理器。)
让我们首先注意,导致内核日志的真正保护异常是非常罕见的事件。大多数保护错误发生在用户空间代码中。用户空间保护错误会导致程序终止和核心文件转储。您可以使用核心文件和您喜欢的调试器对应用程序进行事后分析。这些事件几乎不会打扰 Linux 内核,内核仍然愉快地处理系统中的所有其他应用程序。
我们在这里讨论的错误是发生在内核代码中的处理器异常。这些非常罕见,自从 1993 年我开始使用 Linux 以来,我只见过五次。其中三次发生在我使用德克萨斯 A&M 大学的“TAMU”Linux 发行版时。我们说的是 pre-0.99 Linux。我认为这是可以预料的。下一次发生在我有一个垂死的硬盘驱动器并且我的交换分区是有缺陷的区域时。第五次也是最后一次发生在我笔记本电脑中的 CPU 过热时。自 1994 年以来,除了硬件故障外,我没有因为任何原因见过一次。
话虽如此,它们确实会发生。一些从未用过的硬件组合会导致以前从未运行过的内核代码组合;或者也许您是一个大胆的人,并且您正在运行开发内核。无论是什么原因,有时好的代码也会变坏。好消息是 Linux 是一个开源操作系统。您可以修复错误。或者如果不能,您可以发布错误报告,该报告直接发送给可以修复错误的人。用 Windows 试试 那个!
当发生保护错误时,Linux 会转储处理器状态的转储,包括所有寄存器和系统堆栈的最后几个条目。后者对于查找问题的根源至关重要。问题是,原始转储完全由内存地址组成。由于 Linux 是一个开源系统,并且由于许多安装都是自定义编译的,因此这些地址将有助于支持人员找出问题的可能性很小。
幸运的是,如果您以正常方式构建内核,则会有一个名为 System.map 的文件与您的内核一起安装(可能在 /boot 中)。这会将代码和符号映射到物理地址。klogd 守护进程读取此文件。这处理了所有“编译到”内核代码,但是自 2.0.x 内核系列以来,Linux 支持内核模块,这些模块是动态加载的内核代码模块。这些模块可能位于任何地址,具体取决于在给定时刻加载了哪些模块以及以什么顺序加载。
在程序启动时,或响应信号时,klogd 将查询内核以获取模块列表及其加载地址。内核模块可能会在加载时向内核注册单个函数或标识符地址。klogd 守护进程将使用此信息来报告故障转储中的地址。重要的是要注意,来自 klogd 的模块地址可能已过时!如果在 klogd 初始化后加载或卸载模块,则这些模块/地址解析将不正确。您的发行版可能会通过提供脚本实用程序来自动刷新 klogd 来为您处理此问题。如果它没有这样做,那么我们之前跳过的一些开关就会发挥作用,以帮助您保持内存映射最新。
-i 开关告诉 klogd 重新加载模块符号。-I 告诉 klogd 重新加载 System.map 文件。-p 开关启用“偏执”模式。这样做会导致 klogd 在内核消息流中看到“Oops”时尝试重新加载模块符号。保护错误中包含此字符串。我个人认为这很笨拙,我不使用它。此外,如果发生了保护错误,则内核可能会停止运行,或者内存映射可能处于损坏状态。如果您需要,可以使用它。-k 选项允许您指定包含内核符号信息的文件。请参阅下面的关于多个内核的部分。-x 开关告诉 klogd 不要读取内核和模块符号,而只是转储未翻译的保护错误消息。
Linux 初学者不太可能遇到这种情况,但更有经验的用户通常会在他们的系统上同时拥有多个可启动内核。如果该机器是业余爱好者或内核黑客的机器,则它可能具有许多稳定系列内核和许多开发系列内核。我自己总是将三个世代的稳定内核保存在我的系统上,这样如果出现错误,我可以立即重新启动到旧内核中。
当 klogd 启动时,它会识别内核版本(自 1.3.43 以来的所有内核都将版本信息放在映射文件中),然后查看
/boot/System.map /System.map /usr/src/linux/System.map
如果可能,它将使用内核版本信息来选择正确的内核版本。当我在机器上安装了开发系列内核时,我将稳定内核映射保留在 /boot 中,并将开发内核映射保留在 /usr/src/linux 中。至于我的两个“旧”稳定内核,我只能忍受 klogd 将无法解析故障事件中的地址这一事实(如果我启动到它们)。请记住,如果构建了它们的存档,则可以使用 klogd 上的 -k 开关来强制它使用特定的映射文件。
当您阅读此讨论时,请记住这些事件非常罕见,在 15 个机器年(三台机器 24x7 运行 Linux 五年)中,我只见过两次这种情况,而且两次都是由于硬件故障引起的。
除了命令行开关外,klogd 还会响应某些信号。您可以使用 kill 命令发送信号。
klogd 响应的信号有
SIGTSTP/SIGCONT
SIGTSTP 暂停,SIGCONT 恢复内核日志记录。恢复包括重新初始化,因此您可以使用它来例如卸载 /proc 文件系统,而无需杀死 klogd
kill -TSTP <pid> umount /proc kill -CONT <pid>
SIGUSR1/SIGUSR2
有关更多信息,请参阅内存地址解析部分。
SIGINT/SIGHUP/SIGKILL/SIGTERM
这些信号都优雅地关闭 klogd。
