Android日志系统Logcat源代码简要分析.docx

上传人:牧羊曲112 文档编号:3152600 上传时间:2023-03-11 格式:DOCX 页数:30 大小:48.36KB
返回 下载 相关 举报
Android日志系统Logcat源代码简要分析.docx_第1页
第1页 / 共30页
Android日志系统Logcat源代码简要分析.docx_第2页
第2页 / 共30页
Android日志系统Logcat源代码简要分析.docx_第3页
第3页 / 共30页
Android日志系统Logcat源代码简要分析.docx_第4页
第4页 / 共30页
Android日志系统Logcat源代码简要分析.docx_第5页
第5页 / 共30页
亲,该文档总共30页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

《Android日志系统Logcat源代码简要分析.docx》由会员分享,可在线阅读,更多相关《Android日志系统Logcat源代码简要分析.docx(30页珍藏版)》请在三一办公上搜索。

1、Android日志系统Logcat源代码简要分析 在前面两篇文章Android日志系统驱动程序Logger源代码分析和Android应用程序框架层和系统运行库层日志系统源代码中,介绍了Android内核空间层、系统运行库层和应用程序框架层日志系统相关的源代码,其中,后一篇文章着重介绍了日志的写入操作。为了描述完整性,这篇文章着重介绍日志的读取操作,这就是我们在开发Android应用程序时,经常要用到日志查看工具Logcat了。 Logcat工具内置在Android系统中,可以在主机上通过adb logcat命令来查看模拟机上日志信息。Logcat工具的用法很丰富,因此,源代码也比较多,本文并不

2、打算完整地介绍整个Logcat工具的源代码,主要是介绍Logcat读取日志的主线,即从打开日志设备文件到读取日志设备文件的日志记录到输出日志记录的主要过程,希望能起到一个抛砖引玉的作用。 Logcat工具源代码位于system/core/logcat目录下,只有一个源代码文件logcat.cpp,编译后生成的可执行文件位于out/target/product/generic/system/bin目录下,在模拟机中,可以在/system/bin目录下看到logcat工具。下面我们就分段来阅读logcat.cpp源代码文件。 一. Logcat工具的相关数据结构。 这些数据结构是用来保存从日志设备

