linux内核日志的存储与读取
linux kernel中可以通过printk输出日志,那么日志内容到底会被存放在哪里以及怎么读取呢?
printk
通过查看linux-source/kernel/printk/printk.c中的源码可知,printk函数一路通过vprintk_func、vprintk_default、
vprintk_emit、vprintk_store、log_output调用到log_store函数,其中某些函数执行日志格式解析的功能不是本文关注重点,不再提及
log_store函数的功能就是执行存储一条日志的动作,从下图中的源码可以看出是将每条结构为printk_log的日志条目存储到log_buf中
log_next_idx是下一条日志存放的相对于log_buf起始位置的偏移量,log_next_seq是下一条日志的序列号
log_buf可以看到其是一个全局变量,其大小是1 << CONFIG_LOG_BUF_SHIFT,CONFIG_LOG_BUF_SHIFT定义在init/Kconfig中其值一般是14,即log_buf一般为16KB,那么16KB就足够存放所有kernel日志了吗? 感觉有点不可信啊
在刚才找到CONFIG_LOG_BUF_SHIFT的定义时,发现下一个定义是LOG_CPU_MAX_BUF_SHIFT,其解释是”CPU kernel log buffer size contribution (13 => 8 KB, 17 => 128KB)”。哎?这个好像也是log buf大小的相关数,那么是怎么回事呢?
printk.c中的log_buf_add_cpu使用到了这个值,通过调用log_buf_len_update更新了全局变量new_log_buf_len的大小
#define __LOG_CPU_MAX_BUF_LEN (1 << CONFIG_LOG_CPU_MAX_BUF_SHIFT)
cpu_extra = (num_possible_cpus() - 1) * __LOG_CPU_MAX_BUF_LEN;
log_buf_len_update(cpu_extra + __LOG_BUF_LEN);
setup_log_buf函数中会对buf进行更新大小,更新方法就是新申请一块内存,将log_buf的地址指向新申请的地址
if (!early && !new_log_buf_len)
log_buf_add_cpu();
if (!new_log_buf_len)
return;
new_log_buf = memblock_alloc(new_log_buf_len, LOG_ALIGN);
log_buf_len = new_log_buf_len;
log_buf = new_log_buf;
setup_log_buf是在init/main.c中的start_kernel函数被调用的。
这样做的原因应该是为了保证在kernel被完全启动前的日志也能够保存,kernel启动后会增大log buf的大小保证运行中的日志保存
读取方法
log buf中读取的方法有好几种,如下图所示,可以通过glibc封装的sys_syslog系统调用klogctl函数来读取,也可以通过读取/proc/kmsg来读取,或者是操作/dev/kmsg来获取log
syslog系统调用
我们一般使系统调用都是通过glibc的封装,其对sys_syslog的系统调用封装的函数并不叫syslog,而是klogctl
能过syslog系统调用的定义代码发现,其主要是调用了do_syslog函数来实现的功能
SYSCALL_DEFINE4(syslog, int, type, char __user *, buf, int, len)
{
return do_syslog(type, buf, len, SYSLOG_FROM_READER);
}
do_syslog会判断type的类型,执行不同的操作,例如SYSLOG_ACTION_READ会调用syslog_print将log buf中的log输出到用户态buf
case SYSLOG_ACTION_READ: /* Read from log */
if (!buf || len < 0)
return -EINVAL;
if (!len)
return 0;
if (!access_ok(buf, len))
return -EFAULT;
error = wait_event_interruptible(log_wait,
syslog_seq != log_next_seq);
if (error)
return error;
error = syslog_print(buf, len);
break;
/proc/kmsg
linux在启动时会建立/proc文件系统,它是一个伪文件系统,它只存在内存当中,而不占用外存空间
它以文件系统的方式为访问系统内核数据的操作提供接口,/proc/kmsg提供的就是访问log buf的接口
static int kmsg_open(struct inode * inode, struct file * file)
{
return do_syslog(SYSLOG_ACTION_OPEN, NULL, 0, SYSLOG_FROM_PROC);
}
代码比较简单,如上
/dev/kmsg
/dev目录是设备目录,不同于/proc/kmsg的是,/dev/kmsg可以做seek、read历史log等更复杂的操作,原因是
对/dev/kmsg的operation会触发printk.c中其对应操作的回调函数,例如read /dev/kmsg的话则会调用到devmsg_read函数
const struct file_operations kmsg_fops = {
.open = devkmsg_open,
.read = devkmsg_read,
.write_iter = devkmsg_write,
.llseek = devkmsg_llseek,
.poll = devkmsg_poll,
.release = devkmsg_release,
};
devmsg_read函数源码中也位于printk.c中,其并没有调用do_syslog函数,而是直接操作的log_buf全局变量进行读取,因此行为于上述两种都有不同
本文并未关注太多细节,重点在于整体框架