这个话题真的是非常简单的一个问题,以至于网上很难找到正确的解读。
最近做 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 只需两步,任何需要都满足了。
- 设置 ”
ro.logd.kernel
=true“ - 通过 ”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。
下面这段真的很乱,我看了半天+撸代码总算略微看懂了,咱们用上面的表格来阐明一下。
Value | svelte+ | Return |
---|---|---|
ro.logd.kernel =true,xxx | N/A | true |
ro.logd.kernel =false,xxx | N/A | false |
ro.logd.kernel =xxx,eng (xxx != true or false) | ro.debuggable =1 | true |
ro.logd.kernel =xxx,eng (xxx != true or false) | ro.debuggable !=1 | false |
ro.logd.kernel =xxx,svelte (xxx != true or false) | ro.config.low_ram =true | false |
ro.logd.kernel =xxx,svelte (xxx != true or false) | ro.config.low_ram =false | true |
默认 ro.logd.kernel
是没有被设置的,这时会依据ro.debuggable
和ro.config.low_ram
来决定缺省值。
svelte+ | ro.logd.kernel default value |
---|---|
ro.config.low_ram =true | false |
ro.config.low_ram !=true && ro.debuggable !=1 | false |
others | true |
所以,当 logcat 无奈抓取 Kernel Log 时,须要查看三个 property :ro.logd.kernel
、ro.config.low_ram
、ro.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;}