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全局变量进行读取,因此行为于上述两种都有不同

本文并未关注太多细节,重点在于整体框架