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

茶叶 企业 网站建设手机银行网站建设

茶叶 企业 网站建设,手机银行网站建设,品牌营销推广,南昌建网站的公司本文概要 这是Android系统进程系列的第四篇文章#xff0c;本文以自述的方式来介绍logd进程#xff0c;通过本文您将了解到logd进程存在的意义#xff0c;以及日志系统的实现原理。#xff08;文中的代码是基于android13#xff09; Android系统进程系列的前三篇文章如下…本文概要 这是Android系统进程系列的第四篇文章本文以自述的方式来介绍logd进程通过本文您将了解到logd进程存在的意义以及日志系统的实现原理。文中的代码是基于android13 Android系统进程系列的前三篇文章如下 我是init进程 Android-属性服务的设计魅力 Android帝国之进程杀手–lmkd 我是谁 init“大家好我是你们的老朋友init进程今天我把我的出生最早的孩子‘logd进程‘介绍给各位认识logd那我就把舞台交给你了不要紧张你的二弟‘logd进程‘在介绍自己的时候表现的非常棒父亲相信你你也可以做的更棒“ logd“大家好啊我是logd进程可以直接叫我logd‘logd’这不是一个单词而是‘log daemon’的缩写翻译为中文是‘日志守护进程’。” 一个进程迫不及待地说“不好意思logd我先插句话我想起来了我们每个进程都有打印日志的需求在java层使用Log.i、Log.d、Log.e等Log类的方法来打印日志在native层用ALOGI、ALOGD等方法来打印日志这些打印日志的方法是不是都和你有关” “是的刚刚这位进程提到的这些打印日志的方法都和我有关别看你们在使用的时候特别简单其实这些日志信息在到达我这的时候要经过‘千山万水的旅程‘。回归正题我的名字叫logd日志守护进程主要的功能是给除了init、logd进程的所有用户空间进程提供日志打印的功能这些日志信息我会收集起来以便日志消费者进行消费如logcat展示日志信息” 大家对我应该有了一个初步的印象了我虽然不像你们人类的伟人那么伟大但是我觉得有必要介绍下我的出生。 我的出生 我出生在一个单亲家庭我只有父亲没有母亲我的父亲是init进程我的父亲是一个“不称职的父亲“为啥这样说呢它对于我何时创建、创建后叫啥名字等这些信息它统统不知道你们说它负责吗我只需要把这些信息用init脚本语言配置好后交给它即可剩下的事就全权交给它了脚本语言配置的信息如下 //文件路径/system/logging/logd/logd.rc//logd是进程的名字/system/bin/logd 代表当init进程fork logd成功后需要执行的可执行文件 service logd /system/bin/logd//下面三个socket分别代表需要创建的三个server socketsocket logd stream 0666 logd logdsocket logdr seqpacket 0666 logd logdsocket logdw dgrampasscred 0222 logd logd//kmsg代表内核会把内核的日志信息存储在这个文件中file /proc/kmsg rfile /dev/kmsg wuser logdgroup logd system package_info readproccapabilities SYSLOG AUDIT_CONTROLpriority 10task_profiles ServiceCapacityLowonrestart setprop logd.ready false省略其他信息......//文件路径/system/core/rootdir/init.rc下面的内容是该文件的其中一部分//on init代表init触发器触发的时候会执行下面的各种命令 on init省略其他命令......//start logdstart命令会创建进程logd与上面logd.rc中service后面的logd一致start logd 我的出生是不是非常的简单啊这其实是脚本语言和init进程的功劳可以点击 我是init进程 这篇文章来了解init进程的知识、以及如何通过脚本语言来创建子进程。 当init把我创建成功后执行下面的方法后我才可以为各进程提供日志功能。 //文件路径/system/logging/logd/main.cpp int main(int argc, char* argv[]) {// We want EPIPE when a reader disconnects, not to terminate logd.signal(SIGPIPE, SIG_IGN);省略其他代码.....return EXIT_SUCCESS; }从雏形说起 在介绍我是谁的时候提到了我的主要功能是给除了init、logd进程的所有用户空间进程提供日志打印的功能这些日志信息我会收集起来以便日志消费者进行消费如logcat展示日志信息我给这个功能起了一个高大上的名字日志系统那我接下来就来介绍下日志系统的实现原理这应该也是大家最感兴趣的内容了。 我计划先从日志系统的“雏形”讲起从雏形讲起的主要原因一方面是我在设计日志系统的时候也是从雏形开始设计雏形先有一个大概的模块划分以及模块之间的相互关系进而再对每个模块内部进行详细设计最终才有了日志系统第二方面是遵循你们人类学习知识的一个过程你们人类在学习知识的时候比如学习一门编程语言的时候需要先看下大纲都有哪些内容进而再有针对性的去学习相应章节的内容而不要一上来就开始抠细节。 雏形起源 “我先问大家一个问题如何实现一个进程内的日志打印、显示功能呢给大家几分钟的考虑时间。” 一个进程高高的举起了手“这个我会啊要实现进程内的日志打印、显示功能这还是很简单的首先需要用到生产者/消费者模式日志打印功能就是日志生产者日志显示功能就是日志消费者。这就是大概的思路了。” “这位仁兄回答的很好我用一幅图来总结下实现该功能的逻辑” 如上图进程内实现日志打印、显示功能主要涉及到以下三部分 日志生产者进程中的各线程调用相应的方法把需要打印的日志放入日志队列日志队列主要用来存放日志是按日志存放的先后顺序存放在队列中需要做好同步处理日志消费者显示日志的模块从日志队列中把日志按时间先后顺序取出来进行显示 雏形形成 日志系统解决的是进程之间的日志打印、显示需求它和进程内日志打印、显示的雏形基本是差不多的只不过前者日志生产者是其他进程而日志消费者也是其他进程日志队列是logd进程。如下图 如上图日志系统雏形涉及到以下三部分 日志生产者日志的生产者是其他进程通过进程通信的方式把日志传递到日志收集分发中心日志收集分发中心位于logd进程内其中日志队列是用来存放收集到的日志同时还会把收集到的日志分发给日志消费者日志消费者日志的消费者也是其他进程同样也是通过进程通信的方式把日志传递给消费者 日志生产者会有多个同时日志消费者也有多个日志收集分发中心、日志生产者、日志消费者形成了日志系统的雏形那我就从这三部分来介绍雏形是如何一步步衍化为日志系统的。 日志收集分发中心 日志收集分发中心从名字上大家就能明白它的作用日志收集分发中心存在于我logd进程内主要作用是从日志生产者生产的日志收集起来同时把收集到的日志分发给日志消费者。日志收集分发中心是日志系统的最重要的模块它的设计的好坏会严重影响到日志系统因此在设计的时候我logd花了非常大的精力日志收集分发中心又可以分为三部分日志队列、日志收集中心、日志分发中心因此会从这三部分入手来进行介绍。 日志队列 日志队列从名字上就可以显而易见知道它是用来存储日志的这个日志队列可不是像你们想象的那种简单的日志队列它面对的环境可是非常复杂的首先存储的日志是非常非常多的可不是仅仅只存储一个进程内的日志它存储的是除了init和logd之外所有的用户空间进程的所有日志其次每时每刻都会有各种日志需要存储。因此日志队列的性能、存储量的大小会影响到日志系统。 日志队列有以下几个要求 日志队列中的日志需要存储在内存中不会因为日志消费者消费了对应日志该日志就从日志队列中删除可以通过命令来控制日志队列的状态比如清除某些日志等日志队列中存储的日志总量是有最大限制的不可能无上限的存下去如果这样肯定会出现内存溢出的 日志分类 先从一个问题讲起 如果日志队列存储的日志总量达到了最大上限那采取的措施肯定是需要先把旧的日志清除掉以便腾出更多的存储空间但是如果按这个策略执行的话就有可能出现一个问题假如有一些重要的日志比如crash日志并且它们在日志队列中是最老的日志那如果达到日志上限的时候就会把这些重要的日志清理掉。这个问题肯定会遭到开发者骂娘的明明出现了crash但是却没有抓到。其实这个问题如果是完全解决掉那是完全不可能的因为存储上限的存在老的日志肯定会被清除掉对于这个问题能做的就是让这种概率降到最低最低并且对于crash这种类型的日志尽量保存更多的日志那如何能做到呢 logd自信的说“先不要慌遇到问题咱们就想办法办法总比问题多。” 一个进程说“我想到一个办法:能不能消费者消费掉了对应日志后把对应日志从队列中清除掉这样的话就可以大大的降低这个问题的概率。” logd“这个办法行不通因为日志队列中的日志它面对的消费者是很多的日志队列中的日志对于所有消费者来说是公共资源如果因为某个消费者消费了对应日志而把这些日志删掉那其他的消费者不就取不到这些被删掉的日志了。这肯定是有问题的。” 这个进程接着问“那如果把存储日志的上限值设置大些呢” “这个确实可以降低问题概率但是我觉得这个概率还没到更低更低。咱们可以从查找问题的根源入手找到问题根源进而想对策。” 问题的根源其实是不管什么样的日志crash、kernel、app日志都混杂在一起就是因为它们都混杂在一起所有的日志都共用一个最大上限值。那如果我们采用分而治之的策略呢分而治之就是对日志进行分类每种类型的日志分别有自己的内存最大上限值因为每种类型的日志使用了自己的内存上限值那就可以保存更多的日志了进而就可以非常大非常大的降低此问题的概率。同时对日志分类也可以带来别的好处比如消费者可以针对性的关注自己关心的类别日志在生产者生产日志的时候可以增加权限控制比如某种类型的日志只有生产者经过权限校验后才可以把它生产的日志放入队列。 可以把日志分类为LOG_ID_MAIN、LOG_ID_RADIO、LOG_ID_EVENTS、LOG_ID_SYSTEM、LOG_ID_CRASH、LOG_ID_SECURITY、LOG_ID_KERNEL其中LOG_ID_MAIN是app进程的日志开发者在开发app的时候打印的日志就是这种类型LOG_ID_EVENTS是event类型日志比如Activity生命周期LOG_ID_SYSTEM是systemserver进程的日志LOG_ID_CRASH是崩溃类型日志LOG_ID_KERNEL是kernel类型日志。 下面是代码有兴趣可以看下 //文件路径/system/logging/liblog/include/android/log.h typedef enum log_id {LOG_ID_MIN 0,//app进程的日志/** The main log buffer. This is the only log buffer available to apps. */LOG_ID_MAIN 0,/** The radio log buffer. */LOG_ID_RADIO 1,//event类型日志比如activity生命周期之类的/** The event log buffer. */LOG_ID_EVENTS 2,//systemserver进程的日志比如AMS/** The system log buffer. */LOG_ID_SYSTEM 3,//崩溃日之惠/** The crash log buffer. */LOG_ID_CRASH 4,/** The statistics log buffer. */LOG_ID_STATS 5,/** The security log buffer. */LOG_ID_SECURITY 6,//kernel日志/** The kernel log buffer. */LOG_ID_KERNEL 7,LOG_ID_MAX,/** Let the logging function choose the best log target. */LOG_ID_DEFAULT 0x7FFFFFFF } log_id_t;每种类型的日志在初始化的时候都会分配最大内存上限值如下代码 //文件路径/system/logging/logd/SimpleLogBuffer.cpp SimpleLogBuffer是其中一种日志队列 void SimpleLogBuffer::Init() {//log_id_for_each方法会遍历上面 所有的日志分类log_id_for_each(i) {//调用SetSize方法设置每种类型的最大内存值if (!SetSize(i, GetBufferSizeFromProperties(i))) {SetSize(i, kLogBufferMinSize);}}省略代码...... }bool SimpleLogBuffer::SetSize(log_id_t id, size_t size) {省略代码......auto lock std::lock_guard{logd_lock};max_size_[id] size;return true; } 简单日志队列 logd“介绍完日志的分类问大家个问题你们认为简单的日志队列应该是啥样的” 一个进程说“这题我会啊简单日志队列首先可以选用列表list来存储日志新的日志会加入到list的尾部位置越是老的日志越是位于list的头部位置。” logd“这位兄弟答的非常正确我刚好也设计了一个类来实现简单日志队列的功能它的名字是SimpleLogBuffer。我用一张图总结了下它的结构和工作流程。” SimpleLogBuffer的结构和工作流程图 如上图SimpleLogBuffer的属性logs_是一个list队列list中存放的元素是LogBufferElementLogBufferElement包含了日志信息及log_id它是上面介绍的日志分类的id等其他信息。属性max_size_是一个数组数组的索引是log_id日志分类id它存储了每种类型日志的内存最大上限值。 当有新的LogBufferElement数据加入logs_队列的时候会从max_size_取到该类别日志的最大上限值进而去检测该类型的日志总量是否达到了最大上限值达到了就开始清理老的日志。 下面是对应代码有兴趣可以看下 //文件路径/system/logging/logd/SimpleLogBuffer.cpp//添加日志到队列log_id日志类别id int SimpleLogBuffer::Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,const char* msg, uint16_t len) {省略代码......auto lock std::lock_guard{logd_lock};//每条日志都对应一个sequence从1开始每次加1auto sequence sequence_.fetch_add(1, std::memory_order_relaxed);//log_id, realtime, uid, pid, tid, sequence, msg, len生成LogBufferElement对象LogInternal(LogBufferElement(log_id, realtime, uid, pid, tid, sequence, msg, len));return len; }//添加LogBufferElement到队列 void SimpleLogBuffer::LogInternal(LogBufferElement elem) {log_id_t log_id elem.log_id();//添加到logs_队列中logs_.emplace_back(std::move(elem));stats_-Add(logs_.back().ToLogStatisticsElement());//如若达到上限尝试去清除老的日志MaybePrune(log_id);//通知监听者有新日志reader_list_-NotifyNewLog(1 log_id); }void SimpleLogBuffer::MaybePrune(log_id_t id) {unsigned long prune_rows;//ShouldPrune返回true则代表需要清理该日志类别的旧日志if (stats_-ShouldPrune(id, max_size_[id], prune_rows)) {//清理旧日志id为日志类别prune_rows需要清理多少行Prune(id, prune_rows, 0);} }bool SimpleLogBuffer::Prune(log_id_t id, unsigned long prune_rows, uid_t caller_uid) {省略代码......return true; }压缩功能的日志队列 简单日志队列正常工作是完全没有任何问题的但是为了在有限的内存下存储更多的日志我设计了具有压缩功能的日志队列它的名字是SerializedLogBuffer下面是它结构和工作流程图 如上图SerializedLogBuffer的属性logs_它是一个数组数组的索引是log_id日志类别id它的每个元素是一个list队列队列中包含的元素是SerializedLogChunk SerializedLogChunk是一个具有日志压缩功能的类它的每个元素是SerializedLogEntrySerializedLogChunk在创建的时候会分配一个size当要写入的SerializedLogEntry超过了这个size值后就会把当前的SerializedLogChunk的contents_进行压缩压缩后的数据存放在compressed_log_。 具体代码如下有兴趣可以看下 //文件路径/system/logging/logd/SerializedLogBuffer.cpp//存放日志log_id日志类别id int SerializedLogBuffer::Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,const char* msg, uint16_t len) {省略代码......//生成sequence每个日志都对应一个sequenceauto sequence sequence_.fetch_add(1, std::memory_order_relaxed);auto lock std::lock_guard{logd_lock};//调用LogToLogBuffer开始加入日志auto entry LogToLogBuffer(logs_[log_id], max_size_[log_id], sequence, realtime, uid, pid, tid,msg, len);stats_-Add(entry-ToLogStatisticsElement(log_id));//若超过上限值则开始清除该日志类别的老的日志MaybePrune(log_id);//通知监听者有新的日志可以读取了reader_list_-NotifyNewLog(1 log_id);return len; }//开始加入日志 static SerializedLogEntry* LogToLogBuffer(std::listSerializedLogChunk log_buffer,size_t max_size, uint64_t sequence, log_time realtime,uid_t uid, pid_t pid, pid_t tid, const char* msg,uint16_t len) {//若为empty则push一个SerializedLogChunk它的大小是max_size / SerializedLogBuffer::kChunkSizeDivisorif (log_buffer.empty()) {log_buffer.push_back(SerializedLogChunk(max_size / SerializedLogBuffer::kChunkSizeDivisor));}auto total_len sizeof(SerializedLogEntry) len;//若最后的SerializedLogChunk没有空间存储当前日志if (!log_buffer.back().CanLog(total_len)) {//调用FinishWriting方法会对最后的SerializedLogChunk进行压缩log_buffer.back().FinishWriting();//往log_buffer重新push一个SerializedLogChunklog_buffer.push_back(SerializedLogChunk(max_size / SerializedLogBuffer::kChunkSizeDivisor));}//调用SerializedLogChunk的Log方法把新日志写入return log_buffer.back().Log(sequence, realtime, uid, pid, tid, msg, len); }//文件路径/system/logging/logd/SerializedLogChunk.cpp SerializedLogEntry* SerializedLogChunk::Log(uint64_t sequence, log_time realtime, uid_t uid,pid_t pid, pid_t tid, const char* msg, uint16_t len) {auto new_log_address contents_.data() write_offset_;auto* entry new (new_log_address) SerializedLogEntry(uid, pid, tid, sequence, realtime, len);memcpy(entry-msg(), msg, len);write_offset_ entry-total_len();highest_sequence_number_ sequence;return entry; }不管是SerializedLogBuffer还是SimpleLogBuffer都会在我logd启动后选择其中一种作为日志系统的日志队列当然还有一种类型的日志队列ChattyLogBuffer不是很常用就不赘述了相关代码如下 //文件路径/system/logging/logd/main.cpp int main(int argc, char* argv[]) {省略其他代码......//获取logd.buffer_type属性对应的值默认值是serializedstd::string buffer_type GetProperty(logd.buffer_type, serialized);// LogBuffer is the object which is responsible for holding all log entries.LogBuffer* log_buffer nullptr;//根据buffer_type的值来对日志队列log_buffer进行初始化一般情况下都会初始化SerializedLogBuffer的日志队列if (buffer_type chatty) {log_buffer new ChattyLogBuffer(reader_list, log_tags, prune_list, log_statistics);} else if (buffer_type serialized) {log_buffer new SerializedLogBuffer(reader_list, log_tags, log_statistics);} else if (buffer_type simple) {log_buffer new SimpleLogBuffer(reader_list, log_tags, log_statistics);} else {LOG(FATAL) buffer_type must be one of chatty, serialized, or simple;}省略其他代码...... }总结 日志队列为了在有限的内存能够存放更多的日志首先采用分而治之的办法对日志进行了分类分为LOG_ID_MAIN、LOG_ID_RADIO、LOG_ID_EVENTS、LOG_ID_SYSTEM、LOG_ID_CRASH、LOG_ID_SECURITY、LOG_ID_KERNEL每种类型的日志分别有自己的内存最大上限值因为每种类型的日志使用了自己的内存上限值那就可以保存更多的日志了其次设计了具有压缩功能的日志队列SerializedLogBuffer它可以对日志进行压缩这样就能存储更多的日志了。 日志收集中心 日志收集中心的作用就是把生产者生产的日志收集起来放入日志队列中因为日志的生产者是位于其他进程而日志收集中心是位于logd进程因此要想把它们生产的日志收集起来就需要一个收集渠道你们人类有句话是那样说的“要想富先修路”因此要想收集日志就需要把收集渠道先“修好”。 收集渠道 收集渠道其实就是解决生产日志进程如何与logd进程通信进程通信的方法有socket、signal、binder、共享内存等咱们还是按照老惯例先结合咱们的使用场景进而在决定使用哪种方法。咱们的使用场景是生产者把生产的日志传递给logd进程对于传递过程有以下要求首先是传递是串行的也就是只要收到日志我就会直接放入日志队列中我不需要关心是按照时间或者别的因素对放入日志队列的日志进行排序其次是对于传递快慢没有要求必须非常快最后传递渠道是一对多的关系也就是logd进程是server端日志生产进程是client端也就是C/S模式。 因此基于以上使用场景收集渠道应该使用socket通信方式socket首先是C/S模式logd是socket的server端其他生产者进程是socket的client端其次socket通信是串行的需要传递了一个数据再传递下个数据。而binder通信方式虽然也可以做到C/S模式但是它的通信是并行的也就是会出现多个数据同时传递如若采用binder通信就需要对于收到的日志按时间或者别的因素重新排序而我可不想做这种出力不讨好的工作。 收集渠道会启动一个名字为logdw的server socketserver socket启动后还会启动一个单独的线程不断循环监听日志生产者发送过来的日志。 收集渠道的相关代码如下有兴趣可以看下 //文件路径/system/logging/logd/main.cpp int main(int argc, char* argv[]) {省略其他代码......// LogListener listens on /dev/socket/logdw for client// initiated log messages. New log entries are added to LogBuffer// and LogReader is notified to send updates to connected clients.// 实例化LogListenerLogListener* swl new LogListener(log_buffer);//调用StartListener方法会启动一个线程并且不断循环监听收到的日志数据if (!swl-StartListener()) {return EXIT_FAILURE;}省略其他代码...... }//文件路径/system/logging/logd/LogListener.cpp //GetLogSocket()方法会把server socket名字为logdw的server获取到对socket_进行初始化 LogListener::LogListener(LogBuffer* buf) : socket_(GetLogSocket()), logbuf_(buf) {}//启动一个线程调用ThreadFunction方法 bool LogListener::StartListener() {if (socket_ 0) {return false;}auto thread std::thread(LogListener::ThreadFunction, this);thread.detach();return true; }//该方法会进入循环不断地从socket client端读取日志信息 void LogListener::ThreadFunction() {prctl(PR_SET_NAME, logd.writer);while (true) {HandleData();} }void LogListener::HandleData() {省略代码......//把从socket client端获取的日志信息放入logbuf中logbuf_-Log(logId, header-realtime, cred-uid, cred-pid, header-tid, msg,((size_t)n UINT16_MAX) ? (uint16_t)n : UINT16_MAX); }int LogListener::GetLogSocket() {//server socket的名字是logdwstatic const char socketName[] logdw;int sock android_get_control_socket(socketName);省略代码......return sock; }收集协议 既然收集渠道已经“修好了”那来定下在渠道上传递的协议吧协议格式如下 //文件路径system/logging/liblog/include/android/log.h struct __android_log_message {/** Must be set to sizeof(__android_log_message) and is used for versioning. */size_t struct_size;//日志类别id/** {link log_id_t} values. */int32_t buffer_id;//优先级/** {link android_LogPriority} values. */int32_t priority;//tag 日志对应的tag/** The tag for the log message. */const char* tag;//暂时用不到可以忽略/** Optional file name, may be set to nullptr. */const char* file;/** Optional line number, ignore if file is nullptr. */uint32_t line;//日志具体信息/** The log message itself. */const char* message; };日志分发中心 日志分发中心的作用是把日志队列中的日志分发给日志消费者而日志消费者是位于其他进程与logd不是同一进程因此也需要一个“分发渠道”把日志队列中的日志分发出去。 分发渠道 与收集渠道类似分发渠道也使用socket通信主要原因是首先日志队列会对应多个日志消费者这明显是C/S模式其次日志消费者它是非常省心的只是一味的接收发送过来的日志即可也不需要对收到的日志进行排序等处理因此需要串行传递日志而socket发送接收数据是串行的。基于以上原因分发渠道选用了socket通信。 收集渠道会启动一个名字为logdr的server socketserver socket启动后还会启动一个单独的线程不断循环监听与logdr建立连接的client端并且把它保存。日志队列中的日志就会通过socket通信发送到client端。 对应代码如下: //文件路径/system/logging/logd/main.cpp int main(int argc, char* argv[]) {省略其他代码......// LogReader listens on /dev/socket/logdr. When a client// connects, log entries in the LogBuffer are written to the client.//创建LogReader实例LogReader* reader new LogReader(log_buffer, reader_list);//调用startListener方法会创建一个线程不断循环去监听建立连接的client端if (reader-startListener()) {return EXIT_FAILURE;}省略其他代码...... }//文件路径/system/logging/logd/LogReader.cpp LogReader::LogReader(LogBuffer* logbuf, LogReaderList* reader_list): SocketListener(getLogSocket(), true), log_buffer_(logbuf), reader_list_(reader_list) {}//获取server socket int LogReader::getLogSocket() {//server socket name logdrstatic const char socketName[] logdr;int sock android_get_control_socket(socketName);if (sock 0) {sock socket_local_server(socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET);}return sock; }//若有socket client连接的话会调用这个方法进而把client保存 bool LogReader::onDataAvailable(SocketClient* cli) {static bool name_set;省略代码......return true; }总结 日志队列、日志收集中心、日志分发中心它们三部分组成了日志收集分发中心。日志队列对日志进行了分类同时还为了能够在有限的内存下存储更多的日志设计了具有压缩功能的日志队列–SerializedLogBuffer日志收集中心会启动一个名为logdw的server socket用来不断地监听日志生产者发送的日志并且会把收到的日志放入日志队列中日志分发中心也会启动一个名为logdr的server socket用来不断地监听建立socket连接的client端并且会把日志队列中的日志发送给这些client。 日志生产者 我为了让日志生产者非常非常容易的生产日志我把底层的实现细节全部都给隐藏掉使用者只需要调用简单的方法就可以生产一条日志并且把这条日志通过socket发送到logd进程的日志收集中心。比如Java层只需要简单的调用Log.i(tag,msg)的方法就可以生产一条main类型的日志systemserver进程的代码只需要调用Slog.i(tag,msg)方法就以生产一条system类型的日志native层代码只需要调用ALOGI(msg)方法就可以生产一条main类型的日志。 像上面的log.i、Slog.i这些方法最终都会调用到__android_log_buf_write/system/logging/liblog/logger_write.cpp方法而像ALOGI等native方法最终会调用到__android_log_print/system/logging/liblog/logger_write.cpp无论是__android_log_buf_write还是__android_log_print方法殊途同归最终调用到__android_log_write_log_message/system/logging/liblog/logger_write.cpp方法把封装好的日志信息通过socketsocket与名字为logdw的server建立连接发送到logd的日志收集中心。 如下代码 //文件路径/system/logging/liblog/logger_write.cppint __android_log_buf_write(int bufID, int prio, const char* tag, const char* msg) {省略代码......//构造__android_log_message对象__android_log_message log_message {sizeof(__android_log_message), bufID, prio, tag, nullptr, 0, msg};//由于代码量太大关于__android_log_write_log_message及后续的代码就不贴出来了__android_log_write_log_message(log_message);return 1; }int __android_log_print(int prio, const char* tag, const char* fmt, ...) {ErrnoRestorer errno_restorer;if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {return -EPERM;}va_list ap;__attribute__((uninitialized)) char buf[LOG_BUF_SIZE];va_start(ap, fmt);vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);va_end(ap);//构造__android_log_message对象日志类别为LOG_ID_MAIN__android_log_message log_message {sizeof(__android_log_message), LOG_ID_MAIN, prio, tag, nullptr, 0, buf};//由于代码量太大关于__android_log_write_log_message及后续的代码就不贴出来了__android_log_write_log_message(log_message);return 1; } 日志消费者 最常用的日志消费者就是logcat了在终端输入adb logcat命令后终端上就会显示出所有的日志是不是非常的简单好用啊其实在执行adb logcat命令后会创建一个logcat进程这个进程会与logd进程的日志分发中心的logdr server socket建立连接从而把传递过来日志显示在终端。 相应代码如下 //文件路径/system/logging/logcat/logcatd 在终点输入 adb logcat命令后会执行logcatd shell脚本文件 #! /system/bin/sh 省略代码...... //下面代码会执行logcat.cpp的main方法同时会把adb logcat携带的参数传递过去 exec logcat ${ARGS[]}//文件路径/system/logging/logcat/logcat.cpp int main(int argc, char** argv) {Logcat logcat;return logcat.Run(argc, argv); }int Logcat::Run(int argc, char** argv) {省略代码......//从logd接收日志并显示while (!max_count_ || print_count_ max_count_) {struct log_msg log_msg;int ret android_logger_list_read(logger_list.get(), log_msg);省略代码......//若是二进制则走这if (print_binary_) {WriteFully(log_msg, log_msg.len());} else {//显示拿到的日志ProcessBuffer(log_msg);if (blocking output_file_ stdout) fflush(stdout);}}return EXIT_SUCCESS; } 总结 日志收集分发中心、日志生产者、日志消费者三部分组成了日志系统。 日志收集分发中心位于logd进程logd进程会被init进程创建日志收集分发中心又可以分为三部分日志收集中心、日志分发中心、日志队列。日志收集中心会启动一个名字为logdw的server socket等待日志生产者建立连接收到日志后会把它们放入日志队列中日志队列为了在有限的内存下存储更多了日志对日志进行了分类同时也设计了具有压缩功能的日志队列SerializedLogBuffer从而可以存储更多的日志日志分发中心也会启动一个名字为logdr的server socket如果日志消费者比如logcat想要消费日志那就需要与之建立socket连接进而收到日志进行显示等处理。 日志生产者只需要调用Log、Slog、ALOGI等方法就可以生产一条日志并且把这条日志通过socket发送到日志收集中心。 日志消费者比如logcat使用起来也非常方便只需要在终端输入adb logcat命令即可把日志显示在终端adb logcat命令会创建一个logcat进程这个进程会与日志分发中心建立socket连接进而接收日志并且显示。 好了关于日志系统关于我logd进程的介绍到此为止感谢大家的观看。 欢迎大家关注我的公众号–牛晓伟
http://www.pierceye.com/news/39261/

