Android 日志系统4——logd 写日志过程分析一

目录


|-------------------|
| logd 写日志过程分析一 |

前言

上一节我们说到客户端通过 socket 通信向服务端发送数据,今天我们就来看服务端是如何接受和处理收到的数据的。

在 logd(system/core/logd/main.cpp)的 main 函数中,先初始化一个 LogBuffer 对象,日志的读写都是通过这个对象实现,接着初始化一个 LogReader,LogReader 用于接受客户端的读日志请求,接着初始化一个 LogListener,LogListener 用于接受客户写入的 log 数据,虽然在 main 函数里先创建的是 LogReader,这里我们还是先看 LogListener,毕竟,先写了 log,才有东西可以读。

一、LogBuffer 的初始化

LogBuffer 是在 logd 的 main 函数里初始化的:

c 复制代码
// system/core/logd/main.cpp
LastLogTimes* times = new LastLogTimes();
logBuf = new LogBuffer(times);

// system/core/logd/LogTimes.h
typedef std::list<LogTimeEntry*> LastLogTimes;

LastLogTimes 实际上是一个 std::list,这里 new 了一个空 list。LogBuffer 的构造函数中传入了一个 LastLogTimes 类型的指针。接下来我们就来看 LogBuffer 的构造函数

c 复制代码
// system/core/logd/LogBuffer.cpp
LogBuffer::LogBuffer(LastLogTimes* times)
    : monotonic(android_log_clockid() == CLOCK_MONOTONIC), mTimes(*times) {

    pthread_rwlock_init(&mLogElementsLock, nullptr);

    log_id_for_each(i) {
        lastLoggedElements[i] = nullptr;
        droppedElements[i] = nullptr;
    }

    init();
}

monotonic 表示时间的格式,如果为 true,表示使用的是 CPU 的 up time(系统上电后从 0 开始计数)。一般我们用的是 real time(就是我们一般说的时间的概念)。一般使用 real time。这里 android_log_clockid() 返回的是 CLOCK_REALTIME,所以这里 monotonic 的值是 false

mLogElementsLock 是一个锁,用于保护内部的数据结构。

log_id_for_each 是一个宏,用来遍历所有的 log 类型。每一种 log 类型,都有一个 log_id 来表示。

c 复制代码
// system/core/logd/LogStatistics.h
#define log_id_for_each(i) \
    for (log_id_t i = LOG_ID_MIN; (i) < LOG_ID_MAX; (i) = (log_id_t)((i) + 1))

// system/core/liblog/include/log/log_id.h
typedef enum log_id {
  LOG_ID_MIN = 0,

  LOG_ID_MAIN = 0,
  LOG_ID_RADIO = 1,
  LOG_ID_EVENTS = 2,
  LOG_ID_SYSTEM = 3,
  LOG_ID_CRASH = 4,
  LOG_ID_SECURITY = 5,
  LOG_ID_KERNEL = 6, /* place last, third-parties can not use it */

  LOG_ID_MAX
} log_id_t;

lastLoggedElements 和 droppedElements 是 LogBufferElement * 类型的数组,LogBufferElement 用于保存一条 Log 信息,数组的每个元素对应一种 log 类型:

c 复制代码
// system/core/logd/LogBuffer.h
class LogBuffer {
    // ...

    LogBufferElement* lastLoggedElements[LOG_ID_MAX];
    LogBufferElement* droppedElements[LOG_ID_MAX];

    // ...
};

接下来,调用 LogBuffer 构造函数就会调用 init 函数:

c 复制代码
// system/core/logd/LogBuffer.cpp

void LogBuffer::init() {
    log_id_for_each(i) {
        mLastSet[i] = false;
        mLast[i] = mLogElements.begin();

        if (setSize(i, __android_logger_get_buffer_size(i))) {
            setSize(i, LOG_BUFFER_MIN_SIZE);
        }
    }

    // ...
}

// system/core/logd/LogBuffer.h
typedef std::list<LogBufferElement*> LogBufferElementCollection;

//system/core/logd/LogBuffer.cpp
class LogBuffer {
    // ...
    LogBufferElementCollection mLogElements;
    LogBufferElementCollection::iterator mLast[LOG_ID_MAX];
    bool mLastSet[LOG_ID_MAX];
    unsigned long mMaxSize[LOG_ID_MAX];

    // ...
}

这里通过 log_id_for_each 循环给 LogBuffer 的两个成员 mLastSet mLast 做初始化。

接着调用 setSize 设置各种 log 的最大容量:

c 复制代码
// system/core/logd/LogBuffer.cpp
#define log_buffer_size(id) mMaxSize[id]

