Android 7系统日志(二)logd守护进程启动、初始化与Socket通信

系列目录第一篇:全景图与架构概览 | 第二篇:logd守护进程---启动、初始化与Socket通信 | 第三篇:liblog库---日志写入的完整链路 | 第四篇:日志写入接口---Java层与Native层 | 第五篇:日志读取---logcat源码深度分析 | 第六篇:日志缓冲区管理---容量、裁剪与统计机制 | 第七篇:实战调试与常见问题分析


一、logd 的定位

回顾第一篇的六层架构,logd 处于核心管理层。所有日志写入汇入 logd,读取也由 logd 提供。本篇聚焦 logd 的启动流程、三个 Socket 的创建与工作机制、以及主循环中各线程的角色。

源码路径system/core/logd/main.cpp


二、启动时机:logd.rc 与 init.rc 的配合

logd 是 Android 中启动最早的服务之一。其 service 定义在独立的 rc 文件中:

源码路径system/core/logd/logd.rc

bash 复制代码
service logd /system/bin/logd
    socket logd stream 0666 logd logd       # 控制命令通道
    socket logdr seqpacket 0666 logd logd    # 日志读取通道
    socket logdw dgram 0222 logd logd        # 日志写入通道
    group root system readproc
    writepid /dev/cpuset/system-background/tasks

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

源码路径system/core/rootdir/init.rc

bash 复制代码
# init.rc 在 on boot 阶段触发启动
on boot
    # ... 其他初始化 ...
    start logd
    start logd-reinit

关键点:

  • 三个 Socket 由 init 进程在 fork 前创建 ,logd 通过 android_get_control_socket() 继承 fd
  • logdwSOCK_DGRAM,权限 0222(任何人可写);SO_PASSCRED 选项由 logd 在代码中通过 setsockopt() 设置
  • logdrSOCK_SEQPACKET,保证有序和消息边界
  • logdSOCK_STREAM,用于双向命令通信
  • 用户身份切换由 main() 中的 drop_privs() 函数完成,而非 rc 中的 user 指令

三、三个 Socket 的详细解析

3.1 logdw --- 日志写入通道

复制代码
socket logdw dgram 0222 logd logd
属性 设计理由
类型 dgram (SOCK_DGRAM) 天然消息边界,一条 writev = 一条日志
权限 0222 任何人可写,不可读
SO_PASSCRED 代码中设置 通过 SCM_CREDENTIALS 获取发送方 UID/PID,内核保证,无法伪造

源码路径system/core/logd/LogListener.cpp

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

    // 设置 SO_PASSCRED,使 recvmsg 能获取发送方的 UID/PID
    int on = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
        return -1;
    }
    return sock;
}

3.2 logdr --- 日志读取通道

复制代码
socket logdr seqpacket 0666 logd logd
  • seqpacket (SOCK_SEQPACKET):保证有序和完整性,保持消息边界
  • 权限 0666:允许 logcat 等工具连接读取

3.3 logd --- 控制命令通道

复制代码
socket logd stream 0666 logd logd
  • stream (SOCK_STREAM):命令长度不定,需双向通信,用换行分隔命令
  • 处理 cleargetLogSizesetLogSizegetStatisticsgetPruneListsetPruneListreinit 等命令

四、main() 启动流程

源码路径system/core/logd/main.cpp

