logd 守护进程初始化过程

这是一个介绍 Android 平台日志系统的系列文章:

  • Android 平台日志系统整体框架
  • logd 守护进程初始化过程(本文)
  • 客户端写日志过程分析
  • logd 写日志过程分析
  • 冗余日志处理过程分析
  • logcat 读日志过程分析
  • logd 读日志过程分析
  • 内核日志处理分析
  • SeLinux 日志处理分析

本文基于 AOSP android-10.0.0_r41 版本讲解

在上一节我们了解了 Android 平台日志系统的整体框架,本节将深入源码来了解 logd 守护进程的初始化过程。

system/core/logd/logd.rc 目录中,有如下内容:

bash 复制代码
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

bash 复制代码
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

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() 函数。

cpp 复制代码
// 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 读日志过程分析中来讲解,这里知道流程就好了。

我们接着看主函数的过程:

cpp 复制代码
    // 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 %s\n", 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

接着看主函数流程:

cpp 复制代码
    // 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 信号量,它的原型如下:

cpp 复制代码
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 线程的具体内容:

cpp 复制代码
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/

cpp 复制代码
    // 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 信号的回调。回调函数如下:

cpp 复制代码
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 信号。所以我们接着往下看。

cpp 复制代码
    // 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 读日志过程分析中做详细分析,这里我们先了解流程。

接着看主函数:

cpp 复制代码
    // 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 读日志过程分析中来讲解,目前我们知道这个流程即可。

接着看主函数:

cpp 复制代码
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,可以关注我的微信公众号和抖音,我会持续分享我的学习经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。

相关推荐
Estar.Lee3 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh3 小时前
uiautomator案例
android
工业甲酰苯胺4 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做3435 小时前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee6 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯7 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey8 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!9 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟10 小时前
Android音频采集
android·音视频
小白也想学C11 小时前
Android 功耗分析(底层篇)
android·功耗