这是一个介绍 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(¶m, 0, sizeof(param));
pthread_attr_setschedparam(&attr, ¶m);
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。使用比较少,了解即可。
参考资料
- Android Logd框架梳理
- Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化-[Android取经之路]
- 深入理解安卓日志系统(logcat / liblog / logd)
- Android中 logd 详解
- Android logd日志简介及典型案例分析
- Android 10 根文件系统和编译系统(六):log系统和logcat命令
- Android P 源码分析 4 - logd 的初始化
- Android log 机制 - logd 如何接收 log 数据(下)
关于
我叫阿豪,2015 年本科毕业于国防科学技术大学指挥信息系统专业,毕业后从事信息化装备的研发工作,工作内容主要涉及 Android Framework 与 Linux Kernel。
如果你对 Android Framework 感兴趣或者正在学习 Android Framework,可以关注我的微信公众号和抖音,我会持续分享我的学习经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。