c 复制代码
int main(int argc, char *argv[]) {
    // 阶段1:打开内核日志文件
    int fdPmesg = -1;
    bool klogd = property_get_bool("logd.kernel",
                                   BOOL_DEFAULT_TRUE |
                                   BOOL_DEFAULT_FLAG_PERSIST |
                                   BOOL_DEFAULT_FLAG_ENG |
                                   BOOL_DEFAULT_FLAG_SVELTE);
    if (klogd) {
        fdPmesg = open("/proc/kmsg", O_RDONLY | O_NDELAY);
    }
    fdDmesg = open("/dev/kmsg", O_WRONLY);

    // 阶段2:处理 --reinit 参数(向已有 logd 进程发送命令后退出)
    if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) {
        int sock = TEMP_FAILURE_RETRY(
            socket_local_client("logd",
                                ANDROID_SOCKET_NAMESPACE_RESERVED,
                                SOCK_STREAM));
        if (sock < 0) return -errno;
        static const char reinit[] = "reinit";
        ssize_t ret = TEMP_FAILURE_RETRY(write(sock, reinit, sizeof(reinit)));
        if (ret < 0) return -errno;
        struct pollfd p;
        memset(&p, 0, sizeof(p));
        p.fd = sock;
        p.events = POLLIN;
        ret = TEMP_FAILURE_RETRY(poll(&p, 1, 1000));
        if (ret < 0) return -errno;
        if ((ret == 0) || !(p.revents & POLLIN)) return -ETIME;
        static const char success[] = "success";
        char buffer[sizeof(success) - 1];
        memset(buffer, 0, sizeof(buffer));
        ret = TEMP_FAILURE_RETRY(read(sock, buffer, sizeof(buffer)));
        if (ret < 0) return -errno;
        return strncmp(buffer, success, sizeof(success) - 1) != 0;
    }

    // 阶段3:初始化信号量,创建 reinit 线程
    sem_init(&reinit, 0, 0);
    sem_init(&uidName, 0, 0);
    sem_init(&sem_name, 0, 1);
    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, NULL)) {
                reinit_running = false;
            }
        }
        pthread_attr_destroy(&attr);
    }

    // 阶段4:权限降级
    if (drop_privs() != 0) {
        return -1;
    }

    // 阶段5:创建 LogBuffer(唯一实例)
    LastLogTimes *times = new LastLogTimes();
    logBuf = new LogBuffer(times);

    signal(SIGHUP, reinit_signal_handler);

    if (property_get_bool("logd.statistics",
                          BOOL_DEFAULT_TRUE |
                          BOOL_DEFAULT_FLAG_PERSIST |
                          BOOL_DEFAULT_FLAG_ENG |
                          BOOL_DEFAULT_FLAG_SVELTE)) {
        logBuf->enableStatistics();
    }

    // 阶段6:创建 LogReader 线程(监听 logdr)
    LogReader *reader = new LogReader(logBuf);
    if (reader->startListener()) {
        exit(1);
    }

    // 阶段7:创建 LogListener 线程(监听 logdw)
    LogListener *swl = new LogListener(logBuf, reader);
    if (swl->startListener(600)) {
        exit(1);
    }

    // 阶段8:创建 CommandListener 线程(监听 logd)
    CommandListener *cl = new CommandListener(logBuf, reader, swl);
    if (cl->startListener()) {
        exit(1);
    }

    // 阶段9:创建 LogAudit 线程(监听 NETLINK_AUDIT,可选)
    bool auditd = property_get_bool("logd.auditd",
                                    BOOL_DEFAULT_TRUE |
                                    BOOL_DEFAULT_FLAG_PERSIST);
    LogAudit *al = NULL;
    if (auditd) {
        al = new LogAudit(logBuf, reader,
                          property_get_bool("logd.auditd.dmesg",
                                            BOOL_DEFAULT_TRUE |
                                            BOOL_DEFAULT_FLAG_PERSIST)
                              ? fdDmesg
                              : -1);
    }

    // 阶段10:创建 LogKlog 线程(监听 /proc/kmsg,可选)
    LogKlog *kl = NULL;
    if (klogd) {
        kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != NULL);
    }

    // 阶段11:读取历史内核日志
    readDmesg(al, kl);

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

    // 阶段12:主线程阻塞,等待信号
    TEMP_FAILURE_RETRY(pause());
    exit(0);
}

核心设计要点:

  • 只有一个 LogBuffer 实例logBuf),内部通过 mLast[LOG_ID_MAX] 等数组实现 7 个逻辑缓冲区
  • 五个 SocketListener 线程各自独立运行,底层使用 epoll 事件驱动
  • drop_privs() 将进程从 root 降级到 AID_LOGD,仅保留 CAP_SYSLOGCAP_AUDIT_CONTROL 两个能力
  • --reinit 参数 :向已有 logd 进程发送 reinit 命令后退出,不启动新进程,用于属性变更后重新初始化
  • LogAudit/LogKlog 可选 :通过 logd.auditdlogd.kernel 属性控制是否启用
  • startListener() 失败处理 :LogReader/LogListener/CommandListener 失败直接 exit(1),LogAudit/LogKlog 失败则 delete 释放
  • readDmesg() 在启动时读取 /proc/kmsg 中的历史内核日志,通过 klogctl(KLOG_READ_ALL) 获取,分发给 LogAudit 和 LogKlog 处理