// set the total space allocated to "id"
int LogBuffer::setSize(log_id_t id, unsigned long size) {
    // Reasonable limits ...
    if (!__android_logger_valid_buffer_size(size)) {
        return -1;
    }
    pthread_mutex_lock(&mLogElementsLock);
    log_buffer_size(id) = size;
    pthread_mutex_unlock(&mLogElementsLock);
    return 0;
}

setSize 就是给 LogBuffer 的成员 mMaxSize 赋值。log 的容量大小通过 __android_logger_get_buffer_size() 函数获取到,通常会返回默认值 256 * 1024。

接着,是 init() 的第二部分。这部分检查时间格式是否发生了变化,如果是,就变换已经存在 log 的时间:

c 复制代码
// system/core/logd/LogBuffer.cpp
void LogBuffer::init() {
    // 第一部分代码

    bool lastMonotonic = monotonic;
    monotonic = android_log_clockid() == CLOCK_MONOTONIC;
    if (lastMonotonic != monotonic) {
        //
        // Fixup all timestamps, may not be 100% accurate, but better than
        // throwing what we have away when we get 'surprised' by a change.
        // In-place element fixup so no need to check reader-lock. Entries
        // should already be in timestamp order, but we could end up with a
        // few out-of-order entries if new monotonics come in before we
        // are notified of the reinit change in status. A Typical example would
        // be:
        //  --------- beginning of system
        //      10.494082   184   201 D Cryptfs : Just triggered post_fs_data
        //  --------- beginning of kernel
        //       0.000000     0     0 I         : Initializing cgroup subsys
        // as the act of mounting /data would trigger persist.logd.timestamp to
        // be corrected. 1/30 corner case YMMV.
        //
        pthread_mutex_lock(&mLogElementsLock);
        LogBufferElementCollection::iterator it = mLogElements.begin();
        while ((it != mLogElements.end())) {
            LogBufferElement* e = *it;
            if (monotonic) {
                if (!android::isMonotonic(e->mRealTime)) {
                    LogKlog::convertRealToMonotonic(e->mRealTime);
                }
            } else {
                if (android::isMonotonic(e->mRealTime)) {
                    LogKlog::convertMonotonicToReal(e->mRealTime);
                }
            }
            ++it;
        }
        pthread_mutex_unlock(&mLogElementsLock);
    }

    // ...
}

最后,我们看 init() 的第3部分:

c 复制代码
// system/core/logd/LogBuffer.cpp
void LogBuffer::init() {
    // 第 1 部分代码

    // 第 2 部分代码

    // We may have been triggered by a SIGHUP. Release any sleeping reader
    // threads to dump their current content.
    //
    // NB: this is _not_ performed in the context of a SIGHUP, it is
    // performed during startup, and in context of reinit administrative thread
    LogTimeEntry::lock();

    LastLogTimes::iterator times = mTimes.begin();
    while (times != mTimes.end()) {
        LogTimeEntry* entry = (*times);
        if (entry->owned_Locked()) {
            entry->triggerReader_Locked();
        }
        times++;
    }

    LogTimeEntry::unlock();
}

每一个需要读取 log 数据的客户端都对应 mTimes 里面的一个元素。就像注释里说的,收到信号 SIGHUP 会调用 init,这个时候需要重新唤醒 LogTimeEntry。如果是刚刚初始化 LogBuffer,mTimes 为空,循环不执行。

到这里,LogBuffer 就的初始化就完成了。

二、LogListener 的初始化

LogListener 是在 main 函数里初始化的:

c 复制代码
int main(int argc, char* argv[]) {
    // ...

    // 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()) {
        exit(1);
    }

    // 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)) {
        exit(1);
    }

    // ...
}

// system/core/logd/LogListener.cpp
LogListener::LogListener(LogBuffer* buf, LogReader* reader)
    : SocketListener(getLogSocket(), false), logbuf(buf), reader(reader) {
}

LogReader 在客户从 logd 中读取数据时使用,这里我们先把它放一放。在本篇,我们先看往 logd 写数据这一部分。

c 复制代码
// system/core/logd/LogListener.cpp
LogListener::LogListener(LogBuffer* buf, LogReader* reader)
    : SocketListener(getLogSocket(), false), logbuf(buf), reader(reader) {
}

// system/core/logd/LogListener.h
class LogListener : public SocketListener {
    LogBuffer* logbuf;
    LogReader* reader;

   public:
    LogListener(LogBuffer* buf, LogReader* reader);

   protected:
    virtual bool onDataAvailable(SocketClient* cli);

