Android LOG系统源码解析(一)

在学习老罗的《Android系统源码情景分析》中发现其中的代码版本实在是有点老,和现在版本差别较大所以基于android 11重新梳理了一下。

旧版本差异

删除了logger驱动新增了logd来实现日志系统。源码路径为system/core/logd

log系统启动and初始化

看到路径下面有logd.rc文件可以想到logd应该是在系统启动的时候init进程通过解析init.rc文件启动的,通过查看init.rc文件也确实如此。

# system/core/rootdir/init.rc

on init
    # Start logd before any other services run to ensure we capture all of their logs.
    start logd
    # Start lmkd before any other services run so that it can register them

    start lmkd

    # Start essential services.
    start servicemanager
    start hwservicemanager
    start vndservicemanager

接下来就看下logd.rc文件中启动了什么

# 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
    priority 10
    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
    
# Limit SELinux denial generation to 5/second
service logd-auditctl /system/bin/auditctl -r 5
    oneshot
    disabled
    user logd
    group logd
    capabilities AUDIT_CONTROL

从service可以看到他干了下面几件事

  1. 启动放在system/bin下面的logd
  2. 启动三个socket,logd,logw,logr
  3. 生成两个文件 /proc/kmsg 和 /dev/kmsg
  4. 启动logd-reinit,设置了oneshot所以只会调用一次
  5. 启动logd-auditctl

启动logd

看main方法,其实代码中注释写的也很详细。在main方法中干了几件事

  1. 打开/dev/kmsg 文件拿到fd
  2. 根据系统属性ro.logd.kernel 判断,如果为true就打开/proc/kmsg文件拿到fd
  3. 启动reinit线程,当logd-reinit传入参数reinit时,进行调用
  4. 创建LogBuffer,这个对象保存了所有的日志,但是初始化并不是这里
  5. 启动每个socket的监听器
  1. LogReader监听/dev/socket/logdr,当有客户端连接的时候,发送LogBuffer中的log
  2. LogListener监听/dev/socket/logdw,有日志写入的时候,讲数据写如LogBuff
  3. CommandListener监听/dev/socket/logd,负责监听给logd的指令并处理
  1. 如果配置了ro.logd.auditd 为ture会启动LogAudit,主要负责selinux相关的日志
  2. 如果配置了ro.logd.kernel 为true会启动LogKlog,主要负责收集内核相关的日志

下面是源码:

int main(int argc, char* argv[]) {
     if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) {
        return issueReinit();
    }

    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);
    }

    bool auditd = __android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE);
    if (DropPrivs(klogd, auditd) != 0) {
        return EXIT_FAILURE;
    }

    // Reinit Thread
    sem_init(&reinit, 0, 0);
    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);
    }

    // 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);

    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* reader = new LogReader(logBuf);
    if (reader->startListener()) {
        return EXIT_FAILURE;
    }

    // LogListener listens on /dev/socket/logdw for client
    // initiated log messages. New log entries are added to LogBuffer
    // and LogReader is notified to send updates to connected clients.

    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;
    }

    // Command listener listens on /dev/socket/logd for incoming logd
    // administrative commands.

    CommandListener* cl = new CommandListener(logBuf, reader, swl);
    if (cl->startListener()) {
        return EXIT_FAILURE;
    }

    // LogAudit listens on NETLINK_AUDIT socket for selinux
    // initiated log messages. New log entries are added to LogBuffer
    // and LogReader is notified to send updates to connected clients.

    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);
    
    if (kl && kl->startListener()) {
        delete kl;
    }

    if (al && al->startListener()) {
        delete al;
    }

    TEMP_FAILURE_RETRY(pause());

    return EXIT_SUCCESS;
}

初始化LogBuff

在启动logd的时候启动了reinit线程pthread_create(&thread, &attr, reinit_thread_start, nullptr),等待reinit的信号执行,在logd.rc中可以知道logd启动结束之后会启动 logd-reinit,同样是logd的main方法,但是这时候会传入–reinit参数调用到issueReinit,发送reinit指令使得reinit_thread_start 往下执行,在这里会执行LogBuff的初始化。

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>
        if (logBuf) {
            logBuf->init();
            logBuf->initPrune(nullptr);
        }
        android::ReReadEventLogTags();
    }

    return nullptr;
}

启动logd-auditctl

logd-auditctl的作用主要是限制selinux日志写入频率更新为5秒,这个日常开发的时候用到不多。到这里相关服务就都启动了。

监听启动

这里额外说一下监听启动和接受的方法。在代码中监听启动都是调用的startListener(),这个方法来自于 SocketListener ,LogReader 和LogListener 都继承了SocketListener

方法的调用链为

startListener()

->threadStart(void *obj)

->runListener()

# system/core/libsysutils/src/SocketListener.cpp

int SocketListener::startListener(int backlog) {
    if (mListen && listen(mSock, backlog) < 0) {
        SLOGE("Unable to listen on socket (%s)", strerror(errno));
        return -1;
    } else if (!mListen)
        mClients[mSock] = new SocketClient(mSock, false, mUseCmdNum);

    if (pipe2(mCtrlPipe, O_CLOEXEC)) {
        SLOGE("pipe failed (%s)", strerror(errno));
        return -1;
    }

    if (pthread_create(&mThread, nullptr, SocketListener::threadStart, this)) {
        SLOGE("pthread_create (%s)", strerror(errno));
        return -1;
    }

    return 0;
}
void SocketListener::runListener() {
    while (true) {
 
        for (SocketClient* c : pending) {
            // Process it, if false is returned, remove from the map
            SLOGV("processing fd %d", c->getSocket());
            if (!onDataAvailable(c)) {
                release(c, false);
            }
            c->decRef();
        }
    }
}

代码比较长只展示了核心的代码,主要功能就是两个

  1. 通过名字获取socket,创建socketclient
  2. 开启线程,在线程中用while(true)循环获取数据,通过onDataAvailable() 来处理数据。子类通过重写onDataAvailable来实现各自的功能,所以看LogReader 和LogListener 的处理时就可以直接看onDataAvailable中的实现。