五、LogListener --- 日志写入的接收端

源码路径system/core/logd/LogListener.cpp

c 复制代码
LogListener::LogListener(LogBuffer *buf, LogReader *reader) :
        SocketListener(getLogSocket(), false),
        logbuf(buf),
        reader(reader) {
}

bool LogListener::onDataAvailable(SocketClient *cli) {
    static bool name_set;
    if (!name_set) {
        prctl(PR_SET_NAME, "logd.writer");
        name_set = true;
    }

    // 1. recvmsg() 从 logdw 读取数据,同时获取 SCM_CREDENTIALS
    char buffer[sizeof(android_log_header_t) + LOGGER_ENTRY_MAX_PAYLOAD];
    struct iovec iov = { buffer, sizeof(buffer) };
    char control[CMSG_SPACE(sizeof(struct ucred))];
    struct msghdr hdr = { NULL, 0, &iov, 1, control, sizeof(control), 0 };

    int socket = cli->getSocket();
    ssize_t n = recvmsg(socket, &hdr, 0);
    if (n <= (ssize_t)(sizeof(android_log_header_t))) {
        return false;
    }

    // 2. 从辅助数据中提取 UID/PID(内核保证,无法伪造)
    struct ucred *cred = NULL;
    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr);
    while (cmsg != NULL) {
        if (cmsg->cmsg_level == SOL_SOCKET
                && cmsg->cmsg_type == SCM_CREDENTIALS) {
            cred = (struct ucred *)CMSG_DATA(cmsg);
            break;
        }
        cmsg = CMSG_NXTHDR(&hdr, cmsg);
    }

    if (cred == NULL) {
        return false;
    }

    // 3. 过滤 logd 自身的日志,避免循环
    if (cred->uid == AID_LOGD) {
        return false;
    }

    // 4. 解析日志头,校验 log_id 合法性
    android_log_header_t *header = reinterpret_cast<android_log_header_t *>(buffer);
    if (header->id >= LOG_ID_MAX || header->id == LOG_ID_KERNEL) {
        return false;
    }

    // 5. security 缓冲区权限检查
    if ((header->id == LOG_ID_SECURITY) &&
            (!__android_log_security() ||
             !clientHasLogCredentials(cred->uid, cred->gid, cred->pid))) {
        return false;
    }

    // 6. 写入 LogBuffer
    char *msg = ((char *)buffer) + sizeof(android_log_header_t);
    n -= sizeof(android_log_header_t);
    if (logbuf->log((log_id_t)header->id, header->realtime,
            cred->uid, cred->pid, header->tid, msg,
            ((size_t) n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX) >= 0) {
        reader->notifyNewLog();
    }
    return true;
}

SCM_CREDENTIALS 的信任根 :UID/PID 由 Linux 内核在 recvmsg() 时填入,调用方无法伪造,为后续裁剪策略提供可靠基础。

log_id 路由header->id 直接作为 log_id_t 传入 LogBuffer::log(),由 LogBuffer 内部通过 mLast[id] 迭代器定位到对应逻辑缓冲区。


六、LogReader --- 日志读取的服务端

源码路径system/core/logd/LogReader.cpp

c 复制代码
LogReader::LogReader(LogBuffer *logbuf) :
        SocketListener(getLogSocket(), true),
        mLogbuf(*logbuf) {
}

// 当有新日志到达时,通知所有连接的客户端
void LogReader::notifyNewLog() {
    FlushCommand command(*this);
    runOnEachSocket(&command);
}