相关文章:

  • 云虚拟主机做二个网站顺德装修网站建设
  • 做物流网站的公司哪家好推广怎么做
  • 南昌 网站购物网站需要做的功能
  • 环球易购招聘网站建设seo优化排名易下拉程序
  • 毕业设计代做网站机械wordpress 云服务器
  • 建网站价格 建设网站需要多少钱关于茶叶网站模板
  • 网站推广方案编写肇庆网站建设制作公司
  • 西班牙语网站设计公司哪家好微信公众号做微网站
  • 自己做图片上传网站wordpress添加微博
  • 手机网站建设合同python网站开发架构
  • 专业的营销型网站制作网站显示内容不显示
  • 旅游景点网站建设现状制作小程序需要什么技术
  • 宜春市城乡规划建设局网站苏州快速建站模板
  • 网站活动策划方案建设银行网上官方网站
  • 织梦pc怎么做手机网站wordpress后台多媒体不显示缩
  • 垂直汽车网站做电商的优势wordpress 单页面主题
  • 做网站建设公司crm在线的提升服务dw制作简单网页
  • 青岛新公司网站建设推广太原seo排名收费
  • 福建百度seo排名点击软件南城网站优化公司
  • 网站建设需要提供那些资料宽带公司
  • 优化网站seo方案网站一定要备案
  • 网站建设新手看什么书网站建设合同的要素
  • 涉县移动网站建设报价南和网站建设公司太原网站建设
  • 怎样低成本做网站推广网站设计与开发的基本步骤包括哪些?
  • 用织梦做手机移动版网站镇江制作网页图片
  • 网站建设网站排名广州网站开发公司哪家好
  • 汽车网站模块网站编辑有前途吗
  • 网站怎么更改关键词国内炫酷的网站首页
  • 镇江教育云网站建设合肥网站推广公司
  • 自建网站怎么做二级页跳转wordprees可以做棋类网站吗