网站做用户登录详情页设计公司
网站做用户登录,详情页设计公司,自助建站免费申请个人网页,网站页脚代码目录一、SocketListener 读取 App 发送的日志信息二、写入 LogBuffer三、参考资料logd 写日志过程分析二
一、SocketListener 读取 App 发送的日志信息
上一篇#xff0c;我们讲到 SocketListener 收到 App 发送过出来的日志信息#xff0c;接着会调用 onDataAvailable() …目录一、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_settrue;}// 1 to ensure null terminator if MAX_PAYLOAD buffer is receivedcharbuffer[sizeof_log_id_tsizeof(uint16_t)sizeof(log_time)LOGGER_ENTRY_MAX_PAYLOAD1];structioveciov{buffer,sizeof(buffer)-1};alignas(4)charcontrol[CMSG_SPACE(sizeof(structucred))];structmsghdrhdr{nullptr,0,iov,1,control,sizeof(control),0,};intsocketcli-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_tnrecvmsg(socket,hdr,0);if(n(ssize_t)(sizeof(android_log_header_t))){returnfalse;}buffer[n]0;// 由于我们创建 socket 时打开了 SO_PASSCRED 选项这里我们可以读取一个用于表示客户端身份的 struct ucredstructucred*crednullptr;structcmsghdr*cmsgCMSG_FIRSTHDR(hdr);while(cmsg!nullptr){if(cmsg-cmsg_levelSOL_SOCKETcmsg-cmsg_typeSCM_CREDENTIALS){cred(structucred*)CMSG_DATA(cmsg);break;}cmsgCMSG_NXTHDR(hdr,cmsg);}structucredfake_cred;if(crednullptr){credfake_cred;cred-pid0;cred-uidDEFAULT_OVERFLOWUID;}if(cred-uidAID_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*headerreinterpret_castandroid_log_header_t*(buffer);log_id_tlogIdstatic_castlog_id_t(header-id);if(/* logId LOG_ID_MIN || */logIdLOG_ID_MAX||logIdLOG_ID_KERNEL){returnfalse;}if((logIdLOG_ID_SECURITY)(!__android_log_security()||!clientHasLogCredentials(cred-uid,cred-gid,cred-pid))){returnfalse;}// Check credential validity, acquire corrected details if not supplied.if(cred-pid0){cred-pidlogbuf?logbuf-tidToPid(header-tid):android::tidToPid(header-tid);if(cred-pidgetpid()){// 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-uidDEFAULT_OVERFLOWUID){uid_tuidlogbuf?logbuf-pidToUid(cred-pid):android::pidToUid(cred-pid);if(uidAID_LOGD){uidlogbuf?logbuf-pidToUid(header-tid):android::pidToUid(cred-pid);}if(uid!AID_LOGD)cred-uiduid;}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){intreslogbuf-log(logId,header-realtime,cred-uid,cred-pid,header-tid,msg,((size_t)nUINT16_MAX)?(uint16_t)n:UINT16_MAX);// 此时可能有客户端在等待读取数据于是也调用 reader-notifyNewLog()if(res0reader!nullptr){reader-notifyNewLog(static_castlog_mask_t(1logId));}}returntrue;}函数主要流程如下prctl 系统调用用于设置进程相关的一些东西。这里使用 PR_SET_NAME 设置了线程的名字为 logd.writer由于我们创建 socket 时打开了 SO_PASSCRED 选项这里我们可以读取一个用于表示客户端身份的 struct ucred接下来如果 struct ucred 内部有一些特殊值做单独处理调用 logbuf-log 将数据写入 LogBuffer此时可能有客户端在等待读取数据于是也调用 reader-notifyNewLog()二、写入 LogBufferlog 的写入分为 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_idLOG_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*elemnewLogBufferElement(log_id,realtime,uid,pid,tid,msg,len);// 根据数据内容调整数据if(log_id!LOG_ID_SECURITY){intprioANDROID_LOG_INFO;constchar*tagnullptr;size_ttag_len0;if(log_idLOG_ID_EVENTS||log_idLOG_ID_STATS){tagtagToName(elem-getTag());if(tag){tag_lenstrlen(tag);}}else{prio*msg;tagmsg1;tag_lenstrnlen(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;}}// ......}使用一个状态机去除重复的 logintLogBuffer::log(log_id_tlog_id,log_time realtime,uid_tuid,pid_tpid,pid_ttid,constchar*msg,uint16_tlen){//......wrlock();LogBufferElement*currentLastlastLoggedElements[log_id];if(currentLast){LogBufferElement*droppeddroppedElements[log_id];uint16_tcountdropped?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_typematchidentical(elem,currentLast);if(match!DIFFERENT){if(dropped){// Sum up liblog tag messages?if((count0)/* at Pass 1 */(matchSAME_LIBLOG)){android_log_event_int_t*eventreinterpret_castandroid_log_event_int_t*(const_castchar*(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_tswabevent-payload.data;unsignedlonglongtotalhtole32(swab);eventreinterpret_castandroid_log_event_int_t*(const_castchar*(elem-getMsg()));swabevent-payload.data;lastLoggedElements[LOG_ID_EVENTS]elem;totalhtole32(swab);// check for overflowif(totalUINT32_MAX){log(currentLast);unlock();returnlen;}stats.addTotal(currentLast);delete currentLast;swabtotal;event-payload.datahtole32(swab);unlock();returnlen;}if(countUSHRT_MAX){log(dropped);count1;}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知道流程即可有兴趣的同学可以参考源码中的注释自行分析。写入 logintLogBuffer::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_back5;// five seconds// Insert elements in time sorted order if possible// NB: if end is region locked, place element at end of listLogBufferElementCollection::iterator itmLogElements.end();LogBufferElementCollection::iterator lastit;if(__predict_true(it!mLogElements.begin()))--it;if(__predict_false(itmLogElements.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_setfalse;bool end_alwaysfalse;LogTimeEntry::rdlock();LastLogTimes::iterator timesmTimes.begin();while(times!mTimes.end()){LogTimeEntry*entrytimes-get();if(!entry-mNonBlock){end_alwaystrue;break;}// it passing mEnd is blocked by the following checks.if(!end_set||(endentry-mEnd)){endentry-mEnd;end_settrue;}times;}if(end_always||(end_set(end(*it)-getRealTime()))){mLogElements.push_back(elem);}else{// should be short as timestamps are localized near end()do{lastit;if(__predict_false(itmLogElements.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_tsizesstats.sizes(id);unsignedlongmaxSizelog_buffer_size(id);if(sizesmaxSize){size_tsizeOversizes-((maxSize*9)/10);size_telementsstats.realElements(id);size_tminElementselements/100;if(minElementsminPrune){minElementsminPrune;}unsignedlongpruneRowselements*sizeOver/sizes;if(pruneRowsminElements){pruneRowsminElements;}if(pruneRowsmaxPrune){pruneRowsmaxPrune;}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 数据下