bool LogReader::onDataAvailable(SocketClient *cli) {
    static bool name_set;
    if (!name_set) {
        prctl(PR_SET_NAME, "logd.reader");
        name_set = true;
    }

    // 读取客户端发送的控制命令(文本协议)
    char buffer[255];
    int len = read(cli->getSocket(), buffer, sizeof(buffer) - 1);
    if (len <= 0) {
        doSocketDelete(cli);
        return false;
    }
    buffer[len] = '\0';

    // 解析命令参数: tail=, start=, timeout=, lids=, pid=
    // 例如: "tail=0 lids=0,1,3 pid=1234"
    unsigned long tail = 0;
    log_time start(log_time::EPOCH);
    uint64_t timeout = 0;
    unsigned int logMask = -1;
    pid_t pid = 0;

    // ... 解析参数代码 ...

    // 创建 FlushCommand 将日志发送给客户端
    FlushCommand command(*this, logMask, pid, tail, start, timeout);
    command.runSocketCommand(cli);

    return true;
}

交互流程:

复制代码
logcat 进程                          logd 端 (LogReader)
─────────────────────────────────────────────────────────
socket connect(logdr)
→ 发送 "tail=0 lids=0"             → accept() 新连接
→ 进入读取循环                      → onDataAvailable() 解析命令
    → recv(logdr) 等待               → FlushCommand.runSocketCommand()
    → 收到日志 → 格式化为文本           → LogBuffer.flushTo()
    → 输出到 stdout                     → 遍历 LogBuffer 取出日志
                                     → send(logdr, client)

每个 logcat 连接对应一个 LogTimeEntry 对象,记录客户端关心的缓冲区、起始位置、超时等状态。


七、CommandListener --- 控制命令

源码路径system/core/logd/CommandListener.cpp

c 复制代码
CommandListener::CommandListener(LogBuffer *buf, LogReader *reader,
                                 LogListener *swl) :
        FrameworkListener(getLogSocket()) {
    registerCmd(new ClearCmd(buf));           // "clear" → logcat -c
    registerCmd(new GetBufSizeCmd(buf));       // "getLogSize" → logcat -g
    registerCmd(new SetBufSizeCmd(buf));       // "setLogSize" → 动态调整缓冲区大小
    registerCmd(new GetBufSizeUsedCmd(buf));   // "getLogSizeUsed" → 查询已用空间
    registerCmd(new GetStatisticsCmd(buf));    // "getStatistics" → logcat -S
    registerCmd(new SetPruneListCmd(buf));     // "setPruneList" → 设置裁剪白/黑名单
    registerCmd(new GetPruneListCmd(buf));     // "getPruneList" → 查询裁剪白/黑名单
    registerCmd(new ReinitCmd());              // "reinit" → 触发重新初始化
}

源码路径system/core/logd/CommandListener.cpp

c 复制代码
int CommandListener::getLogSocket() {
    static const char socketName[] = "logd";
    int sock = android_get_control_socket(socketName);
    if (sock < 0) {
        sock = socket_local_server(socketName,
                                   ANDROID_SOCKET_NAMESPACE_RESERVED,
                                   SOCK_STREAM);
    }
    return sock;
}

日志清除 clear <id> 本质是向 logd socket 发送命令,logd 收到后调用 LogBuffer::clear() 清空指定逻辑缓冲区。


八、内核日志转发:LogKlog

源码路径system/core/logd/LogKlog.cpp

c 复制代码
LogKlog::LogKlog(LogBuffer *buf, LogReader *reader, int fdWrite,
                 int fdRead, bool auditd) :
        SocketListener(fdRead, false),  // 将 /proc/kmsg 的 fd 作为监听对象
        logbuf(buf),
        reader(reader),
        signature(CLOCK_MONOTONIC),
        initialized(false),
        enableLogging(true),
        auditd(auditd) {
    // 启动时向 /dev/kmsg 写入 "logd.klogd: <signature>" 标记
    static const char klogd_message[] = "%slogd.klogd: %" PRIu64 "\n";
    char buffer[sizeof(priority_message) + sizeof(klogd_message) + 20 - 4];
    snprintf(buffer, sizeof(buffer), klogd_message, priority_message,
        signature.nsec());
    write(fdWrite, buffer, strlen(buffer));
}

