linux的内核日志系统

简介:

今天有同事问我在应用程序中怎么打印信息,在内核中有printk,用户空间用什么?我感到这个问题很奇怪,用printf不就可以了吗?他说他的代码在一个so中,我都无语了!他原来是做windows的,在windows中很多都是win32程序,带界面的,不是控制台程序,他大多数通过单步跟踪和断点来了解程序运行时的信息,其实吧,我倒是挺不喜欢单步或者断点的,除了特殊的调试需要,一般我都是通过打印日志来获取信息,linux有强大的日志记录系统,可以给程序一个安心的多的执行环境,日志仅仅是一个旁路的作用,起到记录个审计的作用而不会影响程序的执行,单步和断点就不同了,很多的问题你仅仅通过单步是无法发现的,特别是并发问题,不过在windows上写代码的哥们总是喜欢单步,就像他们总喜欢反汇编一样,这里面的原因就是,第一,windows程序大多数不是控制台,它实际上没有地方打印信息,第二,windows拥有强大的可视化IDE,这就给单步和断点提供了方便。

在linux中,syslogd是用户空间的一个守护进程,所有的需要记录日志的别的进程可以和这个守护进程通信,可以委托这个守护进程帮助记录日志,为何不自己记录呢,那是因为syslogd非常的专业,可以将日志记录到很细的地步,可以看etc/下面的配置文件,既然所有的进程都可以委托syslogd记录日志,那么就涉及到了进程间通信,这就是linux中另外一大块的内容了,这里就不谈了,linux善于使用小的程序组合成大的机制,linux善于分布式的思想而不是集中的,但是为何将日志都委托给一个守护进程呢,这不就集中了吗?其实这难道不是各司其职吗?因此这还是一个边界的问题。syslogd之外还有一个klogd,这两个其实不是一个层次的,syslogd是个总的日志记录器,而klogd仅仅是一个syslogd的客户端罢了,和很多需要记录日志的进程的级别一样,在需要记录日志的进程里,可以调用syslog函数将日志发送给syslogd守护进程,可是内核并不是一个进程,那么它的日志就有必要专门设置一个用户进程负责传给syslogd,这个进程就是klogd,至于klogd如何从内核得到内核日志信息,这就是它自己的事情了,其实内核专门留下了两个日志接口,一个是sys_syslog系统调用(注意不要和syslog函数混淆,syslog系统调用是和内核通信的,而syslog函数是和syslogd通信的),另一个是/proc/kmsg文件,klogd使用的是后者,当然也可以直接使用ksyslog系统调用。这里首先看一下内核日志的一些结构,首先内核日志大多数由printk产生,你也可以认为完全由printk产生,但是我们知道,printk将信息打印到了屏幕,并没有别的什么副作用,实际上并不是仅仅发生了我们看到的事情,实际上printk并不是将信息直接写入了标准输出文件,而仅仅将信息塞入了一个环形缓冲区,,然后至于这个缓冲区的内容什么时候显示到标准输出,那就是另外一个事情了,而内核日志就是存在于这个环形缓冲区,打印到屏幕的仅仅是一个缓冲区副本,真正的数据还在缓冲区保留,只有到了klogd或者别的什么调用syslog系统调用将信息读出时或者缓冲区满了(环形缓冲区首尾相接)的时候才会清除部分数据或者全部数据,刚才说了有两种方式来得到内核日志,一个是/proc/kmsg。一个是系统调用,幸运的是,这两个都是调用了do_syslog内核函数:

int do_syslog(int type, char __user * buf, int len)

