美文网首页
Android 日志系统--02:logd、logcat架构分析

Android 日志系统--02:logd、logcat架构分析

作者: DarcyZhou | 来源:发表于2023-12-14 08:38 被阅读0次

    本文转载自:Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化

    本文基于Android 10.0源码分析

    1.概述

      上一节我们看了logd、logcat的指令说明,这一节我们来看看Android的日志系统架构,以及logd\logcat的初始化操作。

    2.架构

    2.1 读写日志架构

      在Android 5.0之前,log由kernel的环形buffer保存。在Android 5.0之后,log保存在用户空间,通过Socket进行访问。在Android 5.0之后,引入了logd的守护进程用来进行日志的读写操作。

      不管是应用层,还是Native层,读写日志都是通过liblog提供的接口,访问logd的两个socket buffer:logdr、logdw来实现读写。图片来自于CSDN-私房菜:

    日志系统2-1.PNG

    2.2 写日志流程

      在应用层可以通过android.util.Log,android.util.SLog,android.util.EventLog接口,把日志写入到main,system,event的不同缓冲区中去。在Java中想调用日志,就需要import下面的内容:

    import android.util.Log;
    import android.util.SLog;
    import android.util.EventLog;
    

    (1)应用层写日志方法如下:

    日志系统2-2.PNG

    在Native C/C++中,进程通过加载liblog.so,调用ALOGD()、ALOGI()来进行日志的写入,最终也是通过logd写入到logdw的socket中。

      如果在Native中想要调用liblog的内容,需要在Android.mk或者Android.bp中加入liblog,并引入头文件:#include <android/log.h>。

    (2)Native层写日志方法如下:

    日志系统2-3.PNG

    2.3 读日志流程

      Android中主要通过logcat进程来读取日志,logcat属于native-C的进程,通过加载liblog,从而调用logd的read接口读取logdr socket的日志内容。

    日志系统2-4.PNG

    3.源码分析

      Android系统日志主要有三个部分需要关注:

    • logd守护进程:日志系统的大管家,管理三个日志的socket:logd、logdr、logdw;

    • logcat进程:日志读取工具;

    • liblog:提供日志读写、过滤等接口,供logcat、Java、Native等程序使用。

    3.1 logd启动及初始化

    3.1.1 启动logd

      在Android系统启动后,init进程加载会解析logd.rc启动logd service如下:

    // system/core/logd/logd.rc
    service logd /system/bin/logd
        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
        capabilities SYSLOG AUDIT_CONTROL
        priority 10
        writepid /dev/cpuset/system-background/tasks
    

    从上面的service可以看出,启动了一个守护进程为logd,存放在手机的/system/bin中,同时创建并启动三个socket:

    • logd:接收logcat传递的指令然后处理 ,比如logcat -g, logcat -wrap等;

    • logdr:logcat从此buffer中读取buffer;

    • logdw:日志写入的buffer。

    (1)logd初始化调用栈如下:

    日志系统2-5.png

    (2)logd的初始化流程:

    • 打开/dev/kmsg来读取内核日志,通过LogKlog来进行存储;

    • 如果属性"ro.logd.kernel"配置了,打开/proc/kmsg来读取内核日志;

    • 设置运行时优先级、权限;

    • 启动Reinit线程,当logd-reinit传入参数reinit时,进行调用,reinit开机只启动一次;

    • 启动各个log监听器:LogBuffer、LogReader、LogListener、CommandListener、LogAudit和LogKlog。

    (3)源码

    // system/core/logd/main.cpp
    int main(int argc, char* argv[]) {
      //logd是在假设时区是UTC的情况下编写的。
      //如果未设置TZ,则在某些时间实用程序libc函数(包括mktime)中查找persist.sys.timezone。
      //它混淆了logd时间处理,因此这里显式地将TZ设置为UTC,这将重写属性。
        setenv("TZ", "UTC", 1);
        // issue reinit command. KISS argument parsing.
        if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) {
            return issueReinit();
        }
    
        //1.打开/dev/kmsg来读取内核日志,通过LogKlog来进行存储
        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));
        }
    
        //2.如果属性"ro.logd.kernel"配置了,打开/proc/kmsg来读取内核日志
        int fdPmesg = -1;
        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);
        }
    
        //3.设置运行时优先级、权限
        bool auditd = __android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE);
        if (drop_privs(klogd, auditd) != 0) {
            return EXIT_FAILURE;
        }
    
        //4.启动Reinit线程,当logd-reinit传入参数reinit时,进行调用,reinit开机只启动一次
        sem_init(&reinit, 0, 0);
        pthread_attr_t attr;
        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;
                if (pthread_create(&thread, &attr, reinit_thread_start, nullptr)) {
                    reinit_running = false;
                }
            }
            pthread_attr_destroy(&attr);
        }
    
        //用于管理在SOCKET连接上读取的最后日志时间,以及作为一个对一系列日志项的读卡器锁。
        LastLogTimes* times = new LastLogTimes();
    
        //5.启动各个log监听器
        //5.1先创建一个LogBuffer的对象,LogBuffer是负责保存所有日志项的对象
        logBuf = new LogBuffer(times);
    
        signal(SIGHUP, reinit_signal_handler);
    
        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();
        }
    
        //5.2 LogReader监听/dev/socket/logdr,当客户端连接时,比如logcat,日志缓冲区中的日志条目将写入客户端。
        LogReader* reader = new LogReader(logBuf);
        if (reader->startListener()) {
            return EXIT_FAILURE;
        }
    
        //5.3 LogListener在/dev/socket/logdw上监听客户端启动的日志消息,监听是否有日志写入
        LogListener* swl = new LogListener(logBuf, reader);
        // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
        if (swl->startListener(600)) {
            return EXIT_FAILURE;
        }
    
         //5.4 CommandListener 在/dev/socket/logd上 监听传入的logd 的command,即监听是否有命令发送给logd
        CommandListener* cl = new CommandListener(logBuf, reader, swl);
        if (cl->startListener()) {
            return EXIT_FAILURE;
        }
    
        //5.5 如果配置了属性"ro.logd.auditd",则启动LogAudit,LogAudit 在NETLINK_AUDIT的socket上侦听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);
        }
    
        //5.6如果配置了属性"ro.logd.kernel",则启动LogKlog,用来存储内核日志
        LogKlog* kl = nullptr;
        if (klogd) {
            kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != nullptr);
        }
    
        //5.7通过LogAudit和LogKlog来分别读取selinux和kernel的日志
        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());
        return EXIT_SUCCESS;
    }
    

    在main函数中,默认创建了LogBuffer、LogReader、LogListener和CommandListener四个对象:

    • LogBuffer:LogBuffer是负责保存所有日志项的对象;

    • LogReader:LogReader监听/dev/socket/logdr,当客户端连接时,比如logcat,日志缓冲区中的日志条目将写入客户端;

    • LogListener:LogListener在/dev/socket/logdw上监听客户端启动的日志消息,监听是否有日志写入;

    • CommandListener:CommandListener在/dev/socket/logd上监听传入的logd的command,即监听是否有命令发送给logd。

      另外,还有两个对象LogAudit和LogKlog,受属性控制:

    • LogAudit:受属性"ro.logd.auditd"控制,在NETLINK_AUDIT的socket上侦听selinux启动的日志消息,新的日志条目将添加到LogBuffer中,并通知LogReader向连接的客户端发送更新;

    • LogKlog:受属性"ro.logd.kernel"控制,用来存储内核日志,内核日志通过"/dev/kmsg", "/proc/kmsg" 获得。

    3.1.2 启动logd-reinit

    (1)logd.rc中启动logd-reinit如下:

    service logd-reinit /system/bin/logd --reinit
        oneshot
        disabled
        user logd
        group logd
        writepid /dev/cpuset/system-background/tasks
    

    启动logd-reinit的服务,主要工作是重新初始化logd的LogBuffer,在上面的启动脚本中,配置为oneshot,即开机只执行一次。通过上面logd的初始化,可以看到,logd启动后,创建了一个线程reinit_thread_start(),当logd-reinit传入参数reinit后,进行功能执行。

      logd-reinit两个步骤:

    • 如果reinit启动后,并且/deg/kmsg打开成功,把 logd.daemon: renit写入kmsg;

    • 重新初始化各个log buffer的大小,以及其他参数的初始化,但不会重新生成LogBuffer对象。

    (2)源码

    // system/core/logd/main.cpp
    static void* reinit_thread_start(void* /*obj*/) {
        prctl(PR_SET_NAME, "logd.daemon");
    
        while (reinit_running && !sem_wait(&reinit) && reinit_running) {
    
            if (fdDmesg >= 0) {
                static const char reinit_message[] = { KMSG_PRIORITY(LOG_INFO),
                                                       'l',
                                                       'o',
                                                       'g',
                                                       'd',
                                                       '.',
                                                       'd',
                                                       'a',
                                                       'e',
                                                       'm',
                                                       'o',
                                                       'n',
                                                       ':',
                                                       ' ',
                                                       'r',
                                                       'e',
                                                       'i',
                                                       'n',
                                                       'i',
                                                       't',
                                                       '\n' };
                write(fdDmesg, reinit_message, sizeof(reinit_message));
            }
    
            // Anything that reads persist.<property>
            //重新初始化各个log buffer的大小,以及其他参数的初始化,但不会重新生成LogBuffer对象
            if (logBuf) {
                logBuf->init();
                logBuf->initPrune(nullptr);
            }
            android::ReReadEventLogTags();
        }
    
        return nullptr;
    }
    

    3.1.3 启动logd-auditctl

    (1)logd.rc中启动logd-auditctl

    # Limit SELinux denial generation to 5/second
    service logd-auditctl /system/bin/auditctl -r 5
        oneshot
        disabled
        user logd
        group logd
        capabilities AUDIT_CONTROL
    

    logd-auditctl的主体是/system/bin/auditctl,在logd的android.bp中,通过编译auditctl.cpp得来,并加载了liblogd的 库。logd-auditctl是Android 10.0中引入的新功能,目的是让selinux denia的日志打印限制为5秒一次。

    (2)Android.bp中auditctl展示如下:

    cc_binary {
        name: "auditctl",
        srcs: ["auditctl.cpp"],
        static_libs: [
            "liblogd",
        ],
        shared_libs: ["libbase"],
        cflags: [
            "-Wall",
            "-Wextra",
            "-Werror",
            "-Wconversion"
        ],
    }
    

    (3)logd-auditctl初始化调用栈如下:

    日志系统2-6.png

    (4)源码
      logd-auditctl的主要作用是让selinux denia的日志打印限制为5秒一次。在logd.rc中配置了logd-auditctl,传入参数为-r 5,即限制selinux日志写入频率更新为5秒。

    // system/core/logd/auditctl.cpp
    int main(int argc, char* argv[]) {
        uint32_t rate = 0;
        bool update_rate = false;
        int opt;
        //如果logd-auditctl传入了-r的参数,获取参数的值
        //即这里的rate为5,并标记update_rate为启动
        while ((opt = getopt(argc, argv, "r:")) != -1) {
            switch (opt) {
                case 'r':
                    if (!android::base::ParseUint<uint32_t>(optarg, &rate)) {
                        error(EXIT_FAILURE, errno, "Invalid Rate");
                    }
                    update_rate = true;
                    break;
                default: /* '?' */
                    usage(argv[0]);
                    exit(EXIT_FAILURE);
            }
        }
    
        // In the future, we may add other options to auditctl
        // so this if statement will expand.
        // if (!update_rate && !update_backlog && !update_whatever) ...
        if (!update_rate) {
            fprintf(stderr, "Nothing to do\n");
            usage(argv[0]);
            exit(EXIT_FAILURE);
        }
    
        //如果传入了-r参数,更新rate
        if (update_rate) {
            do_update_rate(rate);
        }
    
        return 0;
    }
    

    do_update_rate:创建一个netlink的socket,协议号为NETLINK_AUDIT,并通过audit_rate_limit发送selinux频率。

    // system/core/logd/auditctl.cpp
    static void do_update_rate(uint32_t rate) {
        //创建socket PF_NETLINK
        int fd = audit_open();
        if (fd == -1) {
            error(EXIT_FAILURE, errno, "Unable to open audit socket");
        }
        int result = audit_rate_limit(fd, rate);
        close(fd);
        if (result < 0) {
            fprintf(stderr, "Can't update audit rate limit: %d\n", result);
            exit(EXIT_FAILURE);
        }
    }
    

    audit_rate_limit:组装结构体audit_status,传入频率为5秒,最终通过sendto()发送message到内核,由用户态切入到内核态。

    // system/core/logd/libaudit.c
    int audit_rate_limit(int fd, uint32_t limit) {
        struct audit_status status;
        memset(&status, 0, sizeof(status));
        status.mask = AUDIT_STATUS_RATE_LIMIT;
        status.rate_limit = limit; /* audit entries per second */
        return audit_send(fd, AUDIT_SET, &status, sizeof(status));
    }
    

    3.2 logcat启动

      logcat编译时,会编译两个进程/system/bin/logcat和/system/bin/logcatd。和logd一样,logcat进程启动,是init进程解析了logcatd.rc来进行加载。 logcatd.rc 如下所示:

    service logcatd /system/bin/logcatd -L -b ${logd.logpersistd.buffer:-all} -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r ${logd.logpersistd.rotate_kbytes:-1024} -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,存放在手机的/system/bin中。启动logcatd时,传入了-b-v-f等参数。

      logcat启动后,先创建一个context,设置信号量,再启动一个while死循环,用来接收logcat的command。

    int main(int argc, char** argv, char** envp) {
        android_logcat_context ctx = create_android_logcat();
        if (!ctx) return -1;
        signal(SIGPIPE, exit);
        int retval = android_logcat_run_command(ctx, -1, -1, argc, argv, envp);
        int ret = android_logcat_destroy(&ctx);
        if (!ret) ret = retval;
        return ret;
    }
    

    android_logcat_run_command()用来解析logcat传入的command,最终通过函数__logcat()中启动一个while死循环,来执行logcat传入的各种命令。

    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->output_fd = output;
        context->error_fd = error;
        context->argc = argc;
        context->argv = argv;
        context->envp = envp;
        context->stop = false;
        context->thread_stopped = false;
        return __logcat(context);
    }
    

    Android 日志系统架构及初始化讲完了,下一节我们来分析logd、logcat读写日志的源码分析。

    相关文章

      网友评论

          本文标题:Android 日志系统--02:logd、logcat架构分析

          本文链接:https://www.haomeiwen.com/subject/kfnfidtx.html