查看原文
其他

Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化-[Android取经之路]

IngresGe IngresGe 2021-11-05

阅读本文大约需要花费15分钟。

系列文章:

Android取经之路——启动篇

Android系统架构-[Android取经之路]

Android是怎么启动的-[Android取经之路]

Android系统启动之init进程(一)-「Android取经之路」

Android系统启动之init进程(二)-「Android取经之路」

Android 10.0系统启动之init进程(三)-「Android取经之路」

Android 10.0系统启动之init进程(四)-「Android取经之路」

Android 10.0系统启动之Zygote进程(一)-「Android取经之路」

Android 10.0系统启动之Zygote进程(二)-「Android取经之路」

Android 10.0系统启动之Zygote进程(三)-「Android取经之路」

Android 10.0系统启动之Zygote进程(四)-「Android取经之路」

Android 10.0系统启动之SystemServer进程(一)-「Android取经之路」

Android 10.0系统启动之SystemServer进程(二)-「Android取经之路

Android 10.0系统服务之AMS启动流程-「Android取经之路」

Android10.0系统启动之Launcher(桌面)启动流程-[Android取经之路]

Android10.0应用进程创建过程以及Zygote的fork流程-[Android取经之路]

Android 10.0 PackageManagerService(一)工作原理及启动流程-[Android取经之路]

Android 10.0 PackageManagerService(二)权限扫描-[Android取经之路]

Android 10.0 PackageManagerService(三)APK扫描-[Android取经之路]

Android 10.0 PackageManagerService(四)APK安装流程-[Android取经之路]

Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性-[Android取经之路]


    上一节我们看了Logd、logcat的指令说明,这一节我们来看看Android的日志系统架构,以及logd\logcat的初始化操作

    上一节内容:

Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性-[Android取经之路]


6.架构

6.1 读写日志架构

    在Android5.0(Android-L)之前,log由kernel的环形 buffer 保存,在Android5.0 之后,log保存在用户空间,通过Socket进行访问。

在Android5.0之后,引入了Logd的守护进程用来进行日志的读写操作。

不管是应用层,还是Native层,读写日志都是通过liblog提供的接口,访问logd的两个socket buffer:logdr、logdw来实现读写。

图片来自于CSDN-私房菜:


6.2 写日志流程

    在应用层可以通过android.util.Log,android.util.SLog,android.util.EventLog接口,把日志写入到main,system,event的不同缓冲区中去。

    在JAVA中想调用日志,就需要import下面的内容:

    import android.util.Log;

    import android.util.SLog;

    import android.util.EventLog;

应用层写日志方法如下:


    在Native C/C++中,进程通过加载liblog.so,调用ALOGD()、ALOGI()来进行日志的写入,最终也是通过logd写入到logdw的socket中。

    如果在Native中想要调用liblog的内容,需要在Android.mk 或者Android.bp中加入liblog,并引入头文件:#include <android/log.h>

    Native 层写日志方法如下:


6.3 读日志流程

    Android中主要通过logcat进程来读取日志,logcat属于native-C的进程,通过加载liblog,从而调用logd的read接口读取 logdr socket的日志内容。


7. 源码分析

Android系统日志主要有三个部分需要关注:

  • logd守护进程:日志系统的大管家,管理三个日志的socket:logd、logdr、logdw。

  • logcat进程:日志读取工具。

  • liblog:提供日志读写、过滤等接口,供logcat、JAVA、Native等程序使用


7.1 logd启动及初始化

7.1.1启动logd

    在Android 系统启动后,init进程加载,会解析logd.rc启动logd service如下:

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 priority 10 writepid /dev/cpuset/system-background/tasks

从上面的service可以看出,启动了一个守护进程为logd,存放在手机的/system/bin中,同时创建并启动三个socket:

  • logd 接收logcat 传递的指令然后处理 ,比如logcat -g, logcat -wrap等

  • logdr logcat从此buffer中读取buffer

  • logdw 日志写入的buffer

logd初始化调用栈如下:

logd的初始化流程:

  1. 打开/dev/kmsg 来读取内核日志,通过LogKlog来进行存储

  2. 如果属性"ro.logd.kernel" 配置了,打开/proc/kmsg来读取内核日志

  3. 设置运行时优先级、权限

  4. 启动 Reinit线程,当logd-reinit传入参数reinit时,进行调用,reinit开机只启动一次

  5. 启动各个 log 监听器:LogBuffer、LogReader、LogListener、CommandListener、LogAudit和LogKlog

源码:

