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.rcon post-fs# Load properties from#     /system/build.prop,#     /odm/build.prop,#     /vendor/build.prop and#     /factory/factory.propload_system_props# start essential services# 开始启动 logd.rcstart logdstart servicemanagerstart hwservicemanagerstart vndservicemanager
    
    • init.rc 中启动 logd.rc
    # system/core/logd/logd.rc# 启动logd 进程
    service logd /system/bin/logd# 启动三个socketsocket logd stream 0666 logd logdsocket logdr seqpacket 0666 logd logdsocket logdw dgram+passcred 0222 logd logdfile /proc/kmsg rfile /dev/kmsg wuser logdgroup logd system package_info readprocwritepid /dev/cpuset/system-background/tasks# 启动 logd-reinit
    service logd-reinit /system/bin/logd --reinit# 开机只启动一次oneshotdisableduser logdgroup logdwritepid /dev/cpuset/system-background/taskson fswrite /dev/event-log-tags "# content owned by logd"chown logd logd /dev/event-log-tagschmod 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对象,返回 fdstatic 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 Threadsem_init(&reinit, 0, 0);sem_init(&uidName, 0, 0);sem_init(&sem_name, 0, 1);pthread_attr_t attr;//初始化线程,成功返回0,命中ifif (!pthread_attr_init(&attr)) {struct sched_param param;memset(&param, 0, sizeof(param));pthread_attr_setschedparam(&attr, &param);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数据的buflogBuf = 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 valueif (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 logreadDmesg(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 进程的启动过程中,主要完成了这样几件事:

      1. 打开 /dev/kmsg 节点(根据配置决定是否打开 /proc/kmsg 节点)
      2. 创建reinit线程,用来监测是否存在 reinit 请求,初始化 logBuffer
      3. 设置相关运行时优先级、权限
      4. 启动各个 log 监听器
        • /dev/socket/logdr:监听的客户端的连接,用作读取
        • /dev/socket/logdw:监听的是日志的写入
        • /dev/socket/logd:监听的是log指令
      5. 根据属性判断创建 LogKlogLogAudit

    至此就完成了 logd 进程的启动,时序图如下:

  • 这里再拓展一下,看listener的启动,以 LogListener为例,如上启动的方式是:swl->startListener(600)LogListener的父类是 SocketListener

    # system/core/libsysutils/src/SocketListener.cppint 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.javapublic 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.cppstatic 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.cstatic 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.cstatic int __write_to_log_init(log_id_t log_id, struct iovec* vec, size_t nr) {int ret, save_errno = errno;__android_log_lock();//首次进来会命中ifif (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.cstatic 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.cLIBLOG_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.cLIBLOG_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.cstatic 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/logdwsock = 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.cppbool LogListener::onDataAvailable(SocketClient* cli) {...//拿到socketint 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.cppint 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 totalwrlock();stats.addTotal(elem);unlock();delete elem;return -EACCES;}}wrlock();//获取到上一个保存的对应的log_id的elemLogBufferElement* currentLast = lastLoggedElements[log_id];if (currentLast) {//获取上一次被删除的对应log_id的elemLogBufferElement* dropped = droppedElements[log_id];unsigned short count = dropped ? dropped->getDropped() : 0;...//判断当前和前一个保存的是否是同一个enum match_type match = identical(elem, currentLast);...//将当前的存入保存列表中lastLoggedElements[log_id] = new LogBufferElement(*elem);//存入elemlog(elem);unlock();return len;
    }
    • 该函数大致就是将 log 数据封装到了 elem 结构中,然后再将该 elem 元素插入 mLogElements
    # system/core/logd/LogBuffer.cppvoid 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.cppvoid 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_startdisabled# logd for write to /data/misc/logd, log group for read from log daemonuser logdgroup logwritepid /dev/cpuset/system-background/tasksoom_score_adjust -600
    
    • 当该 service 启动时,会去执行 logcatd_main.cpp 中的 main() 函数
    # system/core/logcat/logcatd_main.cppint main(int argc, char** argv, char** envp) {//创建contextandroid_logcat_context ctx = create_android_logcat();if (!ctx) return -1;//设置了信号量signal(SIGPIPE, exit);//解析启动参数// Save and detect presence of -L or --last flagstd::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 contentstd::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 listfor (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.cppint 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 --helpif (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() 函数中,经过一系列的判断之后,主要是通过两个步骤完成日志输出:

      1. 先是调用 android_logger_list_read()/dev/socket/logdr 端点去读取日志信息,存放到 log_msg
      2. 调用 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.cstatic 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.cppbool LogReader::onDataAvailable(SocketClient* cli) {...//调用到 logbufferlogbuf().flushTo(cli, sequence, nullptr, FlushCommand::hasReadLogs(cli),FlushCommand::hasSecurityLogs(cli),logFindStart.callback, &logFindStart);
      ...
      }
      
      • 在该函数中,会调用到 LogBuffer 中去,而 LogBuffer.flushTo() 是调用到 LogBufferElement.flushTo() 函数中
      # system/core/logd/LogBufferElement.cpplog_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.cppint SocketClient::sendDataLockedv(struct iovec *iov, int iovcnt) {...for (;;) {//将相关数据写入 socket 中ssize_t rc = TEMP_FAILURE_RETRY(writev(mSocket, iov + current, iovcnt - current));...}...
      }
      
      • 可以看到,最终是将相关数据写入了socket
  • 至此读取的过程就分析完毕了,总结时序图如下:

