系列目录 :第一篇:全景图与架构概览 | 第二篇: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 logdw是SOCK_DGRAM,权限0222(任何人可写);SO_PASSCRED选项由 logd 在代码中通过setsockopt()设置logdr是SOCK_SEQPACKET,保证有序和消息边界logd是SOCK_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):命令长度不定,需双向通信,用换行分隔命令- 处理
clear、getLogSize、setLogSize、getStatistics、getPruneList、setPruneList、reinit等命令
四、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(¶m, 0, sizeof(param));
pthread_attr_setschedparam(&attr, ¶m);
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_SYSLOG和CAP_AUDIT_CONTROL两个能力--reinit参数 :向已有 logd 进程发送reinit命令后退出,不启动新进程,用于属性变更后重新初始化- LogAudit/LogKlog 可选 :通过
logd.auditd和logd.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() 写入 logBuf(LOG_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));
}
LogAudit 的 getLogSocket() 通过 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.rc的on 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 的完整调用链。