【Android源码 栏目提醒】:网学会员为需要Android源码 的朋友们搜集整理了Android日志系统Logcat源代码简要分析 - 电子设计相关资料,希望对各位网友有所帮助!
Android日志系统Logcat源代码简要分析 Logcat工具内置在
Android系统中可以在主机上通过adb logcat命令来查看模拟机上日志信息。
Logcat工具的用法很丰富因此源代码也比较多本文并不打算完整地介绍整个Logcat工具的源代码主要是介绍Logcat读取日志的主线即从打开日志设备文件到读取日志设备文件的日志记录到输出日志记录的主要过程希望能起到一个抛砖引玉的作用。
Logcat工具源代码位于system/core/logcat目录下只有一个
源代码文件logcat.cpp编译后生成的可执行文件位于out/target/product/generic/system/bin目录下在模拟机中可以在/system/bin目录下看到logcat工具。
下面我们就分段来阅读logcat.cpp源代码文件。
一. Logcat工具的相关数据结构。
这些数据结构是用来保存从日志设备文件读出来的日志记录 cpp view plaincopy 1. struct queued_entry_t 2. union 3. unsigned char bufLOGGER_ENTRY_MAX_LEN 1 __attribute__aligned4 4. struct logger_entry entry __attribute__aligned4 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 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_tchar 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 enqueuequeued_entry_t entry 33. if this-gtqueue NULL 34. this-gtqueue entry 35. else 36. queued_entry_t e ampthis-gtqueue 37. while e ampamp cmpentry e gt 0 38. e ampe-gtnext 39. 40. entry-gtnext e 41. e entry 42. 43. 44. 其中宏LOGGER_ENTRY_MAX_LEN和struct logger_entry定义在system/core/include/cutils/logger.h文件中在
Android应用程序框架层和系统运行库层日志
系统源代码分析一文有提到为了方便描述这里列出这个宏和结构体的定义 cpp view plaincopy 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. __s32 sec / seconds since Epoch / 7. __s32 nsec / nanoseconds / 8. char msg0 / the entrys payload / 9. 10. 11. define LOGGER_ENTRY_MAX_LEN 41024 从结构体struct queued_entry_t和struct log_device_t的定义可以看出每一个log_device_t都包含有一个queued_entry_t队列queued_entry_t就是对应从日志设备文件读取出来的一条日志记录了而log_device_t则是对应一个日志设备文件上下文。
在
Android日志系统驱动程序Logger源代码分析一文中我们曾提到
Android日志系统有三个日志设备文件分别是/dev/log/main、/dev/log/events和/dev/log/radio。
每个日志设备上下文通过其next成员指针连接起来每个设备文件上下文的日志记录也是通过next指针连接起来。
日志记录队例是按时间戳从小到大排列的这个log_device_t::enqueue函数可以看出当要插入一条日志记录的时候先队列头开始查找直到找到一个时间戳比当前要插入的日志记录的时间戳大的日志记录的位置然后插入当前日志记录。
比较函数cmp的定义如下 cpp view plaincopy 1. static int cmpqueued_entry_t a queued_entry_t b 2. int n a-gtentry.sec - b-gtentry.sec 3. if n 0 4. return n 5. 6. return a-gtentry.nsec - b-gtentry.nsec 7. 为什么日志记录要按照时间戳从小到大排序呢原来Logcat在使用时可以指定一个参数-t ltcountgt可以指定只显示最新count条记录超过count的记录将被丢弃在这里的实现中就是要把排在队列前面的多余
日记记录丢弃了因为排在前面的日志记录是最旧的默认是显示所有的日志记录。
在下面的代码中我们还会继续分析这个过程。
二. 打开日志设备文件。
Logcat工具的入口函数main打开日志设备文件和一些初始化的
工作也是在这里进行。
main函数的内容也比较多前面的逻辑都是解析命令行参数。
这里假设我们使用logcat工具时不带任何参数。
这不会影响我们分析logcat读取日志的主线有兴趣的读取可以自行分析解析命令行参数的逻辑。
分析完命令行参数以后就开始要创建日志设备文件上下文结构体struct log_device_t了 cpp view plaincopy 1. if devices 2. devices new log_device_tstrdupquot/dev/quotLOGGER_LOG_MAIN false m 3.
android::g_devCount 1 4. int accessmode 5. mode amp O_RDONLY R_OK : 0 6. mode amp O_WRONLY W_OK : 0 7. // only add this if its available 8. if 0 accessquot/dev/quotLOGGER_LOG_SYSTEM accessmode 9. devices-gtnext new log_device_tstrdupquot/dev/quotLOGGER_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文件中 cpp view plaincopy 1. define LOGGER_LOG_MAIN quotlog/mainquot 2. define LOGGER_LOG_SYSTEM quotlog/systemquot 我们在
Android日志系统驱动程序Logger源代码分析一文中看到在
Android日志系统驱动程序Logger中默认是不创建/dev/log/system设备文件的。
往下看调用setupOutput函数来初始化输出文件 cpp view plaincopy 1.
android::setupOutput setupOutput函数定义如下 cpp view plaincopy 1. static void setupOutput 2. 3. 4. if g_outputFileName NULL 5. g_outFD STDOUT_FILENO 6. 7. else 8. struct stat statbuf 9. 10. g_outFD openLogFile g_outputFileName 11. 12. if g_outFD lt 0 13. perror quotcouldnt open output filequot 14. exit-1 15. 16. 17. fstatg_outFD ampstatbuf 18. 19. g_outByteCount statbuf.st_size 20. 21. 如果我们在执行logcat命令时指定了-f ltfilenamegt选项日志内容就输出到filename文件中否则就输出到标准输出控制台去了。
再接下来就是打开日志设备文件了 cpp view plaincopy 1. dev devices 2. while dev 3. dev-gtfd opendev-gtdevice mode 4. if dev-gtfd lt 0 5. fprintfstderr quotUnable to open log device s: snquot 6. dev-gtdevice strerrorerrno 7. exitEXIT_FAILURE 8. 9. 10. if clearLog 11. int ret 12. ret
android::clearLogdev-gtfd 13. if ret 14. perrorquotioctlquot 15. exitEXIT_FAILURE 16. 17. 18. 19. if getLogSize 20. int size readable 21. 22. size
android::getLogSizedev-gtfd 23. if size lt 0 24. perrorquotioctlquot 25. exitEXIT_FAILURE 26. 27. 28. readable
android::getLogReadableSizedev-gtfd 29. if readable lt 0 30. perrorquotioctlquot 31. exitEXIT_FAILURE 32. 33. 34. printfquots: ring buffer is dKb dKb consumed quot 35. quotmax entry is db max payload is dbnquot dev-gtdevice 36. size / 1024 readable / 1024 37. int LOGGER_ENTRY_MAX_LEN int LOGGER_ENTRY_MAX_PAYLOAD 38. 39. 40. dev dev-gtnext 41. 如果执行logcat命令的目的是清空日志即clearLog为true则调用
android::clearLog函数来执行清空日志操作 cpp view plaincopy 1. static int clearLogint logfd 2. 3. return ioctllogfd LOGGER_FLUSH_LOG 4. 这里是通过标准的文件函数ioctl函数来执行日志清空操作具体可以参考logger驱动
程序的实现。
如果执行logcat命令的目的是获取日志内存缓冲区的大小即getLogSize为true通过调用
android::getLogSize函数实现 cpp view plaincopy 1. / returns the total size of the logs ring buffer / 2. static int getLogSizeint logfd 3. 4. return ioctllogfd LOGGER_GET_LOG_BUF_SIZE 5. 如果为负数即size lt 0就表示出错了退出程序。
接着验证日志缓冲区可读内容的大小即调用
android::getLogReadableSize函数 cpp view plaincopy 1. / returns the readable size of the logs ring buffer that is amount of the log consumed / 2. static int getLogReadableSizeint logfd 3. 4. return ioctllogfd LOGGER_GET_LOG_LEN 5. 如果返回负数即readable lt 0也表示出错了退出程序。
接下去的printf语句就是输出日志缓冲区的大小以及可读日志的大小到控制台去了。
继续看下看代码如果执行logcat命令的目的是清空日志或者获取日志的大小信息则现在就完成使命了可以退出程序了 cpp view plaincopy 1. if getLogSize 2. return 0 3. 4. if clearLog 5. return 0 6. 否则就要开始读取设备文件的日志记录了 html view plaincopy 1.
android::readLogLinesdevices 至此日志设备文件就打开并且初始化好了下面我们继续分析从日志设备文件读取日志记录的操作即readLogLines函数。
三. 读取日志设备文件。
读取日志设备文件内容的函数是readLogLines函数 cpp view plaincopy 1. static void readLogLineslog_device_t devices 2. 3. log_device_t dev 4. int max 0 5. int ret 6. int queued_lines 0 7. bool sleep true 8. 9. int result 10. fd_set readset 11. 12. for devdevices dev dev dev-gtnext 13. if dev-gtfd gt max 14. max dev-gtfd 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_ZEROampreadset 22. for devdevices dev dev dev-gtnext 23. FD_SETdev-gtfd ampreadset 24. 25. result selectmax 1 ampreadset NULL NULL sleep NULL : amptimeout 26. while result -1 ampamp errno EINTR 27. 28. if result gt 0 29. for devdevices dev dev dev-gtnext 30. if FD_ISSETdev-gtfd ampreadset 31. queued_entry_t entry new queued_entry_t 32. / NOTE: driver guarantees we read exactly one full entry / 33. ret readdev-gtfd entry-gtbuf LOGGER_ENTRY_MAX_LEN 34. if ret lt 0 35. if errno EINTR 36. delete entry 37. goto next 38. 39. if errno EAGAIN 40. delete entry 41. break 42. 43. perrorquotlogcat readquot 44. exitEXIT_FAILURE 45. 46. else if ret 47. fprintfstderr quotread: Unexpected EOFnquot 48. exitEXIT_FAILURE 49. 50. 51. entry-gtentry.msgentry-gtentry.len 0 52. 53. dev-gtenqueueentry 54. queued_lines 55. 56. 57. 58. if result 0 59. // 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. chooseFirstdevices ampdev 64. if dev NULL 65. break 66. 67. if g_tail_lines 0 queued_lines lt g_tail_lines 68. printNextEntrydev 69. else 70. skipNextEntrydev 71. 72. --queued_lines 73. 74. 75. // the caller requested to just dump the log and exit 76. if g_nonblock 77. exit0 78. 79. else 80. // print all that arent the last in their list 81. sleep false 82. while g_tail_lines 0 queued_lines gt g_tail_lines 83. chooseFirstdevices ampdev 84. if dev NULL dev-gtqueue-gtnext NULL 85. break 86. 87. if g_tail_lines 0 88. printNextEntrydev 89. else 90. skipNextEntrydev 91. 92. --queued_lines 93. 94. 95. 96. next: 97. 98. 99. 由于可能同时打开了多个日志设备文件这里使用select函数来同时监控哪个文件当前可读 cpp view plaincopy 1. do 2. timeval timeout 0 5000 / 5ms / // If we oversleep its ok i.e. ignore EINTR. 3. FD_ZEROampreadset 4. for devdevices dev dev dev-gtnext 5. FD_SETdev-gtfd ampreadset 6. 7. result selectmax 1 ampreadset NULL NULL sleep NULL : amptimeout 8. while result -1 ampamp errno EINTR 如果result gt 0就表示有日志设备文件可读或者超时。
接着用一个for语句检查哪个设备文件可读即FD_ISSETdev-gtfd ampreadset是否为true如果为true表明可读就要进一步通过read函数将日志读出注意每次只读出一条日志记录 cpp view plaincopy 1. for devdevices dev dev dev-gtnext 2. if FD_ISSETdev-gtfd ampreadset 3. queued_entry_t entry new queued_entry_t 4. / NOTE: driver guarantees we read exactly one full entry / 5. ret readdev-gtfd entry-gtbuf LOGGER_ENTRY_MAX_LEN 6. if ret lt 0 7. if errno EINTR 8. delete entry 9. goto next 10. 11. if errno EAGAIN 12. delete entry 13. break 14. 15. perrorquotlogcat readquot 16. exitEXIT_FAILURE 17. 18. else if ret 19. fprintfstderr quotread: Unexpected EOFnquot 20. exitEXIT_FAILURE 21. 22. 23. entry-gtentry.msgentry-gtentry.len 0 24. 25. dev-gtenqueueentry 26. queued_lines 27. 28. 调用read函数之前先创建一个日志记录项entry接着调用read函数将日志读到entry-gtbuf中最后调用dev-gtenqueueentry将日志记录加入到日志队例中去。
同时把当前的日志记录数保存在queued_lines变量中。
继续进一步处理日志 cpp view plaincopy 1. if result 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. chooseFirstdevices ampdev 7. if dev NULL 8. break 9. 10. if g_tail_lines 0 queued_lines lt g_tail_lines 11. printNextEntrydev 12. else 13. skipNextEntrydev 14. 15. --queued_lines 16. 17. 18. // the caller requested to just dump the log and exit 19. if g_nonblock 20. exit0 21. 22. else 23. // print all that arent the last in their list 24. sleep false 25. while g_tail_lines 0 queued_lines gt g_tail_lines 26. chooseFirstdevices ampdev 27. if dev NULL dev-gtqueue-gtnext NULL 28. break 29. 30. if g_tail_lines 0 31. printNextEntrydev 32. else 33. skipNextEn.