Android7.0 klog机制(如何将android log打印到kernel log中)

本文深入探讨Android系统中init进程的Log输出机制,重点分析klog组件如何根据不同级别筛选和输出Log信息,并介绍如何自定义Log级别以解决特定场景下的问题。

    在分析Android7.0 init进程一文中提到,在init进程中是通过klog来输出log信息的,但是由于log的级别不同可能导致有些添加的log无法输出来。在init .cpp的main函数中初始化klog。

    klog_init();          //初始化klog
    klog_set_level(KLOG_NOTICE_LEVEL);   //设置klog的级别为NOTICE
system/core/libcutils/klog.c

void klog_init(void) {
    if (klog_fd >= 0) return; /* Already initialized */      //klog_fd默认值为-1,如果大于等于1表示klog已经初始化过,返回

    klog_fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);   //打开/dev/kmsg文件,获得文件描述符
    if (klog_fd >= 0) {                      //大于等于0表示,打开成功,init进程的log都会输出到kernel log中
        return;  
    }

    static const char* name = "/dev/__kmsg__"; 
    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {   //如果上面没有打开成功,创建/dev/_kmsg_文件
        klog_fd = open(name, O_WRONLY | O_CLOEXEC);      //打开文件,获取文件描述符
        unlink(name);
    }
}
void klog_set_level(int level) {
    klog_level = level;                  //将设置的level赋值给klog_level
}

在init.cpp通过NOTICE函数输出log信息。但是INFO函数就输不出log,下面我们看一下具体原因。

    NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage");
在system/core/init/log.h头文件中对NOTICE,INFO等函数进行定义

#define ERROR(x...)   init_klog_write(KLOG_ERROR_LEVEL, x)
#define WARNING(x...) init_klog_write(KLOG_WARNING_LEVEL, x)
#define NOTICE(x...)  init_klog_write(KLOG_NOTICE_LEVEL, x)
#define INFO(x...)    init_klog_write(KLOG_INFO_LEVEL, x)
#define DEBUG(x...)   init_klog_write(KLOG_DEBUG_LEVEL, x)
#define VERBOSE(x...) init_klog_write(KLOG_DEBUG_LEVEL, x)
调用NOTICE函数,也就调到了函数init_klog_wirte传入的参数为NOTICE log level,如果调用别的函数就传入对应的log level。还有传入需要打印的信息。

先看一下各个log level的值,代码定义位置system/core//include/cutils/klog.h

#define KLOG_ERROR_LEVEL   3
#define KLOG_WARNING_LEVEL 4
#define KLOG_NOTICE_LEVEL  5
#define KLOG_INFO_LEVEL    6
#define KLOG_DEBUG_LEVEL   7

继续分析代码system/core/init/log.cpp

void init_klog_write(int level, const char* fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    init_klog_vwrite(level, fmt, ap);        //函数调用
    va_end(ap);
}
static void init_klog_vwrite(int level, const char* fmt, va_list ap) {
    static const char* tag = basename(getprogname());

    if (level > klog_get_level()) return;      //之前设置的level为NOTICE 5,现在level也为5相等为false,INFO level为6所以return

    // The kernel's printk buffer is only 1024 bytes.
    // TODO: should we automatically break up long lines into multiple lines?
    // Or we could log but with something like "..." at the end?
    char buf[1024];             //kernel 的printk buffer只有1024个字节, 将长的单行log,变为多行,或者直接省略掉
    size_t prefix_size = snprintf(buf, sizeof(buf), "<%d>%s: ", level, tag);
    size_t msg_size = vsnprintf(buf + prefix_size, sizeof(buf) - prefix_size, fmt, ap);
    if (msg_size >= sizeof(buf) - prefix_size) {
        msg_size = snprintf(buf + prefix_size, sizeof(buf) - prefix_size,
                            "(%zu-byte message too long for printk)\n", msg_size);
    }

    iovec iov[1];
    iov[0].iov_base = buf;
    iov[0].iov_len = prefix_size + msg_size;

    klog_writev(level, iov, 1);       //传送整理好的信息
}
system/core/libcutils/klog.c

void klog_writev(int level, const struct iovec* iov, int iov_count) {
    if (level > klog_level) return;           //由于level与klog_level都为5,所以继续执行
    if (klog_fd < 0) klog_init();               //前面已经初始化过klog_fd大于等于0
    if (klog_fd < 0) return;                     
    TEMP_FAILURE_RETRY(writev(klog_fd, iov, iov_count));   
}

//system/core/include/log/uio.h
extern int  readv( int  fd, struct iovec*  vecs, int  count );   //定义readv函数
extern int  writev( int  fd, const struct iovec*  vecs, int  count );  /定义writev函数
//system/core/liblog/uio.c
#include <log/uio.h>
LIBLOG_ABI_PUBLIC int writev(int fd, const struct iovec *vecs, int count)
{
    int   total = 0;

    for ( ; count > 0; count--, vecs++ ) {
        const char*  buf = vecs->iov_base;
        int          len = vecs->iov_len;

        while (len > 0) {
            int  ret = write( fd, buf, len ); //调用linux的write函数,fd为/dev/kmsg的文件描述符,所以将log写入到kmsg中即kernel log中。
            if (ret < 0) {
                if (total == 0)
                    total = -1;
                goto Exit;
            }
            if (ret == 0)
                goto Exit;

            total += ret;
            buf   += ret;
            len   -= ret;
        }
    }
Exit:
    return total;
}

    之前做项目时遇到一个问题,在开机过程中kernel 与 init进程中的log都输在kernel log中,而启动zygote之后log是输出在android中的,这样就发生了init进程到framework这段时间无法精确计算,对分析开机流程总耗时带来不小的困扰。所以我们就仿照klog自己定义TAG来输出信息,将android 中的log输出到kernel中,这样同一行log即在kernel log中有也在android log中有就可以精确计算时间了。

