当前位置: 首页 > news >正文

Android 7系统日志(一):全景图与架构概览

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


一、为什么要系统学习 Android 日志模块

每个 Android 开发者的第一行调试代码几乎都是Log.d("TAG", "hello world")。这个简单的 API 调用背后,藏着一个从 Java 层到 Native 层、跨进程通信、缓冲区管理、裁剪策略逐层堆叠的精密系统。

当你遇到以下问题时,单纯依赖logcat的简单用法是不够的:

  • “日志丢失”:为什么有些Log.d明明执行了,logcat却看不到?是缓冲区满了,还是被 chatty 机制合并了?
  • “日志延迟”:大量日志输出时,为什么 logcat 的显示总是慢半拍?
  • “内核日志缺失”dmesglogcat显示的日志各自来自哪里?它们之间有什么关系?
  • “SELinux 限制”:为什么某些 Native 进程的日志打不出来?
  • “崩溃日志取证”:进程 crash 后,还没来得及写入 logd 的日志去哪了?pstore 机制如何兜底?

本系列以AOSP 7(android-7.1.2_r36)为基准,从Log.d()一直走到 logd 守护进程的缓冲区,逐层拆解 Android 日志子系统。


二、宏观架构:六层模型

┌──────────────────────────────────────────────────────┐ │ 应用层 / 框架层 │ │ Log.d() / Log.e() → android.util.Log │ │ Slog.d() / Slog.e() → android.util.Slog │ │ EventLog.writeEvent() → android.util.EventLog │ ├──────────────────────────────────────────────────────┤ │ JNI 桥接层 │ │ android_util_Log.cpp → println_native() │ ├──────────────────────────────────────────────────────┤ │ Native liblog 库 │ │ __android_log_buf_write() → logdWrite() │ ├──────────────────────────────────────────────────────┤ │ Socket 传输层 │ │ /dev/socket/logdw (写入) │ │ /dev/socket/logdr (读取) │ │ /dev/socket/logd (控制) │ ├──────────────────────────────────────────────────────┤ │ logd 守护进程 │ │ LogListener → LogBuffer → LogReader │ │ main / system / events / radio / crash / kernel │ ├──────────────────────────────────────────────────────┤ │ 内核层 │ │ /dev/kmsg / pstore (ramoops) │ └──────────────────────────────────────────────────────┘

从"环形缓冲区"到"logd 守护进程"的演进

在 Android 5.0 之前,日志直接依赖内核logger驱动,每个缓冲区对应一个/dev/log/main/dev/log/system等设备节点,应用程序和 logcat 通过write()/read()系统调用直接操作。

缺陷:

  1. 内核态管理太重——缓冲区策略写死在内核,修改困难
  2. 无法按 UID/PID 差异化裁剪
  3. 扩展新缓冲区必须改内核驱动

从 Android 5.0 起引入 logd 守护进程,将日志管理完全移到用户态。Android 7 中这套架构已成熟稳定——日志的写入、存储、裁剪、读取全部由 logd 负责,通信方式从设备节点改为 Unix domain socket。


三、核心组件"三剑客"

3.1 logd — 日志管家(守护进程)

logd 是整个日志系统的大脑,由 init 在系统启动早期拉起:

# system/core/logd/logd.rcservicelogd /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

内部维护七个 LogBuffer(对应log_id_t枚举):

缓冲区默认大小用途典型生产者
main256KB应用层日志Log.d()ALOGD()
system256KB系统框架日志Slog.d()
events256KB系统事件(二进制)EventLog.writeEvent()
radio256KB无线通信日志Rlog.d()
crash256KB崩溃日志系统服务 crash
security256KBSELinux 审计日志LogAudit线程写入
kernel256KB内核日志LogKlog/dev/kmsg读取

多个核心工作线程:

logd 进程 ├── LogListener 线程 → 监听 logdw,接收日志写入 ├── LogReader 线程 → 监听 logdr,服务 logcat 读取 ├── CommandListener → 监听 logd,处理控制命令 ├── LogAudit 线程 → 监听 NETLINK_AUDIT,处理 SELinux 审计日志 └── LogKlog 线程 → 从 /dev/kmsg 读取内核日志

3.2 liblog — 日志读写库

连接日志生产者与 logd 的桥梁,所有日志接口最终都调用它。

system/core/liblog/ ├── logger_write.c // 对外接口:__android_log_buf_write() ├── logger_read.c // 读端核心 ├── logd_writer.c // logd 通道写实现(logdWrite) ├── pmsg_writer.c // pstore 内核兜底 ├── logd_reader.c // 读取接口 └── event_tag_map.c // events 日志 tag 映射