int main(int argc, char* argv[]) { //logd是在假设时区是UTC的情况下编写的。 //如果未设置TZ,则在某些时间实用程序libc函数(包括mktime)中查找persist.sys.timezone。 //它混淆了logd时间处理,因此这里显式地将TZ设置为UTC,这将重写属性。 setenv("TZ", "UTC", 1); // issue reinit command. KISS argument parsing. if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) { return issueReinit(); }
//1.打开/dev/kmsg 来读取内核日志,通过LogKlog来进行存储 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)); }
//2.如果属性"ro.logd.kernel" 配置了,打开/proc/kmsg来读取内核日志 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); }
//3.设置运行时优先级、权限 bool auditd = __android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE); if (drop_privs(klogd, auditd) != 0) { return EXIT_FAILURE; }
//4.启动 Reinit线程,当logd-reinit传入参数reinit时,进行调用,reinit开机只启动一次 sem_init(&reinit, 0, 0); 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; if (pthread_create(&thread, &attr, reinit_thread_start, nullptr)) { reinit_running = false; } } pthread_attr_destroy(&attr); }
//用于管理在SOCKET连接上读取的最后日志时间,以及作为一个对一系列日志项的读卡器锁。 LastLogTimes* times = new LastLogTimes();
//5.启动各个 log 监听器 //5.1先创建一个LogBuffer的对象,LogBuffer是负责保存所有日志项的对象 logBuf = new LogBuffer(times);
signal(SIGHUP, reinit_signal_handler);
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(); }
//5.2 LogReader监听/dev/socket/logdr,当客户端连接时,比如logcat,日志缓冲区中的日志条目将写入客户端。 LogReader* reader = new LogReader(logBuf); if (reader->startListener()) { return EXIT_FAILURE; }
//5.3 LogListener在/dev/socket/logdw 上监听客户端启动的日志消息,监听是否有日志写入 LogListener* swl = new LogListener(logBuf, reader); // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value if (swl->startListener(600)) { return EXIT_FAILURE; }
//5.4 CommandListener 在/dev/socket/logd上 监听传入的logd 的command,即监听是否有命令发送给logd CommandListener* cl = new CommandListener(logBuf, reader, swl); if (cl->startListener()) { return EXIT_FAILURE; }
//5.5 如果配置了属性"ro.logd.auditd",则启动LogAudit,LogAudit 在NETLINK_AUDIT的socket上侦听selinux启动的日志消息 LogAudit* al = nullptr; if (auditd) { al = new LogAudit(logBuf, reader, __android_logger_property_get_bool( "ro.logd.auditd.dmesg", BOOL_DEFAULT_TRUE) ? fdDmesg : -1); }
//5.6如果配置了属性"ro.logd.kernel",则启动LogKlog,用来存储内核日志 LogKlog* kl = nullptr; if (klogd) { kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != nullptr); }
//5.7通过 LogAudit和 LogKlog来分别读取selinux和kernel的日志 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;}

    在system/core/lodgd/main.cpp文件的main函数中,默认创建了LogBuffer、LogReader、LogListener和CommandListener四个对象:

  • LogBuffer:LogBuffer是负责保存所有日志项的对象

  • LogReader:LogReader监听/dev/socket/logdr,当客户端连接时,比如logcat,日志缓冲区中的日志条目将写入客户端。

  • LogListener:LogListener在/dev/socket/logdw 上监听客户端启动的日志消息,监听是否有日志写入

  • CommandListener:CommandListener 在/dev/socket/logd上 监听传入的logd 的command,即监听是否有命令发送给logd

另外,还有两个对象-LogAudit 和LogKlog,受属性控制:

  • LogAudit:受属性"ro.logd.auditd"控制,在NETLINK_AUDIT的socket上侦听selinux启动的日志消息,新的日志条目将添加到LogBuffer中,并通知LogReader向连接的客户端发送更新

  • LogKlog:受属性"ro.logd.kernel"控制,用来存储内核日志,内核日志通过"/dev/kmsg", "/proc/kmsg" 获得


7.1.2 启动 logd-reinit

    logd.rc中启动logd-reinit 如下:

service logd-reinit /system/bin/logd --reinit oneshot disabled user logd group logd writepid /dev/cpuset/system-background/tasks


    启动logd-reinit的服务,主要工作是重新初始化logd的LogBuffer,在上面的启动脚本中,配置为oneshot,即开机只执行一次。

    通过上面logd的初始化,可以看到,logd启动后,创建了一个线程reinit_thread_start(),当logd-reinit 传入参数 reinit后,进行功能执行。

    logd-reinit两个步骤:

  1. 如果reinit启动后,并且/deg/kmsg打开成功,把 logd.daemon: renit写入kmsg

  2. 重新初始化各个log buffer的大小,以及其他参数的初始化,但不会重新生成LogBuffer对象

源码:

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)); }
// Anything that reads persist.<property> //重新初始化各个log buffer的大小,以及其他参数的初始化,但不会重新生成LogBuffer对象 if (logBuf) { logBuf->init(); logBuf->initPrune(nullptr); } android::ReReadEventLogTags(); }
return nullptr;}


7.1.3 启动 logd-auditctl

    logd.rc中启动logd-auditctl