3、文件读出来的日志记录: view plain 1. struct queued_entry_t 2. union 3. unsigned char bufLOGGER_ENTRY_MAX_LEN + 1 _attribute_(aligned(4); 4. struct logger_entry entry _attribute_(aligned(4); 5. ; 6. queued_entry_t* next; 7. 8. queued_entry_t 9. next = NULL; 10. 11. ; 12. 13. struct log_device_t 14. char* device

4、; 15. bool binary; 16. int fd; 17. bool printed; 18. char label; 19. 20. queued_entry_t* queue; 21. log_device_t* next; 22. 23. log_device_t(char* d, bool b, char l) 24. device = d; 25. binary = b; 26. label = l; 27. queue = NULL; 28. next = NULL; 29. printed = false; 30. 31. 32. void enqueue(queued

5、_entry_t* entry) 33. if (this-queue = NULL) 34. this-queue = entry; 35. else 36. queued_entry_t* e = &this-queue; 37. while (*e & cmp(entry, *e) = 0) 38. e = &(*e)-next); 39. 40. entry-next = *e; 41. *e = entry; 42. 43. 44. ; 其中,宏LOGGER_ENTRY_MAX_LEN和struct logger_entry定义在system/core/include/cutils/

6、logger.h文件中,在Android应用程序框架层和系统运行库层日志系统源代码分析一文有提到,为了方便描述,这里列出这个宏和结构体的定义: view plain 1. struct logger_entry 2. _u16 len; /* length of the payload */ 3. _u16 _pad; /* no matter what, we get 2 bytes of padding */ 4. _s32 pid; /* generating processs pid */ 5. _s32 tid; /* generating processs tid */ 6. _s

7、32 sec; /* seconds since Epoch */ 7. _s32 nsec; /* nanoseconds */ 8. char msg0; /* the entrys payload */ 9. ; 10. 11. #define LOGGER_ENTRY_MAX_LEN (4*1024) 从结构体struct queued_entry_t和struct log_device_t的定义可以看出,每一个log_device_t都包含有一个queued_entry_t队列,queued_entry_t就是对应从日志设备文件读取出来的一条日志记录了,而log_device_t则是

8、对应一个日志设备文件上下文。在Android日志系统驱动程序Logger源代码分析一文中,我们曾提到,Android日志系统有三个日志设备文件,分别是/dev/log/main、/dev/log/events和/dev/log/radio。 每个日志设备上下文通过其next成员指针连接起来,每个设备文件上下文的日志记录也是通过next指针连接起来。日志记录队例是按时间戳从小到大排列的,这个log_device_t:enqueue函数可以看出,当要插入一条日志记录的时候,先队列头开始查找,直到找到一个时间戳比当前要插入的日志记录的时间戳大的日志记录的位置,然后插入当前日志记录。比较函数cmp的定

9、义如下: view plain 1. static int cmp(queued_entry_t* a, queued_entry_t* b) 2. int n = a-entry.sec - b-entry.sec; 3. if (n != 0) 4. return n; 5. 6. return a-entry.nsec - b-entry.nsec; 7. 为什么日志记录要按照时间戳从小到大排序呢?原来,Logcat在使用时,可以指定一个参数-t ,可以指定只显示最新count条记录,超过count的记录将被丢弃,在这里的实现中,就是要把排在队列前面的多余日记记录丢弃了,因为排在前面的日

10、志记录是最旧的,默认是显示所有的日志记录。在下面的代码中,我们还会继续分析这个过程。 二. 打开日志设备文件。 Logcat工具的入口函数main,打开日志设备文件和一些初始化的工作也是在这里进行。main函数的内容也比较多,前面的逻辑都是解析命令行参数。这里假设我们使用logcat工具时,不带任何参数。这不会影响我们分析logcat读取日志的主线,有兴趣的读取可以自行分析解析命令行参数的逻辑。 分析完命令行参数以后,就开始要创建日志设备文件上下文结构体struct log_device_t了: view plain 1. if (!devices) 2. devices = new log_

11、device_t(strdup(/dev/LOGGER_LOG_MAIN), false, m); 3. android:g_devCount = 1; 4. int accessmode = 5. (mode & O_RDONLY) ? R_OK : 0 6. | (mode & O_WRONLY) ? W_OK : 0; 7. / only add this if its available 8. if (0 = access(/dev/LOGGER_LOG_SYSTEM, accessmode) 9. devices-next = new log_device_t(strdup(/dev

12、/LOGGER_LOG_SYSTEM), false, s); 10. android:g_devCount+; 11. 12. 由于我们假设使用logcat时,不带任何命令行参数,这里的devices变量为NULL,因此,就会默认创建/dev/log/main设备上下文结构体,如果存在/dev/log/system设备文件,也会一并创建。宏LOGGER_LOG_MAIN和LOGGER_LOG_SYSTEM也是定义在system/core/include/cutils/logger.h文件中: view plain 1. #define LOGGER_LOG_MAIN log/main 2.

13、#define LOGGER_LOG_SYSTEM log/system 我们在Android日志系统驱动程序Logger源代码分析一文中看到,在Android日志系统驱动程序Logger中,默认是不创建/dev/log/system设备文件的。 往下看,调用setupOutput函数来初始化输出文件: view plain 1. android:setupOutput; setupOutput函数定义如下: view plain 1. static void setupOutput 2. 3. 4. if (g_outputFileName = NULL) 5. g_outFD = STDO

14、UT_FILENO; 6. 7. else 8. struct stat statbuf; 9. 10. g_outFD = openLogFile (g_outputFileName); 11. 12. if (g_outFD 0) 13. perror (couldnt open output file); 14. exit(-1); 15. 16. 17. fstat(g_outFD, &statbuf); 18. 19. g_outByteCount = statbuf.st_size; 20. 21. 如果我们在执行logcat命令时,指定了-f 选项,日志内容就输出到filenam

15、e文件中,否则,就输出到标准输出控制台去了。 再接下来,就是打开日志设备文件了: view plain 1. dev = devices; 2. while (dev) 3. dev-fd = open(dev-device, mode); 4. if (dev-fd device, strerror(errno); 7. exit(EXIT_FAILURE); 8. 9. 10. if (clearLog) 11. int ret; 12. ret = android:clearLog(dev-fd); 13. if (ret) 14. perror(ioctl); 15. exit(EXI

16、T_FAILURE); 16. 17. 18. 19. if (getLogSize) 20. int size, readable; 21. 22. size = android:getLogSize(dev-fd); 23. if (size fd); 29. if (readable device, 36. size / 1024, readable / 1024, 37. (int) LOGGER_ENTRY_MAX_LEN, (int) LOGGER_ENTRY_MAX_PAYLOAD); 38. 39. 40. dev = dev-next; 41. 如果执行logcat命令的目的

17、是清空日志,即clearLog为true,则调用android:clearLog函数来执行清空日志操作: view plain 1. static int clearLog(int logfd) 2. 3. return ioctl(logfd, LOGGER_FLUSH_LOG); 4. 这里是通过标准的文件函数ioctl函数来执行日志清空操作,具体可以参考logger驱动程序的实现。 如果执行logcat命令的目的是获取日志内存缓冲区的大小,即getLogSize为true,通过调用android:getLogSize函数实现: view plain 1. /* returns the t

18、otal size of the logs ring buffer */ 2. static int getLogSize(int logfd) 3. 4. return ioctl(logfd, LOGGER_GET_LOG_BUF_SIZE); 5. 如果为负数,即size 0,就表示出错了,退出程序。 接着验证日志缓冲区可读内容的大小,即调用android:getLogReadableSize函数: view plain 1. /* returns the readable size of the logs ring buffer (that is, amount of the log

19、consumed) */ 2. static int getLogReadableSize(int logfd) 3. 4. return ioctl(logfd, LOGGER_GET_LOG_LEN); 5. 如果返回负数,即readable next) 13. if (dev-fd max) 14. max = dev-fd; 15. 16. 17. 18. while (1) 19. do 20. timeval timeout = 0, 5000 /* 5ms */ ; / If we oversleep its ok, i.e. ignore EINTR. 21. FD_ZERO(

20、&readset); 22. for (dev=devices; dev; dev = dev-next) 23. FD_SET(dev-fd, &readset); 24. 25. result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout); 26. while (result = -1 & errno = EINTR); 27. 28. if (result = 0) 29. for (dev=devices; dev; dev = dev-next) 30. if (FD_ISSET(dev-fd, &r

21、eadset) 31. queued_entry_t* entry = new queued_entry_t; 32. /* NOTE: driver guarantees we read exactly one full entry */ 33. ret = read(dev-fd, entry-buf, LOGGER_ENTRY_MAX_LEN); 34. if (ret entry.msgentry-entry.len = 0; 52. 53. dev-enqueue(entry); 54. +queued_lines; 55. 56. 57. 58. if (result = 0) 5

22、9. / we did our short timeout trick and theres nothing new 60. / print everything we have and wait for more data 61. sleep = true; 62. while (true) 63. chooseFirst(devices, &dev); 64. if (dev = NULL) 65. break; 66. 67. if (g_tail_lines = 0 | queued_lines g_tail_lines) 83. chooseFirst(devices, &dev);

23、 84. if (dev = NULL | dev-queue-next = NULL) 85. break; 86. 87. if (g_tail_lines = 0) 88. printNextEntry(dev); 89. else 90. skipNextEntry(dev); 91. 92. -queued_lines; 93. 94. 95. 96. next: 97. ; 98. 99. 由于可能同时打开了多个日志设备文件,这里使用select函数来同时监控哪个文件当前可读: view plain 1. do 2. timeval timeout = 0, 5000 /* 5ms

24、 */ ; / If we oversleep its ok, i.e. ignore EINTR. 3. FD_ZERO(&readset); 4. for (dev=devices; dev; dev = dev-next) 5. FD_SET(dev-fd, &readset); 6. 7. result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout);8. while (result = -1 & errno = EINTR); 如果result = 0,就表示有日志设备文件可读或者超时。接着,用一个fo

25、r语句检查哪个设备文件可读,即FD_ISSET(dev-fd, &readset)是否为true,如果为true,表明可读,就要进一步通过read函数将日志读出,注意,每次只读出一条日志记录: view plain 1. for (dev=devices; dev; dev = dev-next) 2. if (FD_ISSET(dev-fd, &readset) 3. queued_entry_t* entry = new queued_entry_t; 4. /* NOTE: driver guarantees we read exactly one full entry */ 5. re

26、t = read(dev-fd, entry-buf, LOGGER_ENTRY_MAX_LEN); 6. if (ret entry.msgentry-entry.len = 0; 24. 25. dev-enqueue(entry); 26. +queued_lines; 27. 28. 调用read函数之前,先创建一个日志记录项entry,接着调用read函数将日志读到entry-buf中,最后调用dev-enqueue(entry)将日志记录加入到日志队例中去。同时,把当前的日志记录数保存在queued_lines变量中。 继续进一步处理日志: view plain 1. if (re

27、sult = 0) 2. / we did our short timeout trick and theres nothing new 3. / print everything we have and wait for more data 4. sleep = true; 5. while (true) 6. chooseFirst(devices, &dev); 7. if (dev = NULL) 8. break; 9. 10. if (g_tail_lines = 0 | queued_lines g_tail_lines) 26. chooseFirst(devices, &dev); 27. if (dev = NULL | dev-queue-next = NULL) 28. break; 29. 30. if (

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号