Logd框架梳理
- 基于 Android P进行流程梳理
1. 整体框架
-
Android P的logd总体框架图如下所示:
和旧版本方案的差异,主要就是去掉了logger驱动,引入了logd进程,将logbuffer放到了用户空间
-
logd部分各模块功能如下图表所示:
模块 功能 LogReader线程 监听 /dev/socket/logdr,将日志发送给client端 logListener线程 监听 /dev/socket/logdw,将日志保存到logBuffer中,还会在有日志存入时通知 LogReader线程 CommandListener线程 监听 /dev/socket/logd,用来响应logcat的指令 LogBuffer 用作日志缓存,会将 LogListener中接收的日志,转换成logBufferElement,并按时间顺序插入 mLogElements中 LogBufferElement 日志单位成员,每个Element表示一行log LogStatistics 用作日志统计,可通过 logcat -s查看 LogAudit 接收kernel selinux相关事件,默认关闭 LogKlog 接收内核日志 LogWhiteBlackList 日志黑白名单,LogBuffer满时,会根据其进行删除,删除的优先级是:黑名单>默认>白名单
2. 流程梳理
1. Logd进程启动
-
logd进程是在Android系统启动时,init进程通过解析 init.rc 文件启动的,大致分析一下启动流程
# system/core/rootdir/init.rc on post-fs # Load properties from # /system/build.prop, # /odm/build.prop, # /vendor/build.prop and # /factory/factory.prop load_system_props # start essential services # 开始启动 logd.rc start logd start servicemanager start hwservicemanager start vndservicemanager
- 在 init.rc 中启动 logd.rc
# system/core/logd/logd.rc # 启动logd 进程 service logd /system/bin/logd # 启动三个socket socket logd stream 0666 logd logd socket logdr seqpacket 0666 logd logd socket logdw dgram+passcred 0222 logd logd file /proc/kmsg r file /dev/kmsg w user logd group logd system package_info readproc writepid /dev/cpuset/system-background/tasks # 启动 logd-reinit service logd-reinit /system/bin/logd --reinit # 开机只启动一次 oneshot disabled user logd group logd writepid /dev/cpuset/system-background/tasks on fs write /dev/event-log-tags "# content owned by logd" chown logd logd /dev/event-log-tags chmod 0644 /dev/event-log-tags
- 在 logd.rc 中,会启动 logd 进程,并创建三个socket,启动完成后,还会启动 logd-reinit;首先先看一下 logd 的启动
# system/core/logd/main.cpp //开始启动logd进程 int main(int argc, char* argv[]) { //设置环境变量 setenv("TZ", "UTC", 1); // issue reinit command. KISS argument parsing. if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) { return issueReinit(); } //路径 /dev/kmsg 下获取 file对象,返回 fd static const char dev_kmsg[] = "/dev/kmsg"; fdDmesg = android_get_control_file(dev_kmsg); if (fdDmesg < 0) { fdDmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY | O_CLOEXEC)); } int fdPmesg = -1; //属性值获取,决定是否创建 /proc/kmsg 下的 file 对象 bool klogd = __android_logger_property_get_bool( "ro.logd.kernel", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE); if (klogd) { static const char proc_kmsg[] = "/proc/kmsg"; fdPmesg = android_get_control_file(proc_kmsg); if (fdPmesg < 0) { fdPmesg = TEMP_FAILURE_RETRY( open(proc_kmsg, O_RDONLY | O_NDELAY | O_CLOEXEC)); } if (fdPmesg < 0) android::prdebug("Failed to open %s\n", proc_kmsg); } // Reinit Thread sem_init(&reinit, 0, 0); sem_init(&uidName, 0, 0); sem_init(&sem_name, 0, 1); pthread_attr_t attr; //初始化线程,成功返回0,命中if if (!pthread_attr_init(&attr)) { struct sched_param param; memset(¶m, 0, sizeof(param)); pthread_attr_setschedparam(&attr, ¶m); pthread_attr_setschedpolicy(&attr, SCHED_BATCH); if (!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) { pthread_t thread; reinit_running = true; //创建reinit线程,用来监测是否存在 reinit 请求 if (pthread_create(&thread, &attr, reinit_thread_start, nullptr)) { reinit_running = false; } } pthread_attr_destroy(&attr); } //判断当前的开发环境 bool auditd = __android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE); //设置相关运行时优先级、权限等 if (drop_privs(klogd, auditd) != 0) { return -1; } LastLogTimes* times = new LastLogTimes(); //存放log数据的buf logBuf = new LogBuffer(times); //发送 reinit 信号 signal(SIGHUP, reinit_signal_handler); //根据 logd.statistics 属性值 设置相关标志 if (__android_logger_property_get_bool( "logd.statistics", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_PERSIST | BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE)) { logBuf->enableStatistics(); } //启动各个 log 监听器 //创建socket 监听,监听的节点是/dev/socket/logdr,监听的客户端的连接,用作读取 LogReader* reader = new LogReader(logBuf); if (reader->startListener()) { exit(1); } //监听 /dev/socket/logdw,写入位置是 logbuf,写入会通知 reader,监听的是日志的写入 LogListener* swl = new LogListener(logBuf, reader); // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value if (swl->startListener(600)) { exit(1); } //监听 /dev/socket/logd,监听的是log指令 CommandListener* cl = new CommandListener(logBuf, reader, swl); if (cl->startListener()) { exit(1); } //auditd是 ro.logd.auditd 属性值,这个是用作监听selinux启动的日志 LogAudit* al = nullptr; if (auditd) { al = new LogAudit(logBuf, reader, __android_logger_property_get_bool( "ro.logd.auditd.dmesg", BOOL_DEFAULT_TRUE) ? fdDmesg : -1); } //klogd是 ro.logd.kernel 属性值,LogKlog是用来读取内核消息的 LogKlog* kl = nullptr; if (klogd) { kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != nullptr); } //设置al和kl为读取 kernel和selinux log readDmesg(al, kl); // failure is an option ... messages are in dmesg (required by standard) if (kl && kl->startListener()) { delete kl; } if (al && al->startListener()) { delete al; } TEMP_FAILURE_RETRY(pause()); exit(0); }
- 可以看到,在 logd 进程的启动过程中,主要完成了这样几件事:
- 打开 /dev/kmsg 节点(根据配置决定是否打开 /proc/kmsg 节点)
- 创建reinit线程,用来监测是否存在 reinit 请求,初始化 logBuffer
- 设置相关运行时优先级、权限
- 启动各个 log 监听器
- /dev/socket/logdr:监听的客户端的连接,用作读取
- /dev/socket/logdw:监听的是日志的写入
- /dev/socket/logd:监听的是log指令
- 根据属性判断创建
LogKlog
和LogAudit
至此就完成了 logd 进程的启动,时序图如下:
-
这里再拓展一下,看listener的启动,以
LogListener
为例,如上启动的方式是:swl->startListener(600)
,LogListener
的父类是SocketListener
# system/core/libsysutils/src/SocketListener.cpp int SocketListener::startListener(int backlog) { ... //创建线程,并启动线程 if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) { SLOGE("pthread_create (%s)", strerror(errno)); return -1; } return 0; } void *SocketListener::threadStart(void *obj) { SocketListener *me = reinterpret_cast<SocketListener *>(obj); //启动监听 me->runListener(); pthread_exit(NULL); return NULL; } void SocketListener::runListener() { SocketClientCollection pendingList; //进入死循环 while(1) { ... //当列表中存在事件时,调用到 onDataAvailable() 进行处理 while (!pendingList.empty()) { /* Pop the first item from the list */ it = pendingList.begin(); SocketClient* c = *it; pendingList.erase(it); /* Process it, if false is returned, remove from list */ if (!onDataAvailable(c)) { release(c, false); } c->decRef(); } } }
- 可以看到,当监听到事件,会使用
onDataAvailable()
回调函数进行处理
- 可以看到,当监听到事件,会使用
2. Logd日志写入
-
在Java层,写入日志使用的通常是 android/util/Log.java 类中的
log.d()
方法(先不区分等级),那么就由此开始梳理写入的流程# framework/base/core/java/android/util/Log.java public static int e(String tag, String msg) { //直接就是调用的native方法往下传递 return println_native(LOG_ID_MAIN, ERROR, tag, msg); }
- java层并未做什么操作,直接传递到了JNI部分
# framework/base/core/jni/android_util_Log.cpp static jint android_util_Log_println_native(JNIEnv* env, jobject clazz, jint bufID, jint priority, jstring tagObj, jstring msgObj) { const char* tag = NULL; const char* msg = NULL; //依次取出相关参数 if (msgObj == NULL) { jniThrowNullPointerException(env, "println needs a message"); return -1; } ... //继续往下传 int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg); if (tag != NULL) env->ReleaseStringUTFChars(tagObj, tag); env->ReleaseStringUTFChars(msgObj, msg); return res; }
__android_log_buf_write()
就是 liblog.so 库中的函数,也就是调用到了 liblog 目录下了
# system/core/liblog/logger_write.c static int (*write_to_log)(log_id_t, struct iovec* vec, size_t nr) = __write_to_log_init; LIBLOG_ABI_PUBLIC int __android_log_buf_write(int bufID, int prio, const char* tag, const char* msg) { struct iovec vec[3]; char tmp_tag[32]; if (!tag) tag = ""; //根据bugID进行区分,bugID部位 RADIO,都需要判断tag /* XXX: This needs to go! */ if (bufID != LOG_ID_RADIO) { switch (tag[0]) { ... inform: bufID = LOG_ID_RADIO; snprintf(tmp_tag, sizeof(tmp_tag), "use-Rlog/RLOG-%s", tag); tag = tmp_tag; /* FALLTHRU */ default: break; } } #if __BIONIC__ if (prio == ANDROID_LOG_FATAL) { android_set_abort_message(msg); } #endif //将数据转存到 iovec 结构体中,记录有log等级、tag、msg、以及各个部分的大小 vec[0].iov_base = (unsigned char*)&prio; vec[0].iov_len = 1; vec[1].iov_base = (void*)tag; vec[1].iov_len = strlen(tag) + 1; vec[2].iov_base = (void*)msg; vec[2].iov_len = strlen(msg) + 1; //通过函数指针进行传递 return write_to_log(bufID, vec, 3); }
- 可以看到,从上层传入的数据都被转存到了 iovec 结构体中,并且最后是通过函数指针进行继续下发,而该函数指针在一开始是定义为
__write_to_log_init()
方法的,继续追查流程
# system/core/liblog/logger_write.c static int __write_to_log_init(log_id_t log_id, struct iovec* vec, size_t nr) { int ret, save_errno = errno; __android_log_lock(); //首次进来会命中if if (write_to_log == __write_to_log_init) { //进行初始化 ret = __write_to_log_initialize(); //初始化失败命中if,正常不会走入if逻辑 if (ret < 0) { ... return ret; } //将 write_to_log 函数指针指向 __write_to_log_daemon() 方法 write_to_log = __write_to_log_daemon; } __android_log_unlock(); //继续调用 write_to_log 函数指针进行下发,此时函数指针指向的函数为 __write_to_log_daemon() 方法 ret = write_to_log(log_id, vec, nr); errno = save_errno; return ret; }
- 在该函数中,首先是调用
__write_to_log_initialize()
函数完成了初始化操作,接着将 函数指针重指向到__write_to_log_daemon()
,最后再次调用函数指针完成数据下发;先来梳理一下初始化中都做了哪些事情
# system/core/liblog/logger_write.c static int __write_to_log_initialize() { struct android_log_transport_write* transport; struct listnode* n; int i = 0, ret = 0; //配置 write 函数 __android_log_config_write(); ... return ret; }
- 省去相对不重要的代码,可以看到在该函数中完成了相关函数的配置
# system/core/liblog/config_write.c LIBLOG_HIDDEN void __android_log_config_write() { //根据 __android_log_transport 的值,进行 __android_log_transport_write 函数的配置 if (__android_log_transport & LOGGER_LOCAL) { extern struct android_log_transport_write localLoggerWrite; //根据 android_log_transport_write 结构体进行匹配添加到 __android_log_transport_write list中 __android_log_add_transport(&__android_log_transport_write, &localLoggerWrite); } ... }
- 在该函数中,主要就是根据
__android_log_transport
的值进行__android_log_transport_write
列表中逐一元素的赋值,此处给的就是android_log_transport_write
结构体中的元素,可以看一下该结构体的元素
# system/core/liblog/logd_writer.c LIBLOG_HIDDEN struct android_log_transport_write logdLoggerWrite = { .node = { &logdLoggerWrite.node, &logdLoggerWrite.node }, .context.sock = -EBADF, .name = "logd", .available = logdAvailable, .open = logdOpen, .close = logdClose, .write = logdWrite, };
- 可以看到元素逐一进行对应,那么返回去看
__write_to_log_init()
函数中,继续调用的__write_to_log_daemon()
函数
# system/core/liblog/logger_write.c static int __write_to_log_daemon(log_id_t log_id, struct iovec* vec, size_t nr) { struct android_log_transport_write* node; int ret, save_errno; struct timespec ts; size_t len, i; ... //匹配write函数进行数据下传 write_transport_for_each(node, &__android_log_persist_write) { if (node->logMask & i) { //调用write(),根据上面的流程分析,此处的write就是 logdWrite() 函数 (void)(*node->write)(log_id, &ts, vec, nr); } } errno = save_errno; return ret; }
- 最后调用到的是
*node->write()
函数,而根据上面流程的分析,可以获知,此处的write()
函数本质上是logdWrite()
函数
static int logdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) { ssize_t ret; int sock; static const unsigned headerLength = 1; struct iovec newVec[nr + headerLength]; android_log_header_t header; size_t i, payloadSize; static atomic_int_fast32_t dropped; static atomic_int_fast32_t droppedSecurity; //拿到socket,这里就是 /dev/socket/logdw sock = atomic_load(&logdLoggerWrite.context.sock); ... header.id = logId; //数据转存到 newVec 中 for (payloadSize = 0, i = headerLength; i < nr + headerLength; i++) { newVec[i].iov_base = vec[i - headerLength].iov_base; payloadSize += newVec[i].iov_len = vec[i - headerLength].iov_len; if (payloadSize > LOGGER_ENTRY_MAX_PAYLOAD) { newVec[i].iov_len -= payloadSize - LOGGER_ENTRY_MAX_PAYLOAD; if (newVec[i].iov_len) { ++i; } break; } } ... if (sock < 0) { ret = sock; } else { //向 dev/socket/logdw 中写入数据 ret = TEMP_FAILURE_RETRY(writev(sock, newVec, i)); if (ret < 0) { ret = -errno; } } ... }
- 可以看到,在该函数中,就开始向 dev/socket/logdw 进行数据的写入,而一旦该 socket 写入数据,那么
LogListener
就会被唤醒,那么接下来就会开始将数据存放到 logbuf 中,根据篇章一的分析,可以得到,当socket被唤醒,会执行onDataAvailable()
回调函数进行处理,那么接下来顺着该函数进行分析
# system/core/logd/LogListener.cpp bool LogListener::onDataAvailable(SocketClient* cli) { ... //拿到socket int socket = cli->getSocket(); //从socket处读取数据 ssize_t n = recvmsg(socket, &hdr, 0); ... if (logbuf != nullptr) { //将数据存入 int res = logbuf->log( logId, header->realtime, cred->uid, cred->pid, header->tid, msg, ((size_t)n <= USHRT_MAX) ? (unsigned short)n : USHRT_MAX); if (res > 0 && reader != nullptr) { //数据存入后,通知 reader 监听线程 reader->notifyNewLog(static_cast<log_mask_t>(1 << logId)); } } return true; }
- 可以看到,最终的处理就是将log信息往 logbuf 中存入,并且在存入完成后,还会通知
reader
监听线程,先来看一下log的存入
# system/core/logd/LogBuffer.cpp int LogBuffer::log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char* msg, unsigned short len) { ... //将该条log数据封装成LogBufferElement对象 LogBufferElement* elem = new LogBufferElement(log_id, realtime, uid, pid, tid, msg, len); if (log_id != LOG_ID_SECURITY) { int prio = ANDROID_LOG_INFO; const char* tag = nullptr; size_t tag_len = 0; //根据log_id执行相应的操作 if (log_id == LOG_ID_EVENTS || log_id == LOG_ID_STATS) { tag = tagToName(elem->getTag()); if (tag) { tag_len = strlen(tag); } } else { prio = *msg; tag = msg + 1; tag_len = strnlen(tag, len - 1); } //判断合法 if (!__android_log_is_loggable_len(prio, tag, tag_len, ANDROID_LOG_VERBOSE)) { // Log traffic received to total wrlock(); stats.addTotal(elem); unlock(); delete elem; return -EACCES; } } wrlock(); //获取到上一个保存的对应的log_id的elem LogBufferElement* currentLast = lastLoggedElements[log_id]; if (currentLast) { //获取上一次被删除的对应log_id的elem LogBufferElement* dropped = droppedElements[log_id]; unsigned short count = dropped ? dropped->getDropped() : 0; ... //判断当前和前一个保存的是否是同一个 enum match_type match = identical(elem, currentLast); ... //将当前的存入保存列表中 lastLoggedElements[log_id] = new LogBufferElement(*elem); //存入elem log(elem); unlock(); return len; }
- 该函数大致就是将 log 数据封装到了 elem 结构中,然后再将该 elem 元素插入
mLogElements
中
# system/core/logd/LogBuffer.cpp void LogBuffer::log(LogBufferElement* elem) { ... if (end_always || (end_set && (end > (*it)->getRealTime()))) { //将elem元素插入mLogElements的最后 mLogElements.push_back(elem); } else { // should be short as timestamps are localized near end() do { last = it; if (__predict_false(it == mLogElements.begin())) { break; } --it; } while (((*it)->getRealTime() > elem->getRealTime()) && (!end_set || (end <= (*it)->getRealTime()))); //插入到对应位置 mLogElements.insert(last, elem); } LogTimeEntry::unlock(); } //将当前的elem添加到 stats 中 stats.add(elem); //判断buf是否需要进行一轮内存删除 maybePrune(elem->getLogId()); }
- 经过一系列的判断,最终会将
elem
插入到mLogElements
中,同时还会添加到stats
中,该stats
是记录当前log存放状态的,最后判断log是否已经存满,是否需要删除
# system/core/logd/LogBuffer.cpp void LogBuffer::maybePrune(log_id_t id) { //对应id存储的大小 size_t sizes = stats.sizes(id); //对应log_id能够存储的最大大小 unsigned long maxSize = log_buffer_size(id); //如果已经存满 if (sizes > maxSize) { size_t sizeOver = sizes - ((maxSize * 9) / 10); size_t elements = stats.realElements(id); size_t minElements = elements / 100; if (minElements < minPrune) { minElements = minPrune; } //这里是需要删除的elem数量,会有一个删除的最大最小值 unsigned long pruneRows = elements * sizeOver / sizes; if (pruneRows < minElements) { pruneRows = minElements; } if (pruneRows > maxPrune) { pruneRows = maxPrune; } //真正的删除逻辑在该函数中 prune(id, pruneRows); } }
- 最后删除的log 的逻辑是在
prune()
函数中完成的,该函数太长有点复杂,暂时没有进入分析,网络上找到一篇分析的文章:Android log 机制 - 删除过多的 log | Jekton,总结一下该函数中主要完成这几件事:- 计算一个
watermark
,表示所有客户正在读取的最早的log。时间小于watermark
的 log 都不能删除 - 如果是客户请求删除 log,删除对应 uid 的 log
- 删除黑名单里的 log
- 如果已删除的条数还不够,删除不在白名单里的 log
- 如果已删除的条数还不够,删除白名单里的 log
- 计算一个
-
至此,写入log的大致流程梳理完成,时序图如下:
3. Logd日志读取
-
log内容的读取,在串口或者借助ADB使用通过指令
logcat
可进行读取,而该命令的解析就是借助logcat command
指令解析监听完成的,接下来解析一下解析和输出日志的过程 -
首先是
logcat command
解析器的创建,是通过logcatd.rc
文件解析进行创建的# system/core/logcatd.rc # 声明了 logcatd service # logcatd service service logcatd /system/bin/logcatd -L -b ${logd.logpersistd.buffer:-all} -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r 20480 -n ${logd.logpersistd.size:-256} --id=${ro.build.id} class late_start disabled # logd for write to /data/misc/logd, log group for read from log daemon user logd group log writepid /dev/cpuset/system-background/tasks oom_score_adjust -600
- 当该
service
启动时,会去执行logcatd_main.cpp
中的main()
函数
# system/core/logcat/logcatd_main.cpp int main(int argc, char** argv, char** envp) { //创建context android_logcat_context ctx = create_android_logcat(); if (!ctx) return -1; //设置了信号量 signal(SIGPIPE, exit); //解析启动参数 // Save and detect presence of -L or --last flag std::vector<std::string> args; bool last = false; for (int i = 0; i < argc; ++i) { if (!argv[i]) continue; args.push_back(std::string(argv[i])); if (!strcmp(argv[i], "-L") || !strcmp(argv[i], "--last")) last = true; } // Generate argv from saved content std::vector<const char*> argv_hold; for (auto& str : args) argv_hold.push_back(str.c_str()); argv_hold.push_back(nullptr); int ret = 0; if (last) { // Run logcat command with -L flag //解析logcat指令 ret = android_logcat_run_command(ctx, -1, -1, argv_hold.size() - 1, (char* const*)&argv_hold[0], envp); // Remove -L and --last flags from argument list for (std::vector<const char*>::iterator it = argv_hold.begin(); it != argv_hold.end();) { if (!*it || (strcmp(*it, "-L") && strcmp(*it, "--last"))) { ++it; } else { it = argv_hold.erase(it); } } ... }
- 在该
main()
函数中,会调用android_logcat_run_command()
函数进行 command 解析器的启动
# system/core/logcat/logcat.cpp int android_logcat_run_command(android_logcat_context ctx, int output, int error, int argc, char* const* argv, char* const* envp) { android_logcat_context_internal* context = ctx; //将参数转存到 context 变量中 context->output_fd = output; context->error_fd = error; context->argc = argc; context->argv = argv; context->envp = envp; context->stop = false; context->thread_stopped = false; //调用 __logcat() 方法 return __logcat(context); } static int __logcat(android_logcat_context_internal* context) { ... //先对命令进行解析 //1. 首先是解析是否将输出重定向到特定位置 for (int i = 0; i < argc; ++i) { ... } ... //2. 判断输入的指令是否是:logcat --help if (argc == 2 && !strcmp(argv[1], "--help")) { show_help(context); context->retval = EXIT_SUCCESS; goto exit; } ... //3. 开始匹配具体的logcat 指令 for (;;) { ... //匹配对应的指令 ret = getopt_long_r(argc, argv, ":cdDhLt:T:gG:sQf:r:n:v:b:BSpP:m:e:", long_options, &option_index, &optctx); if (ret < 0) break; //根据匹配到的指令进行对应的操作 switch (ret) { ... } } ... //检查log_device_t结构的链表,如果链表为空,那么就放入 /main、/system、/crash,默认缓冲区 if (!context->devices) { dev = context->devices = new log_device_t("main", false); context->devCount = 1; if (android_name_to_log_id("system") == LOG_ID_SYSTEM) { dev = dev->next = new log_device_t("system", false); context->devCount++; } if (android_name_to_log_id("crash") == LOG_ID_CRASH) { dev = dev->next = new log_device_t("crash", false); context->devCount++; } } ... dev = nullptr; //满足条件:stop标志、未设置最大打印量maxCount 或者 设置了但是还未达到最大打印量 while (!context->stop && (!context->maxCount || (context->printCount < context->maxCount))) { struct log_msg log_msg; //循环调用 android_logger_list_read() 从 /dev/socket/logdr 去读日志信息,存放到log_msg中 int ret = android_logger_list_read(logger_list, &log_msg); if (!ret) { logcat_panic(context, HELP_FALSE, "read: unexpected EOF!\n"); break; } ... //输出日志内容 if (context->printBinary) { printBinary(context, &log_msg); } else { processBuffer(context, dev, &log_msg); } } ... }
-
根据解析可以看到,最终就是在
__logcat()
函数中,经过一系列的判断之后,主要是通过两个步骤完成日志输出:- 先是调用
android_logger_list_read()
从 /dev/socket/logdr 端点去读取日志信息,存放到log_msg
中 - 调用
printBinary()
或者processBuffer()
函数完成log_msg
中的内容输出
那么接下来逐一梳理
- 先是调用
- 当该
1. 从*/dev/socket/logdr* 读取日志
-
如上分析,读取日志是借助
android_logger_list_read()
函数完成的,经过转换,又会调用到android_transport_read()
函数,# system/core/liblog/logger_read.c /* Validate log_msg packet, read function has already been null checked */ static int android_transport_read(struct android_log_logger_list* logger_list, struct android_log_transport_context* transp, struct log_msg* log_msg) { //借用函数指针进行log内容读取 int ret = (*transp->transport->read)(logger_list, transp, log_msg); ... }
- 和之前的
write()
方法类似,通过一系列的转化,最后是调用的函数:logdRead()
# system/core/liblog/logd_reader.c static int logdRead(struct android_log_logger_list* logger_list, struct android_log_transport_context* transp, struct log_msg* log_msg) { int ret, e; struct sigaction ignore; struct sigaction old_sigaction; unsigned int old_alarm = 0; //创建一个 client 连接 /dev/socket/logdr 端,此时会触发 logdr socket,就会调用 LogReader.onDataAvailable() 函数 ret = logdOpen(logger_list, transp); if (ret < 0) { return ret; } //初始化 log_msg,即将其中进行清空 memset(log_msg, 0, sizeof(*log_msg)); ... /* NOTE: SOCK_SEQPACKET guarantees we read exactly one full entry */ //从 /dev/socket/logdr 处进行数据读取 ret = recv(ret, log_msg, LOGGER_ENTRY_MAX_LEN, 0); e = errno; ... return ret; }
-
可以看到,在该函数中,会首先 创建一个client客户去连接 logdr socket,而这会唤醒 LogReader 线程,就会调用到
LogReader.onDataAvailable()
函数;其次会调用recv()
接收 socket 数据,将其存入log_msg
,这样就将日志信息存到了log_msg
变量中; -
在这里我们再深究一下当有客户端连接 logdr socket时,在
LogReader.onDataAvailable()
都会做些什么?# system/core/logd/LogReader.cpp bool LogReader::onDataAvailable(SocketClient* cli) { ... //调用到 logbuffer logbuf().flushTo(cli, sequence, nullptr, FlushCommand::hasReadLogs(cli), FlushCommand::hasSecurityLogs(cli), logFindStart.callback, &logFindStart); ... }
- 在该函数中,会调用到 LogBuffer 中去,而
LogBuffer.flushTo()
是调用到LogBufferElement.flushTo()
函数中
# system/core/logd/LogBufferElement.cpp log_time LogBufferElement::flushTo(SocketClient* reader, LogBuffer* parent, bool privileged, bool lastSame) { ... //此处的 reader 就是 LogReader 对象 log_time retval = reader->sendDatav(iovec, 1 + (entry.len != 0)) ? FLUSH_ERROR : mRealTime; ... }
- 最后又调用到了
LogReader.sendDatav()
函数,LogReader 是继承自 SocketClient 类的,而在这其中,又是调用到了sendDataLockedv()
函数中
# system/core/libsysutils/src/SocketClient.cpp int SocketClient::sendDataLockedv(struct iovec *iov, int iovcnt) { ... for (;;) { //将相关数据写入 socket 中 ssize_t rc = TEMP_FAILURE_RETRY( writev(mSocket, iov + current, iovcnt - current)); ... } ... }
- 可以看到,最终是将相关数据写入了socket 中
- 在该函数中,会调用到 LogBuffer 中去,而
- 和之前的
-
至此读取的过程就分析完毕了,总结时序图如下:
2. 输出日志
-
如上面分析,输出日志是通过
printBinary()
或者processBuffer()
完成的,大致看一下# system/core/logcat/logcat.cpp void printBinary(android_logcat_context_internal* context, struct log_msg* buf) { size_t size = buf->len(); //直接通过 write() 函数写入到对应的输出位置 TEMP_FAILURE_RETRY(write(context->output_fd, buf, size)); }
printBinary()
其实就是通过write()
方法直接写入到了特定的位置;而分析processBuffer()
方法其实本质也是一样的,也是通过write()
方法写入特定位置,只是多做了一层数据格式的读取和转换