这是一个介绍 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(¶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 信号量,它的原型如下:
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。使用比较少,了解即可。
参考资料
- 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,能够重视我的微信公众号和抖音,我会持续共享我的学习经历,帮助正在学习的你少走一些弯路。学习进程中假如你有疑问或许你的经历想要共享给我们能够增加我的微信,我拉你进技术交流群。