一、前言
logd 守护进程是日志系统的管家,内部维持三个日志 Socket : logd、logdr、logdw 来与客户端进行通信。同时负责维护几个环形缓冲区,用于存放系统中的各种日志,缓冲区包含 main、system、events、radio、crash、kernel ;但是在 Android 5.0 之前,logd 进程并不存在,日志是保留在 /dev/log/main、/dev/log/system、/dev/log/radio、/dev/log/event 等节点中,但是这样面临的一个问题就是当 Android 系统大版本升级时,linux kernel 需要升级对应的日志驱动,因此在后续的版本中就有了 logd 进程。
二、logd 进程详解
2.1 logd 进程框架
在 Android 日志系统分析(一):概述 一文中,总结了整个日志读写的主要流程,因此对于 logd 进程是如何同外界沟通进而读写日志的过程不再赘述,而着重于 logd 本身的一些知识点,这里先看一下 logd 的系统框图:
知识点:
① logd 是日志系统的核心进程,由 init 启动,是属于守护进程常驻后台
② logd 维护各个日志节点缓存队列,提供 socket 接口进行读、写、控制功能
③ logd 进程启动后,分别启动 LogReader、LogListener、CommandListener 三个线程,监听并处理来自三个 socket 的消息。在收到消息后,会通过 LogBuffer 类保存日志到对应的 RAM buffer 中
④ LogAudit 模块用于接收 Kernel selinux 信息,即可以在用户空间打印 selinux 日志信息
⑤ LogKlog 用于接收 kernel 日志信息,通过设置 property ,可以通过 logcat 命令读取内核日志
⑥ LogStatistics 是日志统计模块,默认开启统计数据较少,仅能以 pid/uid 纬度统计打印日志的数量。如果设置了 logd.statistic = true 。会打印更多纬度的统计信息,包括哪些 pid/uid/tid/TAG 日志量比较大,可用于日志裁剪相关
2.2 logd 启动流程
在 main 函数中,会打开 /dev/kmsg 来读取内核日志,通过 LogKlog 来进行存储;若是配置了 ro.logd.kernel 属性,则打开 /proc/kmsg 读取内核日志;
2.3 logd.rc 解析
logd 作为 Native Service ,系统启动时会读取 init.rc 脚本去启动,它的相关属性被定义在 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
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
......
这里主要分为两部分:启动 logd 服务 和 启动 logd-reinit 服务 (在Android 10 上添加了 logd-auditctl 服务,目的是为了限制 selinux denia打印日志为5秒一次);先来看一下 启动 logd 服务 的同时做了些什么:
① 创建 logd、logdr、logdw 这三个 socket 为后面的通信做准备
② logdw 定义为 dgram 类型的 socket ,类似与 UDP类型的 Socket ,这么做的原因是考虑到性能问题,在多个进程同时写日志的情况下,write 函数写入到 socket 的 buffer 中即可返回,这样不会 block 业务逻辑太长时间。如果是 TCP 类型的 Socket ,客户端需要等到 TCP 收到 ACK 响应才能返回,这样就会过多的消耗性能和资源;
启动 logd-reinit 服务:
这个服务的主要作用是重新初始化 logd 的 LogBuffer,在配置中 oneshot 表示开机只启动一次。在上面的 main.cpp 中的 main 函数内,logd 在启动后,会创建一个线程 reinit_thread_start () ,当 logd-reinit 传入参数 reinit 后,进行功能执行:
① 如果 reinit 启动后,并且 /deg/kmsg 打开成功,把 logd.daemon: renit 写入 kmsg
② 重新初始化各个 log buffer 的大小,以及其他参数的初始化,但不会重新生成 LogBuffer 对象
main.cpp##main
int main(int argc, char* argv[]) {
......
// 启动 Reinit线程,当logd-reinit传入参数reinit时,进行调用,reinit开机只启动一次
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;
if (pthread_create(&thread, &attr, reinit_thread_start, nullptr)) {
reinit_running = false;
}
}
pthread_attr_destroy(&attr);
}
......
exit(0);
}
main.cpp#reinit_thread_start()
static void* reinit_thread_start(void* /*obj*/) {
prctl(PR_SET_NAME, "logd.daemon");
while (reinit_running && !sem_wait(&reinit) && reinit_running) {
if (fdDmesg >= 0) {
static const char reinit_message[] = { KMSG_PRIORITY(LOG_INFO),
'l','o','g','d','.','d','a','e','m','o','n',':',' ','r','e','i','n','i','t','\n' };
write(fdDmesg, reinit_message, sizeof(reinit_message));
}
//重新初始化各个log buffer的大小,以及其他参数的初始化,但不会重新生成LogBuffer对象
if (logBuf) {
logBuf->init();
logBuf->initPrune(nullptr);
}
android::ReReadEventLogTags();
}
return nullptr;
}
参考
[ 1 ] 深入理解安卓日志系统(logcat / liblog / logd)
[ 2 ] Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化