从前面4.6.2小节的内容可以知道,Logcat工具是从源代码文件logcat.cpp中的函数readLogLines开始读取日志记录的,我们分段来阅读这个函数的实现。
**system/core/logcat/logcat.cpp**
~~~
static void readLogLines(log_device_t* devices)
{
log_device_t* dev;
int max = 0;
int ret;
int queued_lines = 0;
bool sleep = true;
int result;
fd_set readset;
for (dev=devices; dev; dev = dev->next) {
if (dev->fd > max) {
max = dev->fd;
}
}
while (1) {
do {
timeval timeout = { 0, 5000 /* 5ms */ }; // If we oversleep it's ok, i.e. ignore EINTR.
FD_ZERO(&readset);
for (dev=devices; dev; dev = dev->next) {
FD_SET(dev->fd, &readset);
}
result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout);
} while (result == -1 && errno == EINTR);
~~~
由于Logcat工具有可能同时打开了多个日志设备,因此,第19行到第26行的while循环就使用函数select来同时监控它们是否有内容可读,即是否有新的日志记录需要读取。调用函数select时,需要指定所监控的日志设备文件描述符的最大值,因此,第12行到第16行的for循环就用来查找这些打开的日志设备中的最大文件描述符,并保存在变量max中。在调用函数select之前,第22行到第24行的for循环把所有打开的日志设备的文件描述符都保存到一个fd_set对象readset中,接着第25行就调用函数select来监控前面所打开的日志设备是否有新的日志记录可读,其中,指定的等待时间为5毫秒,即如果在5毫秒之内,所有打开的日志设备都没有新的日志记录可读,那么函数select就超时返回,即它的返回值为0;否则,函数select就会将fd_set对象readset中的相应位设置为1,表示该位所对应的日志设备有新的日志记录可读,这时候函数select的返回值是大于0的。如果在调用函数select的过程中,Logcat工具有信号需要处理,那么函数select的返回值就会等于-1,并且错误代码errno等于EINTR,表示Logcat工具需要重新调用函数select来检查打开的日志设备是否有新的日志记录可读。
当函数跳出第19行到26行的while循环之后,有可能是等待超时,也有可能是所监控的日志设备中有新的日志记录可读,因此,我们需要分两种情况来分析日志记录的读取过程。
首先分析日志设备中有新的日志记录可读的情况,如下所示。
**system/core/logcat/logcat.cpp**
~~~
if (result >= 0) {
for (dev=devices; dev; dev = dev->next) {
if (FD_ISSET(dev->fd, &readset)) {
queued_entry_t* entry = new queued_entry_t();
/* NOTE: driver guarantees we read exactly one full entry */
ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);
if (ret < 0) {
if (errno == EINTR) {
delete entry;
goto next;
}
if (errno == EAGAIN) {
delete entry;
break;
}
perror("logcat read");
exit(EXIT_FAILURE);
}
else if (!ret) {
fprintf(stderr, "read: Unexpected EOF!\n");
exit(EXIT_FAILURE);
}
entry->entry.msg[entry->entry.len] = '\0';
dev->enqueue(entry);
++queued_lines;
}
}
~~~
第28行到第55行的for循环依次处理有新的日志记录可读的日志设备。如果一个日志设备有新的日志记录可读,那么第29行的if语句就会为true,接着第30行就会分配一个queued_entry_t结构体entry,并且第32行调用函数read把该日志设备中的一条新的日志记录读到结构体entry内部的缓冲区buf中。如果在读取日志记录的过程中出现错误,那么Logcat工具就会调用函数exit直接退出。但是如果错误码等于EINTR或者EAGAIN,就需要特殊处理。如果错误代码errno等于EINTR,就说明Logcat工具在读取日志记录的过程中被信号打断,因此,Logcat工具会重新执行next标签处的代码,即重新执行第18行的while循环来监控所打开的日志设备中是否有新的日志记录可读。如果错误码等于EAGAIN,就说明该日志设备在打开时指定了O_NONBLOCK标志,即以非阻塞的模式来打开该日志设备,这时候Logcat工具就会跳出第28行的for循环,继续往下执行。
如果第32行成功地从相应的日志设备中读取到新的日志记录,那么第52行就会将它加入到相应的日志设备的日志记录队列中,并且第53行就会将队列中的日志记录计数queued_lines增加1,表示Logcat工具当前正在等待显示的日志记录条数。
第28行的for循环执行完成之后,就从每个有新的日志记录的日志设备中读出一条日志记录。
> 这些日志设备中的可读日志记录数可能不只一条,因此,接下来还需要执行第18行的while循环来继续读取这些日志设备中的其他日志记录。不过,在继续读取这些剩余的日志记录之前,Logcat工具先处理前面已经从日志设备中读取出来的日志记录,如下所示。
**system/core/logcat/logcat.cpp**
~~~
if (result == 0) {
// we did our short timeout trick and there's nothing new
// print everything we have and wait for more data
sleep = true;
while (true) {
chooseFirst(devices, &dev);
if (dev == NULL) {
break;
}
if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
// the caller requested to just dump the log and exit
if (g_nonblock) {
exit(0);
}
} else {
// print all that aren't the last in their list
sleep = false;
while (g_tail_lines == 0 || queued_lines > g_tail_lines) {
chooseFirst(devices, &dev);
if (dev == NULL || dev->queue->next == NULL) {
break;
}
if (g_tail_lines == 0) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
}
}
next:
;
}
}
~~~
第56行到第92行的if语句块是用来处理日志记录输出的,主要通过chooseFirst、printNextEntry和skipNextEntry三个函数来实现。
由于Logcat工具是按照写入时间的先后顺序来输出日志记录的,因此,在输出已经读取的日志记录之前,Logcat工具首先会调用函数chooseFirst找到包含有最早的未输出日志记录的日志设备,它的实现如下所示。
**system/core/logcat/logcat.cpp**
~~~
static void chooseFirst(log_device_t* dev, log_device_t** firstdev) {
for (*firstdev = NULL; dev != NULL; dev = dev->next) {
if (dev->queue != NULL && (*firstdev == NULL || cmp(dev->queue, (*firstdev)->queue) < 0)) {
*firstdev = dev;
}
}
}
~~~
因为每一个日志设备的日志队列都是按照写入时间的先后顺序来排列日志记录的,因此,函数chooseFirst只要比较日志队列中的第一个日志记录的写入时间,就可以找到包含有最早的未输出日志记录的日志设备。
真正用来输出日志记录的函数是printNextEntry,它的实现如下所示。
**system/core/logcat/logcat.cpp**
~~~
static void printNextEntry(log_device_t* dev) {
maybePrintStart(dev);
if (g_printBinary) {
printBinary(&dev->queue->entry);
} else {
processBuffer(dev, &dev->queue->entry);
}
skipNextEntry(dev);
}
~~~
第2行调用函数maybePrintStart来检查日志设备dev中的日志记录是否是第一次输出。如果是,就会首先输出一行提示性文字,如下所示。
**system/core/logcat/logcat.cpp**
~~~
static void maybePrintStart(log_device_t* dev) {
if (!dev->printed) {
dev->printed = true;
if (g_devCount > 1 && !g_printBinary) {
char buf[1024];
snprintf(buf, sizeof(buf), "--------- beginning of %s\n", dev->device);
if (write(g_outFD, buf, strlen(buf)) < 0) {
perror("output error");
exit(-1);
}
}
}
}
~~~
回到函数printNextEntry中,如果在启动Logcat工具时,指定了B选项,那么全局变量g_printBinary的值就会被设置为1,表示Logcat工具要以二进制格式来输出读取到的日志记录,因此,第4行就会调用函数printBinary来输出已经读取到的日志记录;否则,第6行就会调用函数processBuffer来输出已经读取到的日志记录。在接下来的4.6.4小节中分析日志记录的输出过程时,我们再详细分析这两个函数的实现。
日志队列中的日志记录输出之后,就要将它从队列中删除,这是通过调用函数skipNextEntry来实现的,如下所示。
**system/core/logcat/logcat.cpp**
~~~
static void skipNextEntry(log_device_t* dev) {
maybePrintStart(dev);
queued_entry_t* entry = dev->queue;
dev->queue = entry->next;
delete entry;
}
~~~
回到函数readLogLines中,我们接着分析第56行到第92行的if语句块是如何处理那些已经从日志设备中读取出来的日志记录的。
如果变量result的值等于0,即第56行的if语句为true,就说明前面在调用函数select监控日志设备中是否有新的日志记录可读时超时了。既然所有打开的日志设备都没有新的日志记录可读,第60行到第71行的while循环就是时候输出之前已经读取出来的日志记录了。第61行首先调用函数chooseFirst来获得包含有最早的未输出日志记录的日志设备,然后再考虑是否要输出这条最早的日志记录。如果在启动Logcat工具时,指定了t选项,即限定了可以输出的最新日志记录的条数,那么就需要计算所有日志设备中的未输出日志记录的条数。如果未输出的日志记录条数大于可以输出的最大值,即全局变量g_tail_lines的值,那么就需要将最早的一部分日志记录丢弃;如果没有限定可以输出的最新日志记录的条数,即全局变量g_tail_lines的值为0,那么Logcat工具就会将所有未输出的日志记录输出。处理完成日志设备中的已读取日志记录之后,第74行检查Logcat工具是否以非阻塞的模式来打开日志设备。如果是,由于这时候函数select是超时返回,即日志设备中没有新的日志记录可读,那么Logcat工具就直接从第75行退出了。
如果变量result的值大于0,即第56行的if语句为false,那么Logcat工具就会执行第77行到第92行代码来处理日志设备中的已读取日志记录。在这种情况下,日志设备中可能还有新的日志记录等待读取,因此,它的处理方式就会与函数select超时的情况有所不同。这时候如果没有限定可以输出的最新日志记录的条数,即全局变量g_tail_lines的值为0,那么Logcat工具就不用考虑日志设备中是否还有剩余的日志记录未读取了,它可以立即输出那些已经读取的日志记录;如果限定了可以输出的最新日志记录的条数,即全局变量g_tail_lines的值大于0,那么就要把最早的一部分日志记录删除,直到它们的数量小于等于限定的可以输出的最新日志记录的条数为止。在这种情况下,还需要继续读取日志设备中的其余未读取日志记录,直到所有打开的日志设备都没有新的日志记录可读时,Logcat工具才会将已经读取的日志记录输出。
以上就是日志记录的读取过程。接下来,我们继续分析日志记录的输出过程。
- 文章概述
- 下载Android源码以及查看源码
- win10 平台通过VMware Workstation安装Ubuntu
- Linux系统安装Ubuntu编译Android源码
- Eclipse快捷键大全
- 前言
- 第一篇 初识Android系统
- 第一章 准备知识
- 1.1 Linux内核参考书籍
- 1.2 Android应用程序参考书籍
- 1.3 下载、编译和运行Android源代码
- 1.3.1 下载Android源代码
- 1.3.2 编译Android源代码
- 1.3.3 运行Android模拟器
- 1.4 下载、编译和运行Android内核源代码
- 1.4.1 下载Android内核源代码
- 1.4.2 编译Android内核源代码
- 1.4.3 运行Android模拟器
- 1.5 开发第一个Android应用程序
- 1.6 单独编译和打包Android应用程序模块
- 1.6.1 导入单独编译模块的mmm命令
- 1.6.2 单独编译Android应用程序模块
- 1.6.3 重新打包Android系统镜像文件
- 第二章 硬件抽象层
- 2.1 开发Android硬件驱动程序
- 2.1.1 实现内核驱动程序模块
- 2.1.2 修改内核Kconfig文件
- 2.1.3 修改内核Makefile文件
- 2.1.4 编译内核驱动程序模块
- 2.1.5 验证内核驱动程序模块
- 2.2 开发C可执行程序验证Android硬件驱动程序
- 2.3 开发Android硬件抽象层模块
- 2.3.1 硬件抽象层模块编写规范
- 2.3.1.1 硬件抽象层模块文件命名规范
- 2.3.1.2 硬件抽象层模块结构体定义规范
- 2.3.2 编写硬件抽象层模块接口
- 2.3.3 硬件抽象层模块的加载过程
- 2.3.4 处理硬件设备访问权限问题
- 2.4 开发Android硬件访问服务
- 2.4.1 定义硬件访问服务接口
- 2.4.2 实现硬件访问服务
- 2.4.3 实现硬件访问服务的JNI方法
- 2.4.4 启动硬件访问服务
- 2.5 开发Android应用程序来使用硬件访问服务
- 第三章 智能指针
- 3.1 轻量级指针
- 3.1.1 实现原理分析
- 3.1.2 使用实例分析
- 3.2 强指针和弱指针
- 3.2.1 强指针的实现原理分析
- 3.2.2 弱指针的实现原理分析
- 3.2.3 应用实例分析
- 第二篇 Android专用驱动系统
- 第四章 Logger日志系统
- 4.1 Logger日志格式
- 4.2 Logger日志驱动程序
- 4.2.1 基础数据结构
- 4.2.2 日志设备的初始化过程
- 4.2.3 日志设备文件的打开过程
- 4.2.4 日志记录的读取过程
- 4.2.5 日志记录的写入过程
- 4.3 运行时库层日志库
- 4.4 C/C++日志写入接口
- 4.5 Java日志写入接口
- 4.6 Logcat工具分析
- 4.6.1 基础数据结构
- 4.6.2 初始化过程
- 4.6.3 日志记录的读取过程
- 4.6.4 日志记录的输出过程
- 第五章 Binder进程间通信系统
- 5.1 Binder驱动程序
- 5.1.1 基础数据结构
- 5.1.2 Binder设备的初始化过程
- 5.1.3 Binder设备文件的打开过程
- 5.1.4 设备文件内存映射过程
- 5.1.5 内核缓冲区管理
- 5.1.5.1 分配内核缓冲区
- 5.1.5.2 释放内核缓冲区
- 5.1.5.3 查询内核缓冲区
- 5.2 Binder进程间通信库
- 5.3 Binder进程间通信应用实例
- 5.4 Binder对象引用计数技术
- 5.4.1 Binder本地对象的生命周期
- 5.4.2 Binder实体对象的生命周期
- 5.4.3 Binder引用对象的生命周期
- 5.4.4 Binder代理对象的生命周期
- 5.5 Binder对象死亡通知机制
- 5.5.1 注册死亡接收通知
- 5.5.2 发送死亡接收通知
- 5.5.3 注销死亡接收通知
- 5.6 Service Manager的启动过程
- 5.6.1 打开和映射Binder设备文件
- 5.6.2 注册成为Binder上下文管理者
- 5.6.3 循环等待Client进程请求
- 5.7 Service Manager代理对象接口的获取过程
- 5.8 Service的启动过程
- 5.8.1 注册Service组件
- 5.8.1.1 封装通信数据为Parcel对象
- 5.8.1.2 发送和处理BC_TRANSACTION命令协议
- 5.8.1.3 发送和处理BR_TRANSACTION返回协议
- 5.8.1.4 发送和处理BC_REPLY命令协议
- 5.8.1.5 发送和处理BR_REPLY返回协议
- 5.8.2 循环等待Client进程请求
- 5.9 Service代理对象接口的获取过程
- 5.10 Binder进程间通信机制的Java实现接口
- 5.10.1 获取Service Manager的Java代理对象接口
- 5.10.2 AIDL服务接口解析
- 5.10.3 Java服务的启动过程
- 5.10.4 获取Java服务的代理对象接口
- 5.10.5 Java服务的调用过程
- 第六章 Ashmem匿名共享内存系统
- 6.1 Ashmem驱动程序
- 6.1.1 相关数据结构
- 6.1.2 设备初始化过程
- 6.1.3 设备文件打开过程
- 6.1.4 设备文件内存映射过程
- 6.1.5 内存块的锁定和解锁过程
- 6.1.6 解锁状态内存块的回收过程
- 6.2 运行时库cutils的匿名共享内存接口
- 6.3 匿名共享内存的C++访问接口
- 6.3.1 MemoryHeapBase
- 6.3.1.1 Server端的实现
- 6.3.1.2 Client端的实现
- 6.3.2 MemoryBase
- 6.3.2.1 Server端的实现
- 6.3.2.2 Client端的实现
- 6.3.3 应用实例
- 6.4 匿名共享内存的Java访问接口
- 6.4.1 MemoryFile
- 6.4.2 应用实例
- 6.5 匿名共享内存的共享原理分析
- 第三篇 Android应用程序框架篇
- 第七章 Activity组件的启动过程
- 7.1 Activity组件应用实例
- 7.2 根Activity的启动过程
- 7.3 Activity在进程内的启动过程
- 7.4 Activity在新进程中的启动过程
- 第八章 Service组件的启动过程
- 8.1 Service组件应用实例
- 8.2 Service在新进程中的启动过程
- 8.3 Service在进程内的绑定过程
- 第九章 Android系统广播机制
- 9.1 广播应用实例
- 9.2 广播接收者的注册过程
- 9.3 广播的发送过程
- 第十章 Content Provider组件的实现原理
- 10.1 Content Provider组件应用实例
- 10.1.1 ArticlesProvider
- 10.1.2 Article
- 10.2 Content Provider组件的启动过程
- 10.3 Content Provider组件的数据共享原理
- 10.4 Content Provider组件的数据更新通知机制
- 10.4.1 内容观察者的注册过程
- 10.4.2 数据更新的通知过程
- 第十一章 Zygote和System进程的启动过程
- 11.1 Zygote进程的启动脚本
- 11.2 Zygote进程的启动过程
- 11.3 System进程的启动过程
- 第十二章 Android应用程序进程的启动过程
- 12.1 应用程序进程的创建过程
- 12.2 Binder线程池的启动过程
- 12.3 消息循环的创建过程
- 第十三章 Android应用程序的消息处理机制
- 13.1 创建线程消息队列
- 13.2 线程消息循环过程
- 13.3 线程消息发送过程
- 13.4 线程消息处理过程
- 第十四章 Android应用程序的键盘消息处理机制
- 14.1 InputManager的启动过程
- 14.1.1 创建InputManager
- 14.1.2 启动InputManager
- 14.1.3 启动InputDispatcher
- 14.1.4 启动InputReader
- 14.2 InputChannel的注册过程
- 14.2.1 创建InputChannel
- 14.2.2 注册Server端InputChannel
- 14.2.3 注册当前激活窗口
- 14.2.4 注册Client端InputChannel
- 14.3 键盘消息的分发过程
- 14.3.1 InputReader处理键盘事件
- 14.3.2 InputDispatcher分发键盘事件
- 14.3.3 当前激活的窗口获得键盘消息
- 14.3.4 InputDispatcher获得键盘事件处理完成通知
- 14.4 InputChannel的注销过程
- 14.4.1 销毁应用程序窗口
- 14.4.2 注销Client端InputChannel
- 14.4.3 注销Server端InputChannel
- 第十五章 Android应用程序线程的消息循环模型
- 15.1 应用程序主线程消息循环模型
- 15.2 界面无关的应用程序子线程消息循环模型
- 15.3 界面相关的应用程序子线程消息循环模型
- 第十六章 Android应用程序的安装和显示过程
- 16.1 应用程序的安装过程
- 16.2 应用程序的显示过程