这个话题真的是非常简单的一个问题,以至于网上很难找到正确的解读。

最近做 Android 零碎开发,零碎开发通常要建设本人的 Log 零碎,抓取 Android Log、Kernel Log 和其余一些特定的 Log。对于 Kernel Log 的需要也很简略,

  • 抓取 Kernel 打印的 Log,存储到文件中。
  • Log 应用墙上时钟(wall clock)显示工夫戳,这样不便问题剖析。

看似简略的需要却失去形形色色的实现,可见码农的创造力有限。一个计划是抓取 "/proc/kmsg" 信息,但它不是以墙上时钟显示的,又在 Kernel 中定时打印墙上工夫用于对照。另一个计划是应用 “dmesg -T -c” 来抓取信息,这样是能失去墙上工夫的 Log,但它清空了 Kernel Log buffer,等于是造成了毁坏。还有网络上形容的各种解决方案。看到这些办法时,我就蛊惑了,一个简略正确的获取 Kernel Log 的办法难道不是通过 Logcat 获取吗?产生了什么导致很多人并不知道这个办法,并且网上也很少提及。

正确的读取 Kernel Log

先说论断,偷懒的就省得看前面了。从 Android 6.0开始,收集 Kernel Log 只需两步,任何需要都满足了。

  1. 设置 ”ro.logd.kernel=true“
  2. 通过 ”logcat -b kernel“ 抓取。

接下来说一下具体的影响因素。原本在 debuggable 版本的零碎,能够上面的命令读取 Kernel Log,

# logcat -b kernel

然而~~~,你会发现在许多零碎中下面的命令并不能读到信息,因为它还受一些 property 的限度。在源码中的一个文档记录了与 logd 相干的 property,摘取 Kernel Log 相干的形容如下。

file: /system/core/logd/README.propertyThe properties that logd and friends react to are:name                       type default  descriptionro.logd.kernel             bool+ svelte+ Enable klogd daemon......ro.debuggable              number        if not "1", logd.statistics &                                         ro.logd.kernel default false.......ro.config.low_ram          bool   false  if true, logd.statistics,                                         ro.logd.kernel default false,                                         logd.size 64K instead of 256K.......NB:- auto - managed by /init- bool+ - "true", "false" and comma separated list of "eng" (forced false if  ro.debuggable is not "1") or "svelte" (forced false if ro.config.low_ram is  true).- svelte - see ro.config.low_ram for details.- svelte+ - see ro.config.low_ram and ro.debuggable for details.......

下面形容表明,ro.logd.kernel 用于管制是否使能 klogd 模块,klogd 就是用来采集 Kernel Log 的过程。ro.logd.kernel 的值为 bool+,能够设置为 "true"、"false" 或者通过逗号分隔符连贯 "eng" 或 "svelte"。设置为 "eng" 时,如果 ro.debuggable 不为 "1",则 ro.logd.kernel 为 false。 设置为 "svelte" 时,如果 ro.config.low_ram 为 "true",则 ro.logd.kernel 为 false。

下面这段真的很乱,我看了半天+撸代码总算略微看懂了,咱们用上面的表格来阐明一下。

Valuesvelte+Return
ro.logd.kernel=true,xxxN/Atrue
ro.logd.kernel=false,xxxN/Afalse
ro.logd.kernel=xxx,eng (xxx != true or false)ro.debuggable=1true
ro.logd.kernel=xxx,eng (xxx != true or false)ro.debuggable!=1false
ro.logd.kernel=xxx,svelte (xxx != true or false)ro.config.low_ram=truefalse
ro.logd.kernel=xxx,svelte (xxx != true or false)ro.config.low_ram=falsetrue

默认 ro.logd.kernel 是没有被设置的,这时会依据ro.debuggablero.config.low_ram来决定缺省值。

svelte+ro.logd.kernel default value
ro.config.low_ram=truefalse
ro.config.low_ram!=true && ro.debuggable!=1false
otherstrue

所以,当 logcat 无奈抓取 Kernel Log 时,须要查看三个 property :ro.logd.kernelro.config.low_ramro.debuggable 的状态。而须要强制关上 Kernel Log 时,只有设置 ”ro.logd.kernel=true“。

为什么无人提及

首先,这个办法切实太简略了,简略的都不值得去认真剖析它。但为何还有很多人不晓得呢?我遇到的状况少数都是因为历史继承性引起的。在 Android 6.0 之前,抓取 Kernel Log 只能本人想方法,于是码农们实现了各种各样的形式来获取 Log,并加上工夫戳。然而当零碎版本升级时,这部分抓取 Log 日志的零碎做为可贵的自研代码保留了下来,之后始终继承着。局部零碎素来没从新扫视这部分代码的合理性,因为它统一能用,就接着用。相似的问题在许多代码中都存在,很难有人跳进去重新整理。

代码剖析

简略剖析一下 Kernel Log 相干的代码,要不这个文档就更没有价值了。Android 的 Log 都由 logd 来治理,Kernel Log 当然也时一样,只是多开拓了一个 kernel buffer。

