logd 写日志过程分析一

这是一个介绍 Android 平台日志系统的系列文章:

本文基于 AOSP android-10.0.0_r41 版本讲解

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

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

LogBuffer 的初始化

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

cpp 复制代码
    // 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 的构造函数

cpp 复制代码
// 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 来表示。

cpp 复制代码
// 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 类型:

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

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

    // ...
};

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

cpp 复制代码
// 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 的最大容量:

cpp 复制代码
// 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 的时间:

cpp 复制代码
// 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部分:

cpp 复制代码
// 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 函数里初始化的:

cpp 复制代码
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 写数据这一部分。

cpp 复制代码
// 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:

cpp 复制代码
// 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。

java 复制代码
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 的构造函数如下:

cpp 复制代码
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 中的方法:

cpp 复制代码
// 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 的构造函数:

cpp 复制代码
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:

cpp 复制代码
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 不进入 false
        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。

参考资料

关于

我叫阿豪,2015 年本科毕业于国防科学技术大学指挥信息系统专业,毕业后从事信息化装备的研发工作,工作内容主要涉及 Android Framework 与 Linux Kernel。

如果你对 Android Framework 感兴趣或者正在学习 Android Framework,可以关注我的微信公众号和抖音,我会持续分享我的学习经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。

相关推荐
GEEKVIP几秒前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑
Jouzzy7 小时前
【Android安全】Ubuntu 16.04安装GDB和GEF
android·ubuntu·gdb
极客先躯7 小时前
java和kotlin 可以同时运行吗
android·java·开发语言·kotlin·同时运行
Good_tea_h10 小时前
Android中的单例模式
android·单例模式
计算机源码社15 小时前
分享一个基于微信小程序的居家养老服务小程序 养老服务预约安卓app uniapp(源码、调试、LW、开题、PPT)
android·微信小程序·uni-app·毕业设计项目·毕业设计源码·计算机课程设计·计算机毕业设计开题
丶白泽15 小时前
重修设计模式-结构型-门面模式
android
晨春计17 小时前
【git】
android·linux·git
标标大人17 小时前
c语言中的局部跳转以及全局跳转
android·c语言·开发语言
竹林海中敲代码18 小时前
Qt安卓开发连接手机调试(红米K60为例)
android·qt·智能手机
木鬼与槐18 小时前
MySQL高阶1783-大满贯数量
android·数据库·mysql