bool LogKlog::onDataAvailable(SocketClient *cli) {
    if (!initialized) {
        prctl(PR_SET_NAME, "logd.klogd");
        initialized = true;
    }

    // 从 /proc/kmsg 读取内核日志行
    char buffer[LOGGER_ENTRY_MAX_PAYLOAD];
    size_t len = 0;
    for(;;) {
        ssize_t retval = read(cli->getSocket(), buffer + len, sizeof(buffer) - 1 - len);
        if (retval <= 0) break;
        len += retval;
    }
    if (len == 0) return false;

    // 解析内核日志优先级,转为 Android 级别,写入 logBuf
    char *ptr = buffer;
    while (len > 0) {
        int rc = log(ptr, len);
        if (rc <= 0) break;
        ptr += rc;
        len -= rc;
    }
    return true;
}

内核级别到 Android 的映射:

内核级别 Android 映射
KERN_EMERG/ALERT/CRIT ANDROID_LOG_FATAL
KERN_ERR ANDROID_LOG_ERROR
KERN_WARNING ANDROID_LOG_WARN
KERN_NOTICE/INFO ANDROID_LOG_INFO
KERN_DEBUG ANDROID_LOG_DEBUG

LogKlog 继承 SocketListener,将 /proc/kmsg 的 fd 直接作为 SocketListener 的监听 fd,onDataAvailable() 中循环读取内核日志行,解析 <N> 格式的优先级后调用 log() 写入 logBufLOG_ID_KERNEL 逻辑缓冲区)。


九、SELinux 审计日志:LogAudit

源码路径system/core/logd/LogAudit.cpp

c 复制代码
LogAudit::LogAudit(LogBuffer *buf, LogReader *reader, int fdDmesg) :
        SocketListener(getLogSocket(), false),
        logbuf(buf),
        reader(reader),
        fdDmesg(fdDmesg),
        initialized(false) {
    static const char auditd_message[] = { KMSG_PRIORITY(LOG_INFO),
        'l', 'o', 'g', 'd', '.', 'a', 'u', 'd', 'i', 't', 'd', ':',
        ' ', 's', 't', 'a', 'r', 't', '\n' };
    write(fdDmesg, auditd_message, sizeof(auditd_message));
}

LogAuditgetLogSocket() 通过 audit_open() 打开 NETLINK_AUDIT socket,onDataAvailable() 中调用 audit_get_reply() 接收内核 SELinux 审计事件,解析后调用 logPrint() 写入 LOG_ID_SECURITY 逻辑缓冲区。


十、epoll 事件循环全景

复制代码
                    logd 主线程 (pause阻塞)
                    reinit 线程 (等待信号)
                           │
        ┌──────────────────┼──────────────────┐
        ▼                  ▼                  ▼
   LogListener         LogReader         CommandListener
   线程 (logd.writer)   线程 (logd.reader)  线程 (logd.control)
   epoll → logdw       epoll → logdr       epoll → logd
   onDataAvailable()   onDataAvailable()   runCommand()
        │                  │                  │
        ▼                  ▼                  ▼
   LogBuffer::log()   LogBuffer::flushTo()  LogBuffer::clear()
                                            LogBuffer::setSize()
                                            LogBuffer::getStatistics()
        ▲                  ▲
        │                  │
   LogAudit 线程       LogKlog 线程
   (logd.auditd)       (logd.klog)
   epoll → NETLINK     epoll → /proc/kmsg
   onDataAvailable()   onDataAvailable()

每个 SocketListener 线程内部都使用 epoll 事件驱动,有事件才唤醒处理,高频日志写入也不过度消耗 CPU。


十一、本篇小结

  • logd 由 init.rcon boot 触发 start logd 启动,service 定义在 logd.rc
  • 三个 Socket (logdw/logdr/logd)由 init 预创建,logd 通过 android_get_control_socket() 继承 fd
  • SO_PASSCRED 在代码中通过 setsockopt() 设置,保证 SCM_CREDENTIALS 获取的 UID 不可伪造
  • 只有一个 LogBuffer 实例 ,内部以 LOG_ID_MAX 维度数组管理 7 个逻辑缓冲区
  • 五个线程各自独立运行 epoll 事件循环:LogListener、LogReader、CommandListener、LogAudit、LogKlog
  • LogListener.onDataAvailable()LogBuffer::log()LogReader.notifyNewLog() → logcat 收到日志,形成完整闭环

下一篇将深入 liblog 库 ,逐函数拆解从 __android_log_buf_write() 到 Socket writev 的完整调用链。