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

Android 日志系统5——logd 写日志过程分析二

目录

  • 一、SocketListener 读取 App 发送的日志信息
  • 二、写入 LogBuffer
  • 三、参考资料

logd 写日志过程分析二

一、SocketListener 读取 App 发送的日志信息

上一篇,我们讲到 SocketListener 收到 App 发送过出来的日志信息,接着会调用 onDataAvailable() 来处理收到的数据:

// system/core/logd/LogListener.cppbool LogListener::onDataAvailable(SocketClient*cli){//prctl 系统调用用于设置进程相关的一些东西。这里使用 PR_SET_NAME 设置了线程的名字为 logd.writerstaticbool name_set;if(!name_set){prctl(PR_SET_NAME,"logd.writer");name_set=true;}// + 1 to ensure null terminator if MAX_PAYLOAD buffer is receivedcharbuffer[sizeof_log_id_t+sizeof(uint16_t)+sizeof(log_time)+LOGGER_ENTRY_MAX_PAYLOAD+1];structioveciov={buffer,sizeof(buffer)-1};alignas(4)charcontrol[CMSG_SPACE(sizeof(structucred))];structmsghdrhdr={nullptr,0,&iov,1,control,sizeof(control),0,};intsocket=cli->getSocket();// 接下来使用 recvmsg 从 socket 里读取数据// To clear the entire buffer is secure/safe, but this contributes to 1.68%// overhead under logging load. We are safe because we check counts, but// still need to clear null terminator// memset(buffer, 0, sizeof(buffer));ssize_tn=recvmsg(socket,&hdr,0);if(n<=(ssize_t)(sizeof(android_log_header_t))){returnfalse;}buffer[n]=0;// 由于我们创建 socket 时打开了 SO_PASSCRED 选项,这里我们可以读取一个用于表示客户端身份的 struct ucredstructucred*cred=nullptr;structcmsghdr*cmsg=CMSG_FIRSTHDR(&hdr);while(cmsg!=nullptr){if(cmsg->cmsg_level==SOL_SOCKET&&cmsg->cmsg_type==SCM_CREDENTIALS){cred=(structucred*)CMSG_DATA(cmsg);break;}cmsg=CMSG_NXTHDR(&hdr,cmsg);}structucredfake_cred;if(cred==nullptr){cred=&fake_cred;cred->pid=0;cred->uid=DEFAULT_OVERFLOWUID;}if(cred->uid==AID_LOGD){// ignore log messages we send to ourself.// Such log messages are often generated by libraries we depend on// which use standard Android logging.returnfalse;}// 处理一些特殊值android_log_header_t*header=reinterpret_cast<android_log_header_t*>(buffer);log_id_tlogId=static_cast<log_id_t>(header->id);if(/* logId < LOG_ID_MIN || */logId>=LOG_ID_MAX||logId==LOG_ID_KERNEL){returnfalse;}if((logId==LOG_ID_SECURITY)&&(!__android_log_security()||!clientHasLogCredentials(cred->uid,cred->gid,cred->pid))){returnfalse;}// Check credential validity, acquire corrected details if not supplied.if(cred->pid==0){cred->pid=logbuf?logbuf->tidToPid(header->tid):android::tidToPid(header->tid);if(cred->pid==getpid()){// We expect that /proc/<tid>/ is accessible to self even without// readproc group, so that we will always drop messages that come// from any of our logd threads and their library calls.returnfalse;// ignore self}}if(cred->uid==DEFAULT_OVERFLOWUID){uid_tuid=logbuf?logbuf->pidToUid(cred->pid):android::pidToUid(cred->pid);if(uid==AID_LOGD){uid=logbuf?logbuf->pidToUid(header->tid):android::pidToUid(cred->pid);}if(uid!=AID_LOGD)cred->uid=uid;}char*msg=((char*)buffer)+sizeof(android_log_header_t);n-=sizeof(android_log_header_t);// NB: hdr.msg_flags & MSG_TRUNC is not tested, silently passing a// truncated message to the logs.// 调用 logbuf->log 将数据写入 LogBufferif(logbuf!=nullptr){intres=logbuf->log(logId,header->realtime,cred->uid,cred->pid,header->tid,msg,((size_t)n<=UINT16_MAX)?(uint16_t)n:UINT16_MAX);// 此时可能有客户端在等待读取数据,于是也调用 reader->notifyNewLog()if(res>0&&reader!=nullptr){reader->notifyNewLog(static_cast<log_mask_t>(1<<logId));}}returntrue;}

