这是一个介绍 Android 渠道日志体系的系列文章:

  • Android 渠道日志体系全体结构
  • logd 看护进程初始化进程(本文)
  • 客户端写日志进程剖析
  • logd 写日志进程剖析
  • 冗余日志处理进程剖析
  • logcat 读日志进程剖析
  • logd 读日志进程剖析
  • 内核日志处理剖析
  • SeLinux 日志处理剖析

本文根据 AOSP android-10.0.0_r41 版别解说

在上一节咱们了解了 Android 渠道日志体系的全体结构,本节将深化源码来了解 logd 看护进程的初始化进程。

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 SETGID
    writepid /dev/cpuset/system-background/tasks
service logd-reinit /system/bin/logd --reinit
    oneshot
    disabled
    user logd
    group logd
    writepid /dev/cpuset/system-background/tasks
  • 这儿界说了两个 service,logd 与 logd-reinit,对应的可履行程序都是 /system/bin/logd,主要的区别是 logd-reinit 带一个 –reinit 参数。
  • init 进程为 logd 服务初始化了三个 socket 服务,分别是 logd logdr logdw , 对应的 socket 文件是 /dev/socket/logd, /dev/socket/logdr, /dev/socket/logdw
  • init 进程为 logd 服务翻开了两个文件 /proc/kmsg, /dev/kmsg

再看 system/core/rootdir/init.rc

on init
    # ......
    # Start logd before any other services run to ensure we capture all of their logs.
    start logd
    # ......
on load_persist_props_action
    load_persist_props
    start logd
    start logd-reinit
on property:vold.decrypt=trigger_load_persist_props
    load_persist_props
    start logd
    start logd-reinit

能够看出 logd service 会在 init 阶段被发动,而 logd-reinit 在两种情况下会被发动,一个是加载 persist 特点,另一个是当 vold.decrypt 特点被赋值为 trigger_load_persist_props 时。

/system/bin/logd 可履行文件对应的源码在 system/core/logd/main.cpp

// system/core/logd/main.cpp 的 main 函数
int main(int argc, char* argv[]) {
    setenv("TZ", "UTC", 1);
    if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) {
        return issueReinit();
    }

首先是设置时区,接着会对发动程序的参数做判断,假如带 --reinit 参数,就会履行 issueReinit() 函数。

// system/core/logd/main.cpp
static int issueReinit() {
    cap_t caps = cap_init();
    (void)cap_clear(caps);
    (void)cap_set_proc(caps);
    (void)cap_free(caps);
    // 树立到 socket logd 的链接
    int sock = TEMP_FAILURE_RETRY(socket_local_client(
        "logd", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM));
    if (sock < 0) return -errno;
    // 向 logd 发送 reinit 消息
    static const char reinitStr[] = "reinit";
    ssize_t ret = TEMP_FAILURE_RETRY(write(sock, reinitStr, sizeof(reinitStr)));
    if (ret < 0) return -errno;
    // poll 监听回来信息
    struct pollfd p;
    memset(&p, 0, sizeof(p));
    p.fd = sock;
    p.events = POLLIN;
    ret = TEMP_FAILURE_RETRY(poll(&p, 1, 1000));
    if (ret < 0) return -errno;
    if ((ret == 0) || !(p.revents & POLLIN)) return -ETIME;
    static const char success[] = "success";
    char buffer[sizeof(success) - 1];
    memset(buffer, 0, sizeof(buffer));
    ret = TEMP_FAILURE_RETRY(read(sock, buffer, sizeof(buffer)));
    if (ret < 0) return -errno;
    return strncmp(buffer, success, sizeof(success) - 1) != 0;
}

这儿会向 logd socket,发送一个 reinit 消息,然后经过 poll 机制去读回来值。logd socket 怎么处理这个消息,这个咱们会在logd 读日志进程剖析中来解说,这儿知道流程就好了。

咱们接着看主函数的进程:

    // system/core/logd/main.cpp 的 main 函数
    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;
    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 %sn", proc_kmsg);
    }