{

unsigned long i, j, limit, count;

int do_clear = 0;

char c;

int error = 0;

error = security_syslog(type);

if (error)

return error;

switch (type) {

...

case 2:

...

error = wait_event_interruptible(log_wait, (log_start - log_end)); //如果没有数据,那么就睡眠等待。

if (error)

goto out;

i = 0;

spin_lock_irq(&logbuf_lock); //如果要往外读数据,不希望在读的过程中数据发生变化,因此必须锁住环形数据缓冲区

while (!error && (log_start != log_end) && i < len) { //这里才是内核环形缓冲区真正消耗的地方。

c = LOG_BUF(log_start); //得到一个字符

log_start++; //只有在这里才逻辑上清除环形缓冲区的数据

spin_unlock_irq(&logbuf_lock);

error = __put_user(c,buf);

buf++;

i++;

spin_lock_irq(&logbuf_lock);

}

spin_unlock_irq(&logbuf_lock);

...

}

我们看一下printk调用的vprintk内核函数:

asmlinkage int vprintk(const char *fmt, va_list args)

{

unsigned long flags;

int printed_len;

char *p;

static char printk_buf[1024];

static int log_level_unknown = 1;

...

spin_lock_irqsave(&logbuf_lock, flags); //这个logbuf_lock自旋锁其实很重要,确保一次内核信息写的过程不会有别的写过程来捣乱,否则很多信息会揉在一起,比如hello和 world会变为hwellorold。

printed_len = vscnprintf(printk_buf, sizeof(printk_buf), fmt, args);

for (p = printk_buf; *p; p++) {

if (log_level_unknown) {

if (p[0] != '<' || p[1] < '' || p[1] > '7' || p[2] != '>') {

emit_log_char('<');

emit_log_char(default_message_loglevel + '');

emit_log_char('>');

}

log_level_unknown = 0;

}

emit_log_char(*p);

if (*p == '/n')

log_level_unknown = 1;

}

if (!cpu_online(smp_processor_id()) && system_state != SYSTEM_RUNNING) {

spin_unlock_irqrestore(&logbuf_lock, flags);

goto out;

}

if (!down_trylock(&console_sem)) { //如果现在可以得到设备的信号量,那么马上将数据显示到终端

console_locked = 1;

spin_unlock_irqrestore(&logbuf_lock, flags);

console_may_schedule = 0;

release_console_sem(); //该函数具体显示数据

} else {

spin_unlock_irqrestore(&logbuf_lock, flags);//本次的内核信息已经写入了内核环形缓冲,为了不阻碍别人写,释放掉自旋锁

}

out:

return printed_len;

}

内核的环形缓冲有两个用途,一个是直接打印在屏幕上,还有一个就是可能用户空间的内核日志程序正在运行,因此这个函数完成了两个作用:

void release_console_sem(void)

{

unsigned long flags;

unsigned long _con_start, _log_end;

unsigned long wake_klogd = 0;

for ( ; ; ) { //打印向屏幕,注意,打印的时候也要确保logbuf_lock被拥有,因为不想在打印过程中发生缓冲区变化的事情

spin_lock_irqsave(&logbuf_lock, flags);

wake_klogd |= log_start - log_end; //确保有数据,这个变量还作为是否唤醒do_syslog的睡眠队列的依据。

if (con_start == log_end)

break; /* Nothing to print */

_con_start = con_start;

_log_end = log_end;

con_start = log_end; //注意,log_end和log_start并没有变化,而是副本变化了,这是因为下面还要用到内核缓冲,不能因为写到了屏幕,信息就消耗了。

spin_unlock_irqrestore(&logbuf_lock, flags);

call_console_drivers(_con_start, _log_end);

}

console_locked = 0;

console_may_schedule = 0;

up(&console_sem);

spin_unlock_irqrestore(&logbuf_lock, flags);

if (wake_klogd && !oops_in_progress && waitqueue_active(&log_wait)) //如果内核唤醒缓冲中有数据,那么就唤醒睡眠队列

wake_up_interruptible(&log_wait);

}

最后我们看一下procfs导出的kmsg接口的read函数,其实也是调用了do_syslog函数:

static ssize_t kmsg_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)

{

if ((file->f_flags & O_NONBLOCK) && !do_syslog(9, NULL, 0))

return -EAGAIN;

return do_syslog(2, buf, count);

}

这里我觉得有一个问题,就是在printk的时候,如果我们恰好可以得到设备的信号量,那么就必须等到信息完全显示到设备上时调用栈才可以返回,也就是说,最好不要在内核大量调用printk函数,另外,在多处理器上这也是很不利的,以前的处理器少,因此问题不大,现在都是多处理器了,性能就可能受printk的影响了,我觉得可以为每一个cpu准备一个环形缓冲区,然后在拼接这些缓冲区的时候进行控制,比如在用户空间读每个cpu的缓冲区,只不过将它们作为一个整体去理解,这个我感觉可以用文件系统实现。在任何程序中频繁调用打印都是不妙的,因为打印涉及设备操作,想想看,外设相比cpu多么慢,而且对于机器中的唯一设备,而且是每个进程几乎都在用的设备,我们花在打架上的时间又是何等的长。

回归同事问我的那个问题,在linux上怎么打印信息,其实,只要你不关闭,每个程序都会默认打开0,1,2三个文件描述符,这三个描述符在start_kernel中调用的kernel_init进程(最终成为/sbin/init)中被初始化,也就是在下面的代码:

if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)

printk(KERN_WARNING "Warning: unable to open an initial console./n");

(void) sys_dup(0);

(void) sys_dup(0);

