IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android Logd框架梳理 -> 正文阅读

[移动开发]Android Logd框架梳理

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(&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数据的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 进程的启动过程中,主要完成了这样几件事:
      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.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() 函数中,经过一系列的判断之后,主要是通过两个步骤完成日志输出:

      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.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
  • 至此读取的过程就分析完毕了,总结时序图如下:

在这里插入图片描述

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()方法写入特定位置,只是多做了一层数据格式的读取和转换
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-03-22 20:44:56  更:2022-03-22 20:45:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 19:29:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码