Ahao's Technical Blog Ahao's Technical Blog
首页
  • 001.基础篇
  • 002.玩转AOSP篇
  • 003.学穿Binder篇
  • 004.基础组件篇
  • 005.系统启动过程分析
  • 006.Hal开发入门与实践
  • 007.显示系统
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

阿豪讲Framework

不积跬步无以至千里
首页
  • 001.基础篇
  • 002.玩转AOSP篇
  • 003.学穿Binder篇
  • 004.基础组件篇
  • 005.系统启动过程分析
  • 006.Hal开发入门与实践
  • 007.显示系统
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础篇

  • 玩转AOSP篇

  • 学穿Binder篇

  • 基础组件篇

    • 001.Android 平台智能指针使用与分析
    • 002.弱引用 wp 的作用
    • 003.Linux eventfd 原理与实践
    • 004.Linux IO 多路复用 epoll 机制
    • 005.Linux timerfd 的基本使用
    • 006.Android Native Looper 机制
    • 007.Android Java Looper 机制
    • 008.Handler 同步屏障机制
    • 009.IdleHanlder 原理与使用
    • 010.Android 属性系统入门
    • 011.属性文件生成过程分析
    • 012.如何添加系统属性
    • 013.属性与 Selinux
    • 014.属性系统源码分析一
    • 015.属性系统源码分析二
    • 016.属性系统源码分析三
    • 017.Unix Domain Socket 使用解析之 UDP 篇
    • 018.Unix Domain Socket 使用解析之 TCP 篇
    • 019.Android 中的 Unix Domain Socket 使用解析
    • 020.socketpair 使用解析
    • 021.Android 平台日志系统整体框架
    • 022.logd 守护进程初始化过程
      • 参考资料
    • 023.客户端写日志过程分析
    • 024.logd 写日志过程分析一
    • 025.logd 写日志过程分析二
    • 026.logd 读日志过程分析
    • 027.Android 平台日志丢失问题分析
  • 系统启动过程分析

  • Hal开发入门与实践

  • 显示系统

  • Framework
  • 基础组件篇
阿豪
2023-11-23
目录

022.logd 守护进程初始化过程

这是一个介绍 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 这里定义了两个 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

可以看出 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();
    }
1
2
3
4
5
6
7
8

首先是设置时区,接着会对启动程序的参数做判断,如果带 --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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

这里会向 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 %s\n", proc_kmsg);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

这里拿到 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);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

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

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

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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

这里先做一些初始化设置工作,然后就会在 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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

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

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

注册 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;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

这里首先获取到 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;
    }
1
2
3
4
5
6
7
8
9
10
11

接着初始化 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

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

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

# 参考资料

  • Android Logd框架梳理 (opens new window)
  • Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化-[Android取经之路] (opens new window)
  • 深入理解安卓日志系统(logcat / liblog / logd) (opens new window)
  • Android中 logd 详解 (opens new window)
  • Android logd日志简介及典型案例分析 (opens new window)
  • Android 10 根文件系统和编译系统(六):log系统和logcat命令 (opens new window)
  • Android P 源码分析 4 - logd 的初始化 (opens new window)
  • Android log 机制 - logd 如何接收 log 数据(下) (opens new window) <!-- * 为什么很多deamon使用SIGHUP作为重启/重新载入配置的信号? (opens new window)
  • SIGHUP 信号的作用以及守护进程为什么要忽略 SIGHUP 信号 (opens new window) -->
021.Android 平台日志系统整体框架
023.客户端写日志过程分析

← 021.Android 平台日志系统整体框架 023.客户端写日志过程分析→

最近更新
01
如何调试 SurfaceFlinger
10-05
02
SurfaceFlinger 概述
10-05
03
HWC 接口分析
10-05
更多文章>
Theme by Vdoing | Copyright © 2020-2025 AHao Framework | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式