这儿拿到 init 进程翻开的两个文件 /proc/kmsg, /dev/kmsg 对应的 fd。这两个文件在 system/core/logd/logd.rc 的 logd service 中做了界说,init 程序会帮咱们提前翻开这个两个文件,咱们经过 android_get_control_file 函数就能获取到文件的 fd。

假如 init 进程没有翻开文件,就自己翻开它,/dev/kmsg 文件用于跟内核的 log 体系通讯,/proc/kmsg 文件用于读取内核 log

接着看主函数流程:

    // system/core/logd/main.cpp 的 main 函数
    // 初始化多个 sem 信号量
    // reinit uidName 初始值都是 0,阐明一开始是堵塞的
    // sem_name 初始值是 1,阐明一开始不是堵塞的
    sem_init(&reinit, 0, 0);
    sem_init(&uidName, 0, 0);
    sem_init(&sem_name, 0, 1);
    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; //留意这个变量设置为 true 了
            if (pthread_create(&thread, &attr, reinit_thread_start, nullptr)) {
                reinit_running = false; // 创立线程失利进入分支
            }
        }
        pthread_attr_destroy(&attr);
    }

这儿初始化了多个 sem 信号量,sem_init 用于初始化 POSIX 信号量,它的原型如下:

include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

pshared 参数操控是否在多个进程间同享。这儿咱们仅仅用于进程内部的通讯,所以传入 0

reinit uidName 初始值都是 0,阐明一开始是堵塞的,sem_name 初始值是 1,阐明一开始不是堵塞的.

接着将 reinit_running 变量设置为 true,然后发动一个新的线程 reinit_thread_start,pthread 在成功的时分回来 0,失利则回来一个非 0 的错误码,假如线程发动失利了,将 reinit_running 变量设置为 false

接下来咱们来看 reinit_thread_start 线程的具体内容:

static void* reinit_thread_start(void* /*obj*/) {
    prctl(PR_SET_NAME, "logd.daemon");
    set_sched_policy(0, SP_BACKGROUND);
    setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND);
    // We should drop to AID_LOGD, if we are anything else, we have
    // even lesser privileges and accept our fate.
    gid_t groups[] = {
        AID_SYSTEM,        // search access to /data/system path
        AID_PACKAGE_INFO,  // readonly access to /data/system/packages.list
    };
    if (setgroups(arraysize(groups), groups) == -1) {
        android::prdebug(
            "logd.daemon: failed to set AID_SYSTEM AID_PACKAGE_INFO groups");
    }
    if (setgid(AID_LOGD) != 0) {
        android::prdebug("logd.daemon: failed to set AID_LOGD gid");
    }
    if (setuid(AID_LOGD) != 0) {
        android::prdebug("logd.daemon: failed to set AID_LOGD uid");
    }
    cap_t caps = cap_init();
    (void)cap_clear(caps);
    (void)cap_set_proc(caps); 
    (void)cap_free(caps);
    // 堵塞直到履行了 sem_post(&reinit)
    while (reinit_running && !sem_wait(&reinit) && reinit_running) {
        //......
    }
    return nullptr;
}

这儿先做一些初始化设置作业,然后就会在 sem_wait 处堵塞(reinit 初始化为 0 )。那什么时分会被唤醒呢?这个咱们遇到再说,这部分代码就暂时剖析到这儿。

咱们接着看 system/core/logd/

    // system/core/logd/main.cpp 的 main 函数
    // 获取 ro.logd.auditd 特点值
    bool auditd =
        __android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE);
    if (drop_privs(klogd, auditd) != 0) {
        return EXIT_FAILURE;
    }
    //初始化 LastLogTimes LogBuffer 两个目标
    // Serves the purpose of managing the last logs times read on a
    // socket connection, and as a reader lock on a range of log
    // entries.
    LastLogTimes* times = new LastLogTimes();
    // LogBuffer is the object which is responsible for holding all
    // log entries.
    logBuf = new LogBuffer(times);
    signal(SIGHUP, reinit_signal_handler);