file: /system/core/logd/main.cpp    int main(int argc, char* argv[]) {    ......    static const char dev_kmsg[] = "/dev/kmsg"; //应用这个节点来输入logd中的打印    fdDmesg = android_get_control_file(dev_kmsg);    ......    bool klogd = __android_logger_property_get_bool( //获取klogd状态        "ro.logd.kernel",        BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);    if (klogd) {        static const char proc_kmsg[] = "/proc/kmsg"; //用来读取Kernel Log的节点        fdPmesg = android_get_control_file(proc_kmsg);        ......    }    ......    logBuf = new LogBuffer(times); //创立logBuffer,用于保留日志    ......    //LogReader监听/dev/socket/logdr,logcat通过它来获取日志    LogReader* reader = new LogReader(logBuf);     if (reader->startListener()) {        exit(1);    }    ......    //LogListener监听/dev/socket/logdw,用于写入日志    LogListener* swl = new LogListener(logBuf, reader);    if (swl->startListener(600)) {        exit(1);    }    // CommandListener监听/dev/socket/logd,用于命令传输    CommandListener* cl = new CommandListener(logBuf, reader, swl);    if (cl->startListener()) {        exit(1);    }    ......    LogKlog* kl = nullptr; //LogKlog用于存储Kernel Log    if (klogd) {        kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != nullptr);    }    readDmesg(al, kl); //启动时读取kernel buffer中缓存的日志    if (kl && kl->startListener()) { //启动Kernel log监听        delete kl;    }    ......}

重点的几个中央:

  • 关上 "/dev/kmsg" 用来输入 logd 中的一些日志。这个设计挺奇妙的,在 logd 筹备好之前,日志是无奈写入 Android Log buffer 中的。这里将日志写入 "/dev/kmsg" 中,等于写入了 Kernel Log buffer 中,通过 dmesg 或 "/proc/kmsg" 就能够获取相干日志。
  • 应用 "/proc/kmsg" 来获取 Kernel Log。"/proc/kmsg" 会记录上次读取的地位,与 dmesg 不同,读取时不会输入整个 buffer 内容。只有监听这个节点就能够实时获取 Kernel Log。
  • LogKlog 是一个 socket listener,用来监听 "/proc/kmsg",而后将 Kernel Log 写入到 Android Log buffer 中。

接下来简略看一下 Kernel Log 真正写入的代码,

file: /system/core/logd/LogKlog.cpp    // 当监听到有 Kernel Log 写入时会调用该办法    bool LogKlog::onDataAvailable(SocketClient* cli) {    ......    char buffer[LOGGER_ENTRY_MAX_PAYLOAD]; //每次最多读取4068字节的日志    ssize_t len = 0;    for (;;) {        ssize_t retval = 0;        if (len < (ssize_t)(sizeof(buffer) - 1)) {            retval = //读取 "/proc/kmsg" 中的日志                read(cli->getSocket(), buffer + len, sizeof(buffer) - 1 - len);        }        ......        for (char *ptr = nullptr, *tok = buffer;             !!(tok = android::log_strntok_r(tok, len, ptr, sublen));             tok = nullptr) {            ......            if ((sublen > 0) && *tok) {                log(tok, sublen); // 一行行的将日志写入 buffer            }        }    }    return true;}......// 写入日志,对 Kernel Log 做了大量解析,转换为 Android Log 格局。// 可能 Kernel Log 太随便了,这段代码看起来都晕,大抵了解一下。int LogKlog::log(const char* buf, ssize_t len) {    // 如果 auditd 在运行,它会记录相干日志,所以在 LogKlog 不会重复记录该日志。    if (auditd && android::strnstr(buf, len, auditStr)) {        return 0;    }        // 解析 Kernel Log 中的优先级信息    const char* p = buf;    int pri = parseKernelPrio(p, len);        // 解析工夫信息    log_time now;    sniffTime(now, p, len - (p - buf), false);    ......    // 解析 pid、tid、uid    const pid_t pid = sniffPid(p, len - (p - buf));    const pid_t tid = pid;    uid_t uid = AID_ROOT;    if (pid) {        logbuf->wrlock();        uid = logbuf->pidToUid(pid);        logbuf->unlock();    }        // 解析 Kernel Log 中的 tag 信息    while ((p < &buf[len]) && (isspace(*p) || !*p)) {        ++p;    }    ......    // 依据解析的信息创立 Android Log    char newstr[n];    char* np = newstr;        // 将优先级转换为 Android Log 的优先级格局    *np = convertKernelPrioToAndroidPrio(pri);    ++np;        // 复制解析的 tag 信息    memcpy(np, tag, taglen);    np += taglen;    *np = '\0';    ++np;    // 复制主信息    memcpy(np, p, b);    np[b] = '\0';        // 工夫修改    if (!isMonotonic()) {        ......    }    // 将日志写入 LOG_ID_KERNEL buffer 中    int rc = logbuf->log(LOG_ID_KERNEL, now, uid, pid, tid, newstr,                         (unsigned short)n);    // 告诉 readers    if (rc > 0) {        reader->notifyNewLog(static_cast<log_mask_t>(1 << LOG_ID_KERNEL));    }    return rc;}