核心设计:传输器抽象——正常路径走logd_writer(socket → logd),内核 panic 时走pmsg_writer/dev/pmsg0)兜底。

3.3 logcat — 日志读取工具

Android 7 中 logcat 是单个 C++ 文件:system/core/logcat/logcat.cpp

通过 socketlogdr连接 logd,指定缓冲区、过滤条件后循环读取,按-v参数格式化输出到 stdout。


四、七大日志缓冲区

4.1 main

最常用的缓冲区。应用层Log.d()、Native 层ALOGD()写入,纯文本格式。

4.2 system

系统框架专用,Slog.d()写入。记录 AMS、WMS 等系统级日志。普通应用无写入权限。

4.3 events

二进制格式的事件缓冲区。通过EventLog.writeEvent()写入,用于记录am_proc_startam_anrbinder_sample等系统关键事件。logcat -b events时会解码为可读文本。

4.4 radio

无线通信日志,Rlog.d()写入,用于调试 modem、RIL 等问题。

4.5 crash

Android 7 新增,专门存储系统服务崩溃日志(注意:应用层 crash 走 tombstone,不走这里)。

4.6 security

SELinux 审计日志缓冲区。存储 SELinux 权限检查拒绝事件,由LogAudit线程从 NETLINK_AUDIT socket 读取后写入。

4.7 kernel

logd 通过KernelLogger/dev/kmsg读取内核环形缓冲区日志并转发。dmesg可直接查看,logcat -b kernel查看 logd 转发的版本。


五、一次日志写入的宏观路径

Log.d("MyTag", "hello")为例:

T0 APP 调用 Log.d("MyTag", "hello") ↓ T1 Log.java → println_native(LOG_ID_MAIN, DEBUG, "MyTag", "hello") ↓ (JNI 调用) T2 android_util_Log.cpp → println_native() ↓ 解析参数,拿到 bufID = LOG_ID_MAIN ↓ 调用 __android_log_buf_write(bufID, ...) T3 liblog/logger_write.c → __android_log_buf_write() ↓ 格式化日志头(android_log_header_t) ↓ 依次调用每个 transport 的 write(): ├── logdWrite() → socket send(logdw, ...) │ ↓ │ ┌────────────────────────────────────────┐ │ │ logd 守护进程 │ │ │ LogListener 线程 → recv(logdw) │ │ │ → 解析日志头 │ │ │ → LogBuffer->log() 写入环形缓冲区 │ │ │ → LogReader 通知等待中的客户端 │ │ └────────────────────────────────────────┘ │ └── pmsgWrite() → /dev/pmsg0 (panic 兜底)

关键数据结构:logger_entry_v4

Socket 传输使用二进制 header + 文本消息体的格式。Android 7 中使用的版本是logger_entry_v4

源码路径system/core/include/log/logger.h

structlogger_entry_v4{uint16_tlen;/* 消息体长度 */uint16_thdr_size;/* header 大小 */int32_tpid;/* 写日志的进程 PID */uint32_ttid;/* 写日志的线程 ID */uint32_tsec;/* 时间戳(秒) */uint32_tnsec;/* 时间戳(纳秒) */uint32_tlid;/* log_id(main/system/events 等) */uint32_tuid;/* 写日志的进程 UID */charmsg[0];/* 消息体(柔性数组) */}__attribute__((__packed__));

六、一次日志读取的宏观路径

adb shell logcat -v time MyTag:* *:S为例:

T0 adb shell logcat -v time MyTag:* *:S ↓ T1 logcat/logcat.cpp → main() ↓ 解析 -b → log_ids = [LOG_ID_MAIN] ↓ 解析 -v → 格式 = FORMAT_TIME ↓ 解析过滤 → MyTag:*, default:silent T2 创建 android_logger_list ↓ socket 连接 logdr T3 发送控制命令:"tail" / "logid" T4 while (1) { android_logger_list_read() ← 阻塞读取 ↓ (logd 端) LogReader 线程 │ 从 LogBuffer 取出下一条日志 │ socket send(logdr) → 回传 ▼ 应用过滤条件 ↓ 匹配 → formatBuf() 格式化输出 ↓ 不匹配 → 跳过 }

七、关键源码文件索引

logd 守护进程

文件说明
system/core/logd/main.cpplogd 入口、参数解析
system/core/logd/LogBuffer.cpp环形缓冲区实现
system/core/logd/LogBufferElement.h日志条目数据结构
system/core/logd/LogListener.cpp监听 logdw socket
system/core/logd/LogReader.cpp监听 logdr socket
system/core/logd/CommandListener.cpp监听 logd socket(控制命令)
system/core/logd/LogStatistics.cppUID/PID/TAG 维度统计
system/core/logd/LogKlog.cpp内核日志转发
system/core/logd/LogAudit.cppSELinux 审计日志