函数主要流程如下:

  • prctl 系统调用用于设置进程相关的一些东西。这里使用 PR_SET_NAME 设置了线程的名字为 logd.writer
  • 由于我们创建 socket 时打开了 SO_PASSCRED 选项,这里我们可以读取一个用于表示客户端身份的 struct ucred
  • 接下来,如果 struct ucred 内部有一些特殊值,做单独处理
  • 调用 logbuf->log 将数据写入 LogBuffer
  • 此时可能有客户端在等待读取数据,于是也调用 reader->notifyNewLog()

二、写入 LogBuffer

log 的写入分为 4 个步骤:

  • 根据日志的内容对数据做调整,数据不对,就直接 return
  • 使用一个状态机去除重复的 log
  • 写入 log
  • 如果需要,删除一些 log 以避免 log 数据过多

接下来分步查看源码:

根据 tag 和优先级判断该 log 是否可以写入

// system/core/logd/LogBuffer.cppintLogBuffer::log(log_id_tlog_id,log_time realtime,uid_tuid,pid_tpid,pid_ttid,constchar*msg,uint16_tlen){if(log_id>=LOG_ID_MAX){return-EINVAL;}// Slip the time by 1 nsec if the incoming lands on xxxxxx000 ns.// This prevents any chance that an outside source can request an// exact entry with time specified in ms or us precision.if((realtime.tv_nsec%1000)==0)++realtime.tv_nsec;// LogBufferElement 用于保存一条日志信息LogBufferElement*elem=newLogBufferElement(log_id,realtime,uid,pid,tid,msg,len);// 根据数据内容,调整数据if(log_id!=LOG_ID_SECURITY){intprio=ANDROID_LOG_INFO;constchar*tag=nullptr;size_ttag_len=0;if(log_id==LOG_ID_EVENTS||log_id==LOG_ID_STATS){tag=tagToName(elem->getTag());if(tag){tag_len=strlen(tag);}}else{prio=*msg;tag=msg+1;tag_len=strnlen(tag,len-1);}if(!__android_log_is_loggable_len(prio,tag,tag_len,ANDROID_LOG_VERBOSE)){// Log traffic received to totalwrlock();stats.addTotal(elem);unlock();delete elem;return-EACCES;}}// ......}

使用一个状态机去除重复的 log