2. 输出日志

  • 如上面分析,输出日志是通过 printBinary() 或者 processBuffer() 完成的,大致看一下

    # system/core/logcat/logcat.cppvoid 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()方法写入特定位置,只是多做了一层数据格式的读取和转换

Android Logd框架梳理相关推荐

  1. [Android][MediaRecorder] Android MediaRecorder框架简洁梳理

    Android MediaRecorder框架简洁梳理 一.MediaRecorder整体架构 1.1 MediaRecorder录制数据流框架 1.2 PersistentSurface及Graph ...

  2. Android 通用流行框架梳理

    1. 缓存 DiskLruCache    Java实现基于LRU的磁盘缓存, 在 防止多图OOM的时候用得上. 2.图片加载 Android Universal Image Loader  一个强大 ...

  3. 用android怎么做一个机器人,怎样写一个类似ROS的易用的android机器人框架(2)

    怎样写一个类似ROS的易用的android机器人框架(2) 接下来,我们一步步来实现这个几个目标 ROS式节点通讯的Android实现 相关代码实现位于 ai.easy.robot.framework ...

  4. BAT高级架构师合力熬夜15天,肝出了这份PDF版《Android百大框架源码解析》,还不快快码住。。。

    前言 为什么要阅读源码? 现在中高级Android岗位面试中,对于各种框架的源码都会刨根问底,从而来判断应试者的业务能力边际所在.但是很多开发者习惯直接搬运,对各种框架的源码都没有过深入研究,在面试时 ...

  5. Android 源码梳理

    Android 源码梳理 前言 作为霜枫司机一年学习的总结,附上帅照一张. 目录 1. Android系统启动过程分析 2. Linux内核文件系统 3. Android进程间通信源码梳理 4. An ...

  6. 百家争鸣:Android开源框架排行榜

    一.榜单介绍 排行榜包括四大类: 单一框架:仅提供路由.网络层.UI层.通信层或其他单一功能的框架 混合开发框架:提供开发hybrid app.h5与webview结合能力.web app能力的框架 ...

  7. 震撼发布2017年Android百大框架排行榜

    一.榜单介绍 排行榜包括四大类: 单一框架:仅提供路由.网络层.UI层.通信层或其他单一功能的框架 混合开发框架:提供开发hybrid app.h5与webview结合能力.web app能力的框架 ...

  8. Android Binder框架实现之bindService详解

        Android Binder框架实现之bindService详解 Android Binder框架实现目录: Android Binder框架实现之Binder的设计思想 Android Bi ...

  9. Android音频框架之一 详解audioPolicy流程及HAL驱动加载与配置

    前言 此音频架构梳理笔记.主要是因工作上需要在 Android8.1 以上版本中,增加 snd-aloop 虚拟声卡做前期准备工作, 本篇文章提纲挈领的把音频框架主线梳理清晰,通过这篇文章能够清晰如下 ...

最新文章

  1. 小型直流电机内部结构
  2. Java的异常:Error与Exception
  3. yum安装php和apache先装哪个,yum如何安装apache与php
  4. 修改VIM恶心的注释自动格式化
  5. Oracle 查看sql语句执行计划方法
  6. ssl1626-花店橱窗布置【日常dp】
  7. python sorted函数_Python 经典面试题 二
  8. 《Cisco/H3C交换机高级配置与管理技术手册》目录
  9. 天梯—谁先倒(C语言)
  10. R.I.P. Alan Turing(旧文搬运15.6.6)
  11. Vue入门之常用指令
  12. 五种常用的3D建模方式,各有什么优缺点?
  13. Security Warning: The negotiated TLS 1.0 is an insecure protocol and is supported for backward compa
  14. 高潮再次来袭:马云,东哥两位电商大佬,强行助攻 996
  15. android svg 线条动画教程,简单的SVG线条动画
  16. Django1.11.4框架简介(一)
  17. 六大云端 Jupyter Notebook 平台测评
  18. 计算机毕业设计springboot化妆品商城网站
  19. 0009基于51单片机智能门禁系统设计
  20. 【Spring源码系列】Spring注解扫描-@ComponentScan底层原理解读

热门文章

  1. 电脑怎么设置定时关机?分享2个简单操作!
  2. jQuery easing
  3. Scray和selenium的使用
  4. Flink学习:Flink常见报错
  5. 【ESP 保姆级教程】疯狂毕设篇 —— 案例:基于ESP8266的RFID门禁系统
  6. Python分析抖音用户行为数据,看看发什么样的视频才会爆!
  7. Metrix 5465e-103数字输入模块32 DDI01
  8. 华为折叠手机刷屏京东方借势涨停 买得起还要等一年?
  9. 大话Flink之十一Table API 和 Flink SQL
  10. 用PS修穿帮的小技巧