# Limit SELinux denial generation to 5/secondservice logd-auditctl /system/bin/auditctl -r 5 oneshot disabled user logd group logd capabilities AUDIT_CONTROL

    logd-auditctl 的主体是 /system/bin/auditctl,在logd的android.bp中,通过编译 auditctl.cpp得来,并加载了liblogd的 库。

    logd-auditctl是Android 10.0中引入的新功能,目的是让selinux denia的日志打印限制为5秒一次。

   Android.bp 中auditctl展示如下:

cc_binary { name: "auditctl", srcs: ["auditctl.cpp"], static_libs: [ "liblogd", ], shared_libs: ["libbase"], cflags: [ "-Wall", "-Wextra", "-Werror", "-Wconversion" ],}


logd-auditctl 初始化调用栈如下:

logd-auditctl的主要作用是 让selinux denia的日志打印限制为5秒一次

说明:在logd.rc中配置了logd-auditctl,传入参数为-r5,即限制selinux日志写入频率更新为5秒

源码:

[auditctl.cpp] main()int main(int argc, char* argv[]) { uint32_t rate = 0; bool update_rate = false; int opt; //如果logd-auditctl传入了-r的参数,获取参数的值 //即这里的rate为5,并标记update_rate 为启动 while ((opt = getopt(argc, argv, "r:")) != -1) { switch (opt) { case 'r': if (!android::base::ParseUint<uint32_t>(optarg, &rate)) { error(EXIT_FAILURE, errno, "Invalid Rate"); } update_rate = true; break; default: /* '?' */ usage(argv[0]); exit(EXIT_FAILURE); } }
// In the future, we may add other options to auditctl // so this if statement will expand. // if (!update_rate && !update_backlog && !update_whatever) ... if (!update_rate) { fprintf(stderr, "Nothing to do\n"); usage(argv[0]); exit(EXIT_FAILURE); }
//如果传入了-r参数,更新rate if (update_rate) { do_update_rate(rate); }
return 0;}


说明:创建一个netlink的socket,协议号为NETLINK_AUDIT,并通过audit_rate_limit发送selinux频率

源码:

[auditctl.cpp] do_update_rate()static void do_update_rate(uint32_t rate) { //创建socket PF_NETLINK int fd = audit_open(); if (fd == -1) { error(EXIT_FAILURE, errno, "Unable to open audit socket"); } int result = audit_rate_limit(fd, rate); close(fd); if (result < 0) { fprintf(stderr, "Can't update audit rate limit: %d\n", result); exit(EXIT_FAILURE); }}


说明:组装结构体audit_status,传入频率为5秒,最终通过sendto()发送message到内核,由用户态切入到内核态

源码:

[libaudit.c] audit_rate_limit()int audit_rate_limit(int fd, uint32_t limit) { struct audit_status status; memset(&status, 0, sizeof(status)); status.mask = AUDIT_STATUS_RATE_LIMIT; status.rate_limit = limit; /* audit entries per second */ return audit_send(fd, AUDIT_SET, &status, sizeof(status));}


7.2 logcat启动

    logcat编译时,会编译两个进程/system/bin/logcat 和/system/bin/logcatd。

    和logd一样,logcat进程启动,是init进程解析了logcatd.rc来进行加载。

    logcatd.rc 如下所示:

service logcatd /system/bin/logcatd -L -b ${logd.logpersistd.buffer:-all} -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r ${logd.logpersistd.rotate_kbytes:-1024} -n ${logd.logpersistd.size:-256} --id=${ro.build.id} class late_start disabled # logd for write to /data/misc/logd, log group for read from log daemon user logd group log writepid /dev/cpuset/system-background/tasks oom_score_adjust -600

    从上面的service可以看出,启动了一个守护进程为logcatd,存放在手机的/system/bin中。

    启动logcatd时,传入了-b\-v\-f等参数。


说明:logcat启动后,先创建一个context,设置信号量,再启动一个while死循环,用来接收logcat的command

源码:

int main(int argc, char** argv, char** envp) { android_logcat_context ctx = create_android_logcat(); if (!ctx) return -1; signal(SIGPIPE, exit); int retval = android_logcat_run_command(ctx, -1, -1, argc, argv, envp); int ret = android_logcat_destroy(&ctx); if (!ret) ret = retval; return ret;}


说明:android_logcat_run_command()用来解析logcat传入的command,最终通过函数__logcat()中启动一个while死循环,来执行logcat传入的各种命令。

源码:

int android_logcat_run_command(android_logcat_context ctx, int output, int error, int argc, char* const* argv, char* const* envp) { android_logcat_context_internal* context = ctx;
context->output_fd = output; context->error_fd = error; context->argc = argc; context->argv = argv; context->envp = envp; context->stop = false; context->thread_stopped = false; return __logcat(context);}


Android 日志系统架构及初始化讲完了,下一节我们来分析logd、logcat读写日志的源码分析

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存