intLogBuffer::log(log_id_tlog_id,log_time realtime,uid_tuid,pid_tpid,pid_ttid,constchar*msg,uint16_tlen){//......wrlock();LogBufferElement*currentLast=lastLoggedElements[log_id];if(currentLast){LogBufferElement*dropped=droppedElements[log_id];uint16_tcount=dropped?dropped->getDropped():0;//// State Init// incoming:// dropped = nullptr// currentLast = nullptr;// elem = incoming message// outgoing:// dropped = nullptr -> State 0// currentLast = copy of elem// log elem// State 0// incoming:// count = 0// dropped = nullptr// currentLast = copy of last message// elem = incoming message// outgoing: if match != DIFFERENT// dropped = copy of first identical message -> State 1// currentLast = reference to elem// break: if match == DIFFERENT// dropped = nullptr -> State 0// delete copy of last message (incoming currentLast)// currentLast = copy of elem// log elem// State 1// incoming:// count = 0// dropped = copy of first identical message// currentLast = reference to last held-back incoming// message// elem = incoming message// outgoing: if match == SAME// delete copy of first identical message (dropped)// dropped = reference to last held-back incoming// message set to chatty count of 1 -> State 2// currentLast = reference to elem// outgoing: if match == SAME_LIBLOG// dropped = copy of first identical message -> State 1// take sum of currentLast and elem// if sum overflows:// log currentLast// currentLast = reference to elem// else// delete currentLast// currentLast = reference to elem, sum liblog.// break: if match == DIFFERENT// delete dropped// dropped = nullptr -> State 0// log reference to last held-back (currentLast)// currentLast = copy of elem// log elem// State 2// incoming:// count = chatty count// dropped = chatty message holding count// currentLast = reference to last held-back incoming// message.// dropped = chatty message holding count// elem = incoming message// outgoing: if match != DIFFERENT// delete chatty message holding count// dropped = reference to last held-back incoming// message, set to chatty count + 1// currentLast = reference to elem// break: if match == DIFFERENT// log dropped (chatty message)// dropped = nullptr -> State 0// log reference to last held-back (currentLast)// currentLast = copy of elem// log elem//enummatch_typematch=identical(elem,currentLast);if(match!=DIFFERENT){if(dropped){// Sum up liblog tag messages?if((count==0)/* at Pass 1 */&&(match==SAME_LIBLOG)){android_log_event_int_t*event=reinterpret_cast<android_log_event_int_t*>(const_cast<char*>(currentLast->getMsg()));//// To unit test, differentiate with something like:// event->header.tag = htole32(CHATTY_LOG_TAG);// here, then instead of delete currentLast below,// log(currentLast) to see the incremental sums form.//uint32_tswab=event->payload.data;unsignedlonglongtotal=htole32(swab);event=reinterpret_cast<android_log_event_int_t*>(const_cast<char*>(elem->getMsg()));swab=event->payload.data;lastLoggedElements[LOG_ID_EVENTS]=elem;total+=htole32(swab);// check for overflowif(total>=UINT32_MAX){log(currentLast);unlock();returnlen;}stats.addTotal(currentLast);delete currentLast;swab=total;event->payload.data=htole32(swab);unlock();returnlen;}if(count==USHRT_MAX){log(dropped);count=1;}else{delete dropped;++count;}}if(count){stats.addTotal(currentLast);currentLast->setDropped(count);}droppedElements[log_id]=currentLast;lastLoggedElements[log_id]=elem;unlock();returnlen;}if(dropped){// State 1 or 2if(count){// State 2log(dropped);// report chatty}else{// State 1delete dropped;}droppedElements[log_id]=nullptr;log(currentLast);// report last message in the series}else{// State 0delete currentLast;}}lastLoggedElements[log_id]=newLogBufferElement(*elem);// .....

这里使用一个状态机来去除重复的 log,知道流程即可,有兴趣的同学可以参考源码中的注释自行分析。

写入 log

intLogBuffer::log(log_id_tlog_id,log_time realtime,uid_tuid,pid_tpid,pid_ttid,constchar*msg,uint16_tlen){//......log(elem);unlock();returnlen;}

这里调用另一个重载 log:

// assumes LogBuffer::wrlock() held, owns elem, look after garbage collectionvoidLogBuffer::log(LogBufferElement*elem){// cap on how far back we will sort in-place, otherwise appendstaticuint32_ttoo_far_back=5;// five seconds// Insert elements in time sorted order if possible// NB: if end is region locked, place element at end of listLogBufferElementCollection::iterator it=mLogElements.end();LogBufferElementCollection::iterator last=it;if(__predict_true(it!=mLogElements.begin()))--it;if(__predict_false(it==mLogElements.begin())||__predict_true((*it)->getRealTime()<=elem->getRealTime())||__predict_false((((*it)->getRealTime().tv_sec-too_far_back)>elem->getRealTime().tv_sec)&&(elem->getLogId()!=LOG_ID_KERNEL)&&((*it)->getLogId()!=LOG_ID_KERNEL))){mLogElements.push_back(elem);}else{log_timeend(log_time::EPOCH);bool end_set=false;bool end_always=false;LogTimeEntry::rdlock();LastLogTimes::iterator times=mTimes.begin();while(times!=mTimes.end()){LogTimeEntry*entry=times->get();if(!entry->mNonBlock){end_always=true;break;}// it passing mEnd is blocked by the following checks.if(!end_set||(end<=entry->mEnd)){end=entry->mEnd;end_set=true;}times++;}if(end_always||(end_set&&(end>(*it)->getRealTime()))){mLogElements.push_back(elem);}else{// should be short as timestamps are localized near end()do{last=it;if(__predict_false(it==mLogElements.begin())){break;}--it;}while(((*it)->getRealTime()>elem->getRealTime())&&(!end_set||(end<=(*it)->getRealTime())));mLogElements.insert(last,elem);}LogTimeEntry::unlock();}stats.add(elem);maybePrune(elem->getLogId());}

__predict_true 和 __predict_false 用来提示编译器对应的判断很可能是 true/false,类似于 Linux 内核的 likely/unlikely。如果判断正确,可以得到很大的性能提升。

客户端使用 Unix-domain socket 来写入 log,读出来的时候,很可能就已经按时间顺序排好,这个时候,只需要把 LogBufferElement 插入列表末尾就可以了。

万一很不幸的,上面的判断失败了,就只能从后往前遍历列表,找到一个合适的位置来插入 LogBufferElement。这个过程类似于插入排序。

如果需要,删除一些 log 以避免 log 数据过多

void LogBuffer::log(LogBufferElement* elem)在最后会调用 maybePrune,根据 log 总量判断是否需要删除一些 log。

// system/core/logd/LogBuffer.cpp// Prune at most 10% of the log entries or maxPrune, whichever is less.//// LogBuffer::wrlock() must be held when this function is called.voidLogBuffer::maybePrune(log_id_tid){size_tsizes=stats.sizes(id);unsignedlongmaxSize=log_buffer_size(id);if(sizes>maxSize){size_tsizeOver=sizes-((maxSize*9)/10);size_telements=stats.realElements(id);size_tminElements=elements/100;if(minElements<minPrune){minElements=minPrune;}unsignedlongpruneRows=elements*sizeOver/sizes;if(pruneRows<minElements){pruneRows=minElements;}if(pruneRows>maxPrune){pruneRows=maxPrune;}prune(id,pruneRows);}}

如果 id 类型的 log 超过了总量限制,就删除 10% 的 log。在这个前提下,所删除的 log 调试大于 minElements 和 minPrune,小于 maxPrune。其中,minElements 是所有 id 类型的 log 的总条数的百分之一。

当每条 log 都很大、log 总量又很小,限制最小值可以避免总是需要剔除旧 log。如果 log 每条很小,限制最大数目可以避免删除过多的 log。

实际的删除工作由 prune 方法完成。


三、参考资料

Android log 机制 - logd 如何接收 log 数据(上)
Android log 机制 - logd 如何接收 log 数据(下)


http://www.jsqmd.com/news/374395/

相关文章:

  • 跨境汇款被冻,万里汇WorldFirst中非直通车能否担当外贸交易大任?
  • 2026年常州靠谱的专业环保评价机构分析,费用怎么算 - 工业品牌热点
  • 即构实时互动AI Agent 2026年重磅升级:支持用户情绪识别,多情感TTS
  • 深度评测:本地新型撬装设备供应商的服务与口碑,中低压管件/保温管道/合金管道/压力容器/异径管件,撬装产品设备厂家有哪些 - 品牌推荐师
  • 某LLM问答系统安全测试报告:提示词注入与越狱攻击分析
  • 2026年标识标牌系统分类及应用功能分析,哪家制造商性价比高 - myqiye
  • 2026 干湿联合冷却塔厂家哪家好?权威榜单一文读懂 - 深度智识库
  • 优质干燥设备哪里找?这些厂家市场口碑不俗,耙式干燥机/干燥设备/真空螺旋干燥机/闪蒸干燥机,干燥设备品牌口碑推荐 - 品牌推荐师
  • 如何轻松地将联系人从 iPhone 同步到 Mac
  • color: var(--el-color-success); CSS里面使用函数
  • 2026年,诚信GEO推广公司如何成为行业TOP 5? - 品牌企业推荐师(官方)
  • 教你轻松搞定分期乐的支付宝红包回收 - 团团收购物卡回收
  • 基于SSM的线上宿舍管理系统(人脸识别登录)
  • 从 BIOS 开发者的视角看家政服务:东氿一号“环境重构”实录
  • 食品糖测定色谱仪租赁靠谱平台真实测评 - 资讯焦点
  • 国产信创IM软件哪个好?(附信创适配清单) - 企业数字化观察家
  • P0922VV FBM216通信输入模块
  • 元数据平台选型避坑指南:从“血缘不准”到“DataOps 自动化治理”的跨越
  • 2026合击传奇手游推荐榜单:五大高口碑复古合击服测评,散人玩家首选出炉 - 博客湾
  • ArchLinux Vmware安装指北
  • 真心不骗你!继续教育论文神器 —— 千笔·专业论文写作工具
  • 如何选择一家靠谱的私有化即时通讯软件厂商? - 企业数字化观察家
  • 1.大模型使用 - 指南
  • 聊聊下传动数控折弯机,哪个品牌定制能力强、振动小 - 工业设备
  • LIN升级工具集成OTA功能代码资料
  • 2026年2月心理咨询机构推荐,专业疏导与贴心服务深度解析 - 品牌鉴赏师
  • 2025氧化镁厂家排行:哪些更受市场青睐?有实力的氧化镁精选综合实力TOP企业 - 品牌推荐师
  • 2026新中式女装品牌盘点,你的衣橱该更新了!,济南诚信的新中式女装推荐色麦新中式满足多元需求 - 品牌推荐师
  • 陕西旋转楼梯哪家好?2026厂家TOP3实测,鑫木阳钢构闭眼选 - 朴素的承诺
  • 清单来了:8个降AIGC软件测评,专科生降AI率必备工具推荐