   private:
    static int getLogSocket();
};

可以看到,LogListener 构造函数里并没有太多的工作要做,只是调用父类 SocketListener 的构造函数,然后通过传递进来的参数初始化成员变量。

getLogSocket() 用于获取 UNIX Domain Socket /dev/socket/logdw:

c 复制代码
// system/core/logd/LogListener.cpp
int LogListener::getLogSocket() {
    static const char socketName[] = "logdw";
    int sock = android_get_control_socket(socketName);

    if (sock < 0) {
        sock = socket_local_server(
            socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_DGRAM);
    }

    int on = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
        return -1;
    }
    return sock;
}

// system/core/libcutils/include/cutils/sockets.h

// Linux "abstract" (non-filesystem) namespace
#define ANDROID_SOCKET_NAMESPACE_ABSTRACT 0
// Android "reserved" (/dev/socket) namespace
#define ANDROID_SOCKET_NAMESPACE_RESERVED 1
// Normal filesystem namespace
#define ANDROID_SOCKET_NAMESPACE_FILESYSTEM 2

这里通过 android_get_control_socket 直接获取到 init 进程初始化好的 socket fd。

接下来需要关注的是 setsockopt(),通过设置 SO_PASSCRED,能够接收一个 SCM_CREDENTIALS 消息,消息中包含发送者的 pid, uid, 和 gid。该消息通过 struct ucred 结构返回。通过这个选项,我们就能够知道是谁写入了 log。

c 复制代码
struct ucred {
    pid_t pid;    /* process ID of the sending process */
    uid_t uid;    /* user ID of the sending process */
    gid_t gid;    /* group ID of the sending process */
};

另外,由于 /dev/socket/logdw 的类型是 dgram,所以传给 SocketListener 的第二个参数 listen == false。

父类 SocketListener 的构造函数如下:

c 复制代码
SocketListener::SocketListener(int socketFd, bool listen) {
    init(NULL, socketFd, listen, false);
}

void SocketListener::init(const char *socketName, int socketFd, bool listen, bool useCmdNum) {
    mListen = listen;
    mSocketName = socketName;
    mSock = socketFd;
    mUseCmdNum = useCmdNum;
    pthread_mutex_init(&mClientsLock, NULL);
    mClients = new SocketClientCollection();
}

// system/core/libsysutils/include/sysutils/SocketClient.h
typedef android::sysutils::List<SocketClient *> SocketClientCollection;

主要是内部成员的初始化,没什么特别要说的。

三、监听客户端请求

初始化 LogListener 后,main 函数执行 swl->startListener(600) 启动服务端 socket,开始监听客户请求。startListener 是 SocketListener 中的方法:

c 复制代码
// mSocketName == null, mSock != -1, mListen == false, mUseCmdNum == false
int SocketListener::startListener(int backlog) {

    if (!mSocketName && mSock == -1) { // 不进入
        SLOGE("Failed to start unbound listener");
        errno = EINVAL;
        return -1;
    } else if (mSocketName) { // 不进入
        if ((mSock = android_get_control_socket(mSocketName)) < 0) {
            SLOGE("Obtaining file descriptor socket '%s' failed: %s",
                 mSocketName, strerror(errno));
            return -1;
        }
        SLOGV("got mSock = %d for %s", mSock, mSocketName);
        fcntl(mSock, F_SETFD, FD_CLOEXEC);
    }

   
    if (mListen && listen(mSock, backlog) < 0) { // 不进入
        SLOGE("Unable to listen on socket (%s)", strerror(errno));
        return -1;
    } else if (!mListen) // 走这里,new 一个 SocketClient
        mClients->push_back(new SocketClient(mSock, false, mUseCmdNum));

    if (pipe(mCtrlPipe)) { // 初始化一个 pipe fd
        SLOGE("pipe failed (%s)", strerror(errno));
        return -1;
    }

    // 开启一个新线程 threadStart
    if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {
        SLOGE("pthread_create (%s)", strerror(errno));
        return -1;
    }

    return 0;
}

接着看下 SocketClient 的构造函数:

c 复制代码
SocketClient::SocketClient(int socket, bool owned, bool useCmdNum) {
    init(socket, owned, useCmdNum);
}

void SocketClient::init(int socket, bool owned, bool useCmdNum) {
    mSocket = socket;
    mSocketOwned = owned;
    mUseCmdNum = useCmdNum;
    pthread_mutex_init(&mWriteMutex, nullptr);
    pthread_mutex_init(&mRefCountMutex, nullptr);
    mPid = -1;
    mUid = -1;
    mGid = -1;
    mRefCount = 1;
    mCmdNum = 0;

    struct ucred creds;
    socklen_t szCreds = sizeof(creds);
    memset(&creds, 0, szCreds);

    int err = getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);
    if (err == 0) {
        mPid = creds.pid;
        mUid = creds.uid;
        mGid = creds.gid;
    }
}

