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

[Android取经之路] 的源码都基于Android-Q(10.0) 进行分析

[Android取经之路]系列文章:

《系统启动篇》

Android系统架构
Android是怎么启动的
Android 10.0系统启动之init进程
Android10.0系统启动之Zygote进程
Android 10.0 系统启动之SystemServer进程
Android 10.0 系统服务之ActivityMnagerService
Android10.0系统启动之Launcher(桌面)启动流程
Android10.0应用进程创建过程以及Zygote的fork流程
Android 10.0 PackageManagerService(一)工作原理及启动流程
Android 10.0 PackageManagerService(二)权限扫描
Android 10.0 PackageManagerService(三)APK扫描
Android 10.0 PackageManagerService(四)APK安装流程
《日志系统篇》

Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性
Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化
Android10.0 日志系统分析(三)-logd、logcat读写日志源码分析
Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现​
《Binder通信原理》:

Android10.0 Binder通信原理(一)Binder、HwBinder、VndBinder概要
Android10.0 Binder通信原理(二)-Binder入门篇
Android10.0 Binder通信原理(三)-ServiceManager篇
Android10.0 Binder通信原理(四)-Native-C\C++实例分析
Android10.0 Binder通信原理(五)-Binder驱动分析
Android10.0 Binder通信原理(六)-Binder数据如何完成定向打击
Android10.0 Binder通信原理(七)-Framework binder示例
Android10.0 Binder通信原理(八)-Framework层分析
Android10.0 Binder通信原理(九)-AIDL Binder示例
Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub设计模式
Android10.0 Binder通信原理(十一)-Binder总结

《HwBinder通信原理》

HwBinder入门篇-Android10.0 HwBinder通信原理(一)
 HIDL详解-Android10.0 HwBinder通信原理(二)
HIDL示例-C++服务创建Client验证-Android10.0 HwBinder通信原理(三)
HIDL示例-JAVA服务创建-Client验证-Android10.0 HwBinder通信原理(四)
HwServiceManager篇-Android10.0 HwBinder通信原理(五)
Native层HIDL服务的注册原理-Android10.0 HwBinder通信原理(六)
Native层HIDL服务的获取原理-Android10.0 HwBinder通信原理(七)
JAVA层HIDL服务的注册原理-Android10.0 HwBinder通信原理(八)
JAVA层HIDL服务的获取原理-Android10.0 HwBinder通信原理(九)
HwBinder驱动篇-Android10.0 HwBinder通信原理(十)
HwBinder原理总结-Android10.0 HwBinder通信原理(十一)
《编译原理》

编译系统入门篇-Android10.0编译系统(一)
编译环境初始化-Android10.0编译系统(二)
make编译过程-Android10.0编译系统(三)
Image打包流程-Android10.0编译系统(四
Kati详解-Android10.0编译系统(五)
Blueprint简介-Android10.0编译系统(六)
Blueprint代码详细分析-Android10.0编译系统(七)
Android.bp 语法浅析-Android10.0编译系统(八)
Ninja简介-Android10.0编译系统(九)
Ninja提升编译速度的方法-Android10.0编译系统(十)
Android10.0编译系统(十一)

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

 

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的初始化流程:

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

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

设置运行时优先级、权限

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

启动各个 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两个步骤:

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

重新初始化各个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/second
service 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);
}

 

  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android SDK下, 如何在程序中输出日志 以及如何查看日志. 闲话少说,直接进入正题 在程序中输出日志, 使用 android.util.Log 类. 该类提供了若干静态方法 Log.v(String tag, String msg); Log.d(String tag, String msg); Log.i(String tag, String msg); Log.w(String tag, String msg); Log.e(String tag, String msg); 分别对应 Verbose, Debug, Info, Warning,Error. tag是一个标识,可以是任意字符串,通常可以使用类名+方法名, 主要是用来在查看日志时提供一个筛选条件. 程序运行后 并不会在 ide的控制台内输出任何信息. 如果要后查看日志 请使用 adb logcat 关于adb的更多信息请查看官方网站. 当执行 adb logcat 后会以tail方式实时显示出所有的日志信息. 这时候我们通常需要对信息进行过滤,来显示我们需要的信息, 这时候我们指定的 tag就派上了用场. adb logcat -s MyAndroid:I 这时将只显示tag为MyAndroid,级别为I或级别高于I(Warning,Error)的日志信息. 示例代码如下: Java代码 1. package com.zijun; 2. 3. import android.app.Activity; 4. import android.content.Context; 5. import android.graphics.Canvas; 6. import android.os.Bundle; 7. import android.util.Log; 8. import android.view.MotionEvent; 9. import android.view.View; 10. 11. public class MyAndroid extends Activity { 12. 13. protected static final String ACTIVITY_TAG="MyAndroid"; 14. 15. @Override 16. protected void onCreate(Bundle icicle) { 17. super.onCreate(icicle); 18. setContentView(new MyView(this)); 19. } 20. public class MyView extends View { 21. public MyView(Context c) { 22. super(c); 23. } 24. @Override 25. protected void onDraw(Canvas canvas) { 26. 27. } 28. @Override 29. public boolean onMotionEvent(MotionEvent event) { 30. Log.i(MyAndroid.ACTIVITY_TAG, "============================="); 31. 32. Log.d(MyAndroid.ACTIVITY_TAG, "Haha , this is a DEBUG of MyAndroid. "); 33. Log.i(MyAndroid.ACTIVITY_TAG, "Haha , this is a INFO of MyAndroid. "); 34. Log.w(MyAndroid.ACTIVITY_TAG, "Haha , this is a WARNING of MyAndroid. "); 35. 36. return true; 37. } 38.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值