首先在system/core/include/cutils/klog.h中定义log level

	#define KLOG_PERFORMANCE_LEVEL 0              //根据之前分析,将level设置小一点,至少要小于5
	#define KLOG_BOOTINFO(tag,x...) klog_write(KLOG_PERFORMANCE_LEVEL, "<0>" tag ": " x)    //定义函数关系

定义完之后就可以在framework中使用了,

例如init进程启动完zygote服务,会进入/frameowrk/base/cmds/app_process/app_main.cpp我们可以在这里使用

#include <cutils/klog.h>              //先include对应的头文件
#define BOOTINFO(x...) KLOG_BOOTINFO("bootinfo", x)       //定义对应的函数关系

 BOOTINFO("Entered app_main.cpp main() \n");    //就可以通过BOOTINFO函数将log输出到kernel log中
不过将编译好的文件替换到手机中仍然无法输出对应log信息,经过分析发现是有与selinux权限,输出log存在selinux安全问题。

为了调试我们本地只好将selinux关闭了,代码还是在init .cpp 中我们之前将init进程时有过提到。

static void selinux_initialize(bool in_kernel_domain) {
    Timer t;

    //..............
    if (in_kernel_domain) {
       //.........

        bool kernel_enforcing = (security_getenforce() == 1);
        bool is_enforcing = selinux_is_enforcing();
        if (kernel_enforcing != is_enforcing) {
            if (security_setenforce(is_enforcing)) {     //可以直接将is_enforcing改为false,就将selinux关闭了
                ERROR("security_setenforce(%s) failed: %s\n",
                      is_enforcing ? "true" : "false", strerror(errno));
                security_failure();
            }
        }

        //.............
    } else {
        selinux_init_all_handles();
    }
}
### 如何在Android系统中打印和查看内核日志 在Android系统中,查看和打印内核日志(kernel log)可以通过多种方式实现。以下是一些常用的方法和技术细节。 #### 1. 使用 `dmesg` 命令 `dmesg` 是一个常用的工具,用于查看内核日志缓冲区的内容。在Android设备上,可以通过ADB命令执行 `dmesg` 来查看内核日志。 ```bash adb shell dmesg > kernel_dmesg.log ``` 此命令会将内核日志输出到文件 `kernel_dmesg.log` 中[^2]。 #### 2. 使用 `printk` 函数 在内核代码中,可以使用 `printk` 函数来记录日志信息。`printk` 支持不同的日志级别,这些级别定义在 `include/linux/kern_levels.h` 中。例如: - `KERN_EMERG`:紧急情况 - `KERN_ALERT`:必须立即采取行动 - `KERN_CRIT`:关键条件 - `KERN_ERR`:错误条件 - `KERN_WARNING`:警告条件 - `KERN_NOTICE`:正常但重要的条件 - `KERN_INFO`:信息性消息 - `KERN_DEBUG`:调试级别的消息 通过设置适当的日志级别,开发者可以控制哪些消息会被记录并显示。例如: ```c printk(KERN_INFO "This is an informational message\n"); ``` 这会在内核日志中生成一条信息性消息[^3]。 #### 3. 调整日志级别 为了确保内核日志能够被正确捕获,需要调整内核的日志级别。可以通过以下命令修改日志级别: ```bash adb shell "echo 8 > /proc/sys/kernel/printk" ``` 这里的数字 `8` 表示启用所有级别的日志输出。数值范围为 `0` 到 `8`,其中 `0` 表示仅显示紧急消息,而 `8` 表示显示所有级别的消息[^4]。 #### 4. 将日志保存到文件 为了便于后续分析,可以将内核日志保存到文件中。例如: ```bash adb shell dmesg > /data/kernel_dmesg.log ``` 此命令会将内核日志保存到 `/data/kernel_dmesg.log` 文件中,方便后续查阅和分析[^2]。 #### 5. 结合 `logcat` 和内核日志 在某些场景下,可能需要同时查看 Android 日志(`logcat`)和内核日志。可以通过以下命令将两者结合: ```bash adb logcat -v time > logcat.log & adb shell dmesg > kernel_dmesg.log ``` 这样可以分别获取带有时间戳的 `logcat` 日志和内核日志[^2]。 #### 6. 自定义日志输出 如果需要更精确地控制日志输出,可以仿照 klog 机制,自定义 TAG 来输出信息。这种方法可以将 Android 日志同步输出到内核日志中,从而实现更精确的时间计算和流程分析[^1]。 --- ### 示例代码 以下是一个简单的内核模块代码示例,展示了如何使用 `printk` 输出日志: ```c #include <linux/module.h> #include <linux/kernel.h> static int __init my_module_init(void) { printk(KERN_INFO "My module has been loaded\n"); return 0; } static void __exit my_module_exit(void) { printk(KERN_INFO "My module has been unloaded\n"); } module_init(my_module_init); module_exit(my_module_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Author Name"); MODULE_DESCRIPTION("A simple Linux kernel module for logging"); ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值