getsockopt 虽然会返回成功,但是 creds 里面的数据都是无效的,这个就是我们本地生成的 socket (而不是某个客户的连接)。

把 mSock 放到 mClients 里面后,再创建一个 mCtrlPipe,这个 pipe 将会用于唤醒 poll 系统调用。这是一种非常常见的用法。

随后,创建一个线程通过 IO 多路复用监听 mClients 和 mCtrlPipe:

c 复制代码
void *SocketListener::threadStart(void *obj) {
    SocketListener *me = reinterpret_cast<SocketListener *>(obj);

    me->runListener();
    pthread_exit(nullptr);
    return nullptr;
}

void SocketListener::runListener() {
    while (true) {
        // 需要监听的 fd
        std::vector<pollfd> fds;

        // 收集需要监听的 fd
        pthread_mutex_lock(&mClientsLock);
        fds.reserve(2 + mClients.size());
        fds.push_back({.fd = mCtrlPipe[0], .events = POLLIN});
        if (mListen) fds.push_back({.fd = mSock, .events = POLLIN});
        
        for (auto pair : mClients) {
            // NB: calling out to an other object with mClientsLock held (safe)
            const int fd = pair.second->getSocket();
            if (fd != pair.first) SLOGE("fd mismatch: %d != %d", fd, pair.first);
            fds.push_back({.fd = fd, .events = POLLIN});
        }
        pthread_mutex_unlock(&mClientsLock);

        SLOGV("mListen=%d, mSocketName=%s", mListen, mSocketName);
        // 调用 poll 开始监听多个 fd
        int rc = TEMP_FAILURE_RETRY(poll(fds.data(), fds.size(), -1));
        if (rc < 0) {
            SLOGE("poll failed (%s) mListen=%d", strerror(errno), mListen);
            sleep(1);
            continue;
        }

        // pipe fd 有事件到来
        if (fds[0].revents & (POLLIN | POLLERR)) {
            char c = CtrlPipe_Shutdown;
            TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
            if (c == CtrlPipe_Shutdown) {
                break;
            }
            continue;
        }

        // mListen false 不进入 if
        if (mListen && (fds[1].revents & (POLLIN | POLLERR))) {
            int c = TEMP_FAILURE_RETRY(accept4(mSock, nullptr, nullptr, SOCK_CLOEXEC));
            if (c < 0) {
                SLOGE("accept failed (%s)", strerror(errno));
                sleep(1);
                continue;
            }
            pthread_mutex_lock(&mClientsLock);
            mClients[c] = new SocketClient(c, true, mUseCmdNum);
            pthread_mutex_unlock(&mClientsLock);
        }

        // 处理所有可读的 socket

        // Add all active clients to the pending list first, so we can release
        // the lock before invoking the callbacks.
        std::vector<SocketClient*> pending;
        pthread_mutex_lock(&mClientsLock);
        const int size = fds.size();
        for (int i = mListen ? 2 : 1; i < size; ++i) {
            const struct pollfd& p = fds[i];
            if (p.revents & (POLLIN | POLLERR)) {
                auto it = mClients.find(p.fd);
                if (it == mClients.end()) {
                    SLOGE("fd vanished: %d", p.fd);
                    continue;
                }
                SocketClient* c = it->second;
                pending.push_back(c);
                c->incRef();
            }
        }
        pthread_mutex_unlock(&mClientsLock);

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

虽然这段代码很长,但是它的逻辑还是很直接的:

  • 用 poll 在所有描述符上等待
  • 往 mCtrlPipe 写入 CtrlPipe_Shutdown 后,线程退出。其他字符只会唤醒 poll
  • 处理所有可读的 socket(回调子类的 onDataAvailable 函数)

对于 LogListener 来说,mClients 永远只会有一个 socket(就是前面我们创建的那一个)。当它可读时,表示有客户端要写入 log。


四、参考资料

Android log 机制 - logd 如何接收 log 数据(上)
Android log 机制 - logd 如何接收 log 数据(下)

相关推荐
Kapaseker10 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴10 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭20 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab21 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe1 天前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少1 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker1 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋1 天前
Android 协程时代,Handler 应该退休了吗?
android
火柴就是我2 天前
让我们实现一个更好看的内部阴影按钮
android·flutter