在这里,sys_open打开了/dev/console,将其作为标准输出,其实也就是我们的终端,当前终端,在sys_open中会自动将文件描述符0分配给这个终端文件,然后紧接着的sys_dup相当于复制了这个终端文件描述符代表的文件给了新的描述符1和2,这样的话实际上0,1,2代表的是同一个东西,都是指当前的终端,这也就是为什么你往标准错误里面写东西也不会错的道理,实际上标准输入,输出,错误都是给用户进程用的,对于内核,它们是一个东西。因为linux进程都是fork出来的,根源就是这个kernel_init进程,于是每个进程都会默认复制父进程的0,1,2文件描述符(如果父进程没有关闭它们的话)。



 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1274024

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
12天前
|
存储 Linux 数据处理
探索Linux操作系统的内核与文件系统
本文深入探讨了Linux操作系统的核心组件,包括其独特的内核结构和灵活的文件系统。文章首先概述了Linux内核的主要功能和架构,接着详细分析了文件系统的工作原理以及它如何支持数据存储和检索。通过比较不同的文件系统类型,本文旨在为读者提供一个关于如何根据特定需求选择合适文件系统的参考框架。
|
16天前
|
安全 算法 网络协议
探索Linux操作系统的内核管理
【5月更文挑战第31天】本文将深入探讨Linux操作系统的内核管理机制,包括其设计原则、主要组件以及它们如何协同工作以提供高效的系统性能。通过分析Linux内核的关键特性和功能,我们将揭示这一开源操作系统如何在各种计算环境中保持其稳定性和灵活性。
|
17天前
|
机器学习/深度学习 人工智能 负载均衡
深度解析:Linux内核调度策略的演变与优化
【5月更文挑战第30天】 随着计算技术的不断进步,操作系统的性能调优成为了提升计算机系统效率的关键。在众多操作系统中,Linux因其开源和高度可定制性而备受青睐。本文将深入剖析Linux操作系统的内核调度策略,追溯其历史演变过程,并重点探讨近年来为适应多核处理器和实时性要求而产生的调度策略优化。通过分析比较不同的调度算法,如CFS(完全公平调度器)、实时调度类和批处理作业的调度需求,本文旨在为系统管理员和开发者提供对Linux调度机制深层次理解,同时指出未来可能的发展趋势。
|
27天前
|
算法 安全 Linux
探索Linux内核的虚拟内存管理
【5月更文挑战第20天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分之一——虚拟内存管理。通过剖析其关键组件和运作机制,揭示虚拟内存如何提供高效的内存抽象,支持庞大的地址空间,以及实现内存保护和共享。文章将重点讨论分页机制、虚拟内存区域(VMAs)的管理、页面置换算法,并简要分析这些技术是如何支撑起现代操作系统复杂而多变的内存需求的。
|
5天前
|
Linux 编译器 C语言
编译Linux内核:基础、重要性和交叉编译方法
Linux内核作为操作系统的心脏,负责管理计算机的硬件资源,同时也是运行所有其他程序的基础。理解如何编译Linux内核对于系统管理员、开发者乃至高级用户来说都是一项极其宝贵的技能。本文将介绍编译Linux内核的基本知识、编译的重要性、具体步骤以及交叉编译的概念。
15 0
|
8天前
|
SQL Oracle 关系型数据库
探索 Linux 命令 `db_archive`:Oracle 数据库归档日志的工具
探索 Linux 中的 `db_archive`,实际与 Oracle 数据库归档日志管理相关。在 Oracle 中,归档日志用于恢复,当在线重做日志满时自动归档。管理员可使用 SQL*Plus 查看归档模式,通过 `RMAN` 进行备份和恢复操作。管理归档日志需谨慎,避免数据丢失。了解归档管理对 Oracle 管理员至关重要,确保故障时能快速恢复数据库。
|
8天前
|
存储 关系型数据库 Linux
探索 `db_printlog`:Linux 下的数据库日志打印工具
`db_printlog`是Linux下用于解析和打印Berkeley DB日志文件的命令行工具,帮助用户以可读格式理解数据库事务和系统事件。要使用它,先安装Berkeley DB,然后通过`db_printlog logfilename`命令分析日志。常见选项包括`-h`(显示帮助)、`-v`(详细输出)和`-f`(跟随日志更新)。注意日志文件权限和大小问题,此工具适用于Berkeley DB,其他数据库系统可能有不同解析工具。
|
10天前
|
关系型数据库 分布式数据库 数据库
PolarDB产品使用合集之诊断内核问题时,除了增加日志还有什么诊断手段
PolarDB是阿里云推出的一种云原生数据库服务,专为云设计,提供兼容MySQL、PostgreSQL的高性能、低成本、弹性可扩展的数据库解决方案,可以有效地管理和优化PolarDB实例,确保数据库服务的稳定、高效运行。以下是使用PolarDB产品的一些建议和最佳实践合集。
|
10天前
|
运维 NoSQL Ubuntu
深入理解Linux中的"crash"命令:内核崩溃的调试利器
`crash`是Linux内核崩溃调试工具,用于分析内核崩溃转储文件,提供GDB-like的交互式CLI。通过加载`vmcore`文件和内核映像,管理员可以查看系统状态、调用栈、内存布局等。安装`crash`可使用包管理器,如`apt-get`或`yum/dnf`。尽管有学习曲线且依赖转储文件,但`crash`在系统故障排查中极其重要。
|
12天前
|
Shell Linux
linux shell 脚本实现:根据文件内容中的每行分隔符放入数组,根据规则打印日志并重新创建目录 备份文件
linux shell 脚本实现:根据文件内容中的每行分隔符放入数组,根据规则打印日志并重新创建目录 备份文件

热门文章

最新文章