这儿先获取 ro.logd.auditd 特点值,接着调用 drop_privs 函数设置相关的优先级和权限,接着再初始化 LastLogTimes LogBuffer 两个目标,然后调用 signal 注册 SIGHUP 信号的回调。回调函数如下:

void reinit_signal_handler(int /*signal*/) {
    sem_post(&reinit);
}

注册 SIGHUP 信号回调是看护进程程序编写常用的技术手段,其他进程能够向 logd 进程发送 SIGHUP 信号来触发 reinit_signal_handler 函数的履行,reinit_signal_handler 函数中 sem_post(&reinit) 就会唤醒等待中的 reinit_thread_start 线程,可是我在源码中没有找到有向 logd 进程发送 SIGHUP 信号。所以咱们接着往下看。

    // system/core/logd/main.cpp 的 main 函数
    // 获取到 logd.statistics 特点值
    // 假如特点值是 true,LogBuffer 敞开统计功能
    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();
    }
    // LogReader listens on /dev/socket/logdr. When a client
    // connects, log entries in the LogBuffer are written to the client.
    // 初始化一个 LogReader 指针
    LogReader* reader = new LogReader(logBuf);
    if (reader->startListener()) {
        return EXIT_FAILURE;
    }

这儿首先获取到 logd.statistics 特点值,假如特点值是 true,调用 LogBuffer 的 enableStatistics 办法,敞开统计功能。

接着初始化一个 LogReader 指针,然后调用它的 startListener 办法。LogReader 内部会发动一个 Unix Domain Socket 服务,logcat 指令经过 socket 衔接与 LogReader 通讯读出日志信息。LogReader 的实现细节咱们会在 logd 读日志进程剖析中做详细剖析,这儿咱们先了解流程。

接着看主函数:

    // system/core/logd/main.cpp 的 main 函数
    LogListener* swl = new LogListener(logBuf, reader);
    if (swl->startListener(600)) {
        return EXIT_FAILURE;
    }
    CommandListener* cl = new CommandListener(logBuf, reader, swl);
    if (cl->startListener()) {
        return EXIT_FAILURE;
    }

接着初始化 LogListener,然后调用它的 startListener 办法。LogListener 内部会发动一个 Unix Domain Socket 服务, App 经过 socket 衔接与 LogListener 通讯写日志信息。

接着初始化 CommandListener,然后调用它的 startListener 办法。CommandListener内部会发动一个 Unix Domain Socket 服务, logcat 指令经过 socket 衔接与 LogReader 通讯读出日志信息。

这两部分的代码细节都会在logd 读日志进程剖析中来解说,现在咱们知道这个流程即可。

接着看主函数:

LogAudit* al = nullptr;
    if (auditd) {
        al = new LogAudit(logBuf, reader,
                          __android_logger_property_get_bool(
                              "ro.logd.auditd.dmesg", BOOL_DEFAULT_TRUE)
                              ? fdDmesg
                              : -1);
    }
    LogKlog* kl = nullptr;
    if (klogd) {
        kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != nullptr);
    }
    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;
}

假如获取到的特点值 auditd 是 true,就初始化一个 LogAudit 目标,selinux 相关的日志消息会经过 socket 发送给 LogAudit,LogAudit 会将日志信息写入 LogBuffer,并通知LogReader向衔接的客户端发送更新。这个用得少,了解一下即可。

假如获取到的特点值 klogd 是 true,就初始化一个 LogKlog 目标,接着调用 readDmesg 函数把 /deg/kmsg 中的内核日志写入 LogBuffer。使用比较少,了解即可。

参考资料

关于

我叫阿豪,2015 年本科结业于国防科学技术大学指挥信息体系专业,结业后从事信息化装备的研制作业,作业内容主要涉及 Android Framework 与 Linux Kernel。

假如你对 Android Framework 感兴趣或许正在学习 Android Framework,能够重视我的微信公众号和抖音,我会持续共享我的学习经历,帮助正在学习的你少走一些弯路。学习进程中假如你有疑问或许你的经历想要共享给我们能够增加我的微信,我拉你进技术交流群。

logd 看护进程初始化进程