liblog 库

文件说明
system/core/liblog/logger_write.c__android_log_buf_write()
system/core/liblog/logger_read.c读取接口
system/core/liblog/logd_writer.clogd 通道写实现
system/core/liblog/pmsg_writer.cpstore 兜底
system/core/include/log/log.hALOGD 等宏
system/core/include/log/logger.h核心结构体

logcat 工具

文件说明
system/core/logcat/logcat.cpp命令行解析、读取、格式化

Java 层

文件说明
frameworks/base/core/java/android/util/Log.java应用层日志接口
frameworks/base/core/java/android/util/Slog.java系统框架日志接口
frameworks/base/core/java/android/util/EventLog.java事件日志接口
frameworks/base/core/jni/android_util_Log.cppLog.java 的 JNI 实现

八、各篇预告

篇目聚焦
第二篇logd 启动流程、四个 Socket 的创建与权限、LogListener/LogReader 线程初始化
第三篇liblog 写入链路的逐函数剖析:__android_log_buf_writelogd_dgram_write
第四篇Java 层四大接口(Log/Slog/EventLog/Rlog)和 Native 层宏(ALOGD等)的源码
第五篇logcat 的参数解析、缓冲区选择、过滤匹配、格式化输出
第六篇LogBuffer 环形数据结构、按 UID 裁剪策略、LogStatistics 统计
第七篇日志丢失/不输出/延迟的根因排查,结合 bugreport、tombstone 的综合调试
http://www.jsqmd.com/news/1097032/

相关文章:

  • CCRC-CSERE网络安全应急响应工程师认证信息整理
  • Fast-GitHub:国内开发者告别GitHub龟速下载的终极解决方案
  • 终极指南:如何零配置实现WebRTC远程屏幕共享
  • Strix:开源AI漏洞检测工具,无缝集成CI/CD,快速准确查漏洞!
  • OpenAI王炸发布GPT-5.6!Sol、Terra、Luna三神降临,“Ultra模式”开启多智能体内卷时代!
  • Deeplabcut实战:从数据标注到行为分析的全流程解析
  • 如何5分钟完成漫画翻译:智能OCR工具的终极完整指南
  • 电商必备AI工具:如何把产品详情页,变成高转化种草笔记?
  • 别再只用Jieba了!用Pyhanlp给你的Python项目加个NLP瑞士军刀(附关键词提取实战)
  • 从零到一:手把手实现OLED的IIC四线驱动与内容显示
  • 别再只会画UMAP了!Scanpy核心绘图函数实战:从散点图到热图,手把手教你玩转单细胞数据可视化
  • ComfyUI-KJNodes:模块化节点系统的架构设计与技术实现
  • 多相抽取滤波在FPGA数字下变频中的工程实践(Matlab与Verilog协同验证)
  • R语言实战:运用IPTW与并行计算优化生存分析流程
  • 2001-2024年企业绿色媒体覆盖率绿色新闻数据
  • 从零到一:手把手教你为SPSS配置R环境并安装高级PSM插件
  • Python AES加密实战:用pycryptodome给你的配置文件‘上锁’(避坑IV和Padding)
  • AI 时代跨职能网络安全技能缺口与分层全员安全能力建设研究
  • 抖音内容批量下载工具:从手动保存到自动化管理的解决方案
  • 3个简单步骤掌握Cellpose:让细胞分割从复杂变轻松
  • 零成本云服务实测!阿贝云助力个人开发与学习运维
  • uni-app Vue3 集成uQRCode实现微信支付二维码动态生成与弹窗交互
  • 中导光电科创板IPO申请获受理,三年营收超8亿,半导体业务待突破
  • 2026年辽宁省高杆灯厂TOP5排名,工期短质量好选哪家?
  • 跨越数据鸿沟:领域自适应(Domain Adaptation)核心思想与实践路径
  • 保姆级教程:在Ubuntu 20.04 ROS Noetic下搞定轮趣N100 IMU驱动(含串口固定与Rviz可视化)
  • 一、Linux C编程笔记——标准IO
  • 技术揭秘:DeepMosaics如何用深度学习实现智能马赛克处理
  • Citizens2:Minecraft服务器NPC插件终极指南
  • 半导体全工艺流程详解|从硅砂到成品芯片,入门必看干货(附国产驱动芯片替代方案)