系列目录第一篇全景图与架构概览 | 第二篇logd守护进程—启动、初始化与Socket通信 | 第三篇liblog库—日志写入的完整链路 | 第四篇日志写入接口—Java层与Native层 | 第五篇日志读取—logcat源码深度分析 | 第六篇日志缓冲区管理—容量、裁剪与统计机制 | 第七篇实战调试与常见问题分析本篇是全系列最核心的一篇逐函数拆解从__android_log_buf_write()到 Socketwritev()的完整调用链。一、先看全局一条日志的旅程在深入源码之前先建立全局视角。无论是 Java 层的Log.d()、Native 层的ALOGD()还是Slog.d()、EventLog.writeEvent()最终都收敛到同一个函数Log.d() / ALOGD() / Slog.d() / EventLog.writeEvent() │ ▼ __android_log_buf_write(bufID, prio, tag, msg) ← 唯一入口 │ ▼ write_to_log(bufID, vec, 3) ← 函数指针 │ ├── 首次调用初始化 → 打开 socket → 替换指针 │ └── 后续调用直接分发 │ ├── logdWrite() → writev(/dev/socket/logdw) ← 主通道 └── pmsgWrite() → writev(/dev/pmsg0) ← 兜底通道liblog 模块的源码结构system/core/liblog/ ├── logger_write.c // 入口 函数指针机制 transport 管理 ├── logd_writer.c // logd 通道socket 连接 writev 发送 ├── pmsg_writer.c // pstore 兜底/dev/pmsg0 ├── config_write.c // transport 注册 ├── logger_lock.c // 线程安全单把 mutex └── log_is_loggable.c // 运行时级别过滤二、入口函数__android_log_buf_write()源码路径system/core/liblog/logger_write.c所有日志接口最终都调用这个函数。它只做四件事tag 路由、FATAL 处理、构建 iovec、调用函数指针。int__android_log_buf_write(intbufID,intprio,constchar*tag,constchar*msg){structiovecvec[3];chartmp_tag[32];if(!tag)tag;// 步骤1tag 自动路由到 radio 缓冲区// RIL/IMS/CDMA/PHONE/SMS 等无线相关 tag 自动重定向到 LOG_ID_RADIOif((bufID!LOG_ID_RADIO)(!strcmp(tag,HTC_RIL)||!strncmp(tag,RIL,3)||!strncmp(tag,IMS,3)||!strcmp(tag,AT)||!strcmp(tag,GSM)||!strcmp(tag,STK)||!strcmp(tag,CDMA)||!strcmp(tag,PHONE)||!strcmp(tag,SMS))){bufIDLOG_ID_RADIO;snprintf(tmp_tag,sizeof(tmp_tag),use-Rlog/RLOG-%s,tag);tagtmp_tag;}// 步骤2FATAL 级别日志写入 tombstone#if__BIONIC__if(prioANDROID_LOG_FATAL){android_set_abort_message(msg);}#endif// 步骤3构建 iovec 数组[prio, tag\0, msg\0]vec[0].iov_base(unsignedchar*)prio;vec[0].iov_len1;vec[1].iov_base(void*)tag;vec[1].iov_lenstrlen(tag)1;vec[2].iov_base(void*)msg;vec[2].iov_lenstrlen(msg)1;// 步骤4调用函数指针returnwrite_to_log(bufID,vec,3);}关键设计这个函数不构建 headerandroid_log_header_t由下游logdWrite()构建只负责参数校验和 iovec 打包。iovec 格式为[prio, tag\0, msg\0]简洁高效。三、函数指针机制一次初始化永久复用liblog 使用函数指针替换实现懒初始化。核心只有三行代码// 初始指向初始化函数staticint(*write_to_log)(...)__write_to_log_init;// 初始化完成后在 __write_to_log_init 内部替换write_to_log__write_to_log_daemon;流程第1次调用 write_to_log → __write_to_log_init() ├── 加锁 双重检查 ├── __write_to_log_initialize() │ ├── logdOpen() → socket() connect(/dev/socket/logdw) │ └── pmsgOpen() → open(/dev/pmsg0) ├── write_to_log __write_to_log_daemon ← 替换指针 └── 尾递归 → 进入快速路径 第2次及以后 write_to_log → __write_to_log_daemon() ← 直接干活零初始化开销staticint__write_to_log_init(log_id_tlog_id,structiovec*vec,size_tnr){__android_log_lock();if(write_to_log__write_to_log_init){// 双重检查intret__write_to_log_initialize();// 打开 socket/pmsgif(ret0){__android_log_unlock();returnret;}write_to_log__write_to_log_daemon;// ★ 替换指针 ★}__android_log_unlock();returnwrite_to_log(log_id,vec,nr);// 尾递归 → 快速路径}初始化完成后每次写日志走__write_to_log_daemon()staticint__write_to_log_daemon(log_id_tlog_id,structiovec*vec,size_tnr){// 步骤0空消息检查// 步骤1权限检查SECURITY 日志校验 UID/GID普通日志校验 isLoggable// 步骤2clock_gettime() 获取时间戳// 步骤3遍历 transport 链表逐个调用 node-write()// → logdWrite() → writev(/dev/socket/logdw)// → pmsgWrite() → writev(/dev/pmsg0)}三个函数的角色__write_to_log_init是初始化守卫只执行一次__write_to_log_initialize做实际初始化打开 socket__write_to_log_daemon是快速路径每次调用。函数指针替换后后续调用零额外开销。四、logdWrite()通往 logd 的主通道源码路径system/core/liblog/logd_writer.c这是日志写入的核心函数负责构建 header、拼接 iovec、发送到 logd。4.1 打开连接logdOpen()staticintlogdOpen(){// 创建 SOCK_DGRAM socket无连接每条消息独立边界intisocket(PF_UNIX,SOCK_DGRAM|SOCK_CLOEXEC,0);fcntl(i,F_SETFL,O_NONBLOCK);// connect() 绑定到 /dev/socket/logdwstructsockaddr_unun;un.sun_familyAF_UNIX;strcpy(un.sun_path,/dev/socket/logdw);connect(i,(structsockaddr*)un,sizeof(un));logdLoggerWrite.context.socki;}SOCK_DGRAM保证消息边界connect()绑定远端地址后可以直接用writev()发送无需每次指定目标。O_NONBLOCK避免阻塞。4.2 发送日志logdWrite()staticintlogdWrite(log_id_tlogId,structtimespec*ts,structiovec*vec,size_tnr){structiovecnewVec[nr1];// header 原始 iovecandroid_log_header_theader;// 1. 构建 headerid tid realtimeheader.idlogId;header.tidgettid();header.realtime.tv_sects-tv_sec;header.realtime.tv_nsects-tv_nsec;newVec[0].iov_baseheader;newVec[0].iov_lensizeof(header);// 2. 将原始 iovecprio tag msg追加到 header 之后for(size_ti0;inr;i){newVec[i1]vec[i];}// 3. writev() 一次性发送header prio tag msgintretwritev(logdLoggerWrite.context.sock,newVec,nr1);// 4. ENOTCONNlogd 重启 → 关闭旧 socket重新打开重试if(ret-ENOTCONN){logdClose();logdOpen();retwritev(logdLoggerWrite.context.sock,newVec,nr1);}// 5. EAGAINsocket 缓冲区满 → 丢失计数 1if(ret-EAGAIN){atomic_fetch_add(dropped,1);}}Socket 上的实际数据布局┌──────────────────────────┐ │ android_log_header_t │ ← 11 字节 (packed) │ id: 1 字节 │ │ tid: 2 字节 │ │ realtime: 8 字节 │ ├──────────────────────────┤ │ prio (1 byte) │ ← 日志级别 ├──────────────────────────┤ │ tag\0 │ ← 标签字符串 ├──────────────────────────┤ │ msg\0 │ ← 消息字符串 └──────────────────────────┘关键点__android_log_buf_write()不构建 headerheader 在这里由logdWrite()构建writev()一次系统调用发送所有数据高效EAGAIN时只计数不阻塞设置了O_NONBLOCK丢失计数在下一次写入时以 EVENT 格式上报给 logdENOTCONN自动重连应对 logd 重启五、pmsgWrite()内核 panic 时的兜底源码路径system/core/liblog/pmsg_writer.cpmsg 通道在正常运行时将日志写入/dev/pmsg0内核暂存于 RAM。系统崩溃kernel panic时pstore 机制将其刷入 ramoops 区域重启后可从/sys/fs/pstore/读取。staticintpmsgWrite(log_id_tlogId,structtimespec*ts,structiovec*vec,size_tnr){android_pmsg_log_header_tpmsgHeader;// 额外头magic len uid pidandroid_log_header_theader;// 标准头id tid realtimepmsgHeader.magicLOGGER_MAGIC;pmsgHeader.lensizeof(pmsgHeader)sizeof(header);pmsgHeader.uid__android_log_uid();pmsgHeader.pidgetpid();header.idlogId;header.tidgettid();header.realtime*ts;// 组装 newVec [pmsgHeader, header, prio, tag, msg]newVec[0]{pmsgHeader,sizeof(pmsgHeader)};newVec[1]{header,sizeof(header)};// ... 追加原始 iovec ...writev(pmsgLoggerWrite.context.fd,newVec,nr2);}双写机制的生存策略正常情况 logdWrite() → writev(logdw) → logd 接收 → LogBuffer ✓ pmsgWrite() → writev(/dev/pmsg0) → 内核暂存 ✓ 系统 crash (内核 panic) logdWrite() → logd 进程已死 ✗ pmsgWrite() → pstore 刷入 ramoops → 重启后可读 ✓这就是为什么即便系统崩溃通过logcat -L或/sys/fs/pstore/仍能恢复部分关键日志。六、Transport 注册与线程安全6.1 Transport 链表源码路径system/core/liblog/config_write.c两个 transport 在编译时静态注册到全局链表// logd 通道structandroid_log_transport_writelogdLoggerWrite{.node{logdLoggerWrite.node,logdLoggerWrite.node},.namelogd,.availablelogdAvailable,.openlogdOpen,.closelogdClose,.writelogdWrite,};// pmsg 通道structandroid_log_transport_writepmsgLoggerWrite{.node{pmsgLoggerWrite.node,pmsgLoggerWrite.node},.namepmsg,.availablepmsgAvailable,.openpmsgOpen,.closepmsgClose,.writepmsgWrite,};每个 transport 提供统一接口{available, open, close, write}。__write_to_log_daemon()遍历链表时通过node-logMask按位判断该 transport 是否处理当前 log_id。6.2 线程安全源码路径system/core/liblog/logger_lock.c整个 liblog 只有一把锁log_init_lock仅用于保护初始化过程。初始化完成后__write_to_log_daemon()中没有锁——因为 transport 链表在初始化后不再变更各 transport 的write()内部自行处理并发logd 端通过 epoll 和 LogBuffer 的mLogElementsLock来保证。七、完整调用链__android_log_buf_write(bufID, prio, tag, msg) │ ├── tag 自动路由RIL/IMS/PHONE 等 → LOG_ID_RADIO ├── FATAL 设置 abort message ├── 构建 iovec[3] [prio, tag\0, msg\0] │ └── write_to_log(bufID, vec, 3) ─── 函数指针 │ ├── 首次调用__write_to_log_init() │ ├── logdOpen() → socket() connect(/dev/socket/logdw) │ ├── pmsgOpen() → open(/dev/pmsg0) │ └── write_to_log __write_to_log_daemon ← 替换指针 │ └── 后续调用__write_to_log_daemon() ├── 权限检查 isLoggable 过滤 ├── clock_gettime() 获取时间戳 └── 遍历 transport 链表 ├── logdWrite(logId, ts, vec, nr) │ ├── 构建 header {id, tid, realtime} │ ├── 组装 newVec [header, prio, tag, msg] │ └── writev(sock, newVec) → /dev/socket/logdw │ │ │ ▼ │ ┌──────────────────┐ │ │ logd 守护进程 │ │ │ LogListener │ │ │ recvmsg() │ │ │ → LogBuffer.log()│ │ └──────────────────┘ │ └── pmsgWrite(logId, ts, vec, nr) ├── 构建 pmsgHeader {magic, len, uid, pid} ├── 构建 header {id, tid, realtime} └── writev(/dev/pmsg0) → panic 时刷入 pstore八、本篇总结函数职责位置__android_log_buf_write()唯一入口路由、FATAL、构建 ioveclogger_write.c__write_to_log_init()初始化守卫加锁、双重检查、触发初始化、替换指针logger_write.c__write_to_log_daemon()快速路径权限检查、时间戳、遍历 transport 写入logger_write.clogdWrite()主通道构建 header writev 发送到 logdwlogd_writer.cpmsgWrite()兜底写入 /dev/pmsg0panic 时通过 pstore 恢复pmsg_writer.c核心设计亮点函数指针替换一次初始化之后零开销双写机制logd主通道 pmsg兜底系统崩溃日志不丢失header 延迟构建入口函数只打包 iovecheader 由 logdWrite 构建职责分离DGRAM socket天然消息边界无需拆包