Android日志系统Logcat源代码简要分析_第1页
Android日志系统Logcat源代码简要分析_第2页
Android日志系统Logcat源代码简要分析_第3页
Android日志系统Logcat源代码简要分析_第4页
Android日志系统Logcat源代码简要分析_第5页
已阅读5页,还剩17页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

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

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

3、structqueued_entry_t2. union3. unsignedcharbufLOGGER_ENTRY_MAX_LEN+1_attribute_(aligned(4);4. structlogger_entryentry_attribute_(aligned(4);5. ;6. queued_entry_t*next;7. 8. queued_entry_t()9. next=NULL;10. 11. ;12. 13. structlog_device_t14. char*device;15. boolbinary;16. intfd;17. boolprinted;18. ch

4、arlabel;19. 20. queued_entry_t*queue;21. log_device_t*next;22. 23. log_device_t(char*d,boolb,charl)24. device=d;25. binary=b;26. label=l;27. queue=NULL;28. next=NULL;29. printed=false;30. 31. 32. voidenqueue(queued_entry_t*entry)33. if(this-queue=NULL)34. this-queue=entry;35. else36. queued_entry_t*

5、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和structlogger_entry定义在system/core/include/cutils/logger.h文件中,在Android应用程序框架层和系统运行库层日志系统源代码分析一文有提到,为了方便描述,这里列出这个宏和结构体的定义:view plain1. structlogger_entry2. _u16len;/*le

6、ngthofthepayload*/3. _u16_pad;/*nomatterwhat,weget2bytesofpadding*/4. _s32pid;/*generatingprocessspid*/5. _s32tid;/*generatingprocessstid*/6. _s32sec;/*secondssinceEpoch*/7. _s32nsec;/*nanoseconds*/8. charmsg0;/*theentryspayload*/9. ;10. 11. #defineLOGGER_ENTRY_MAX_LEN(4*1024) 从结构体structqueued_entry

7、_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指针连接起来。日志记录

8、队例是按时间戳从小到大排列的,这个log_device_t:enqueue函数可以看出,当要插入一条日志记录的时候,先队列头开始查找,直到找到一个时间戳比当前要插入的日志记录的时间戳大的日志记录的位置,然后插入当前日志记录。比较函数cmp的定义如下:view plain1. staticintcmp(queued_entry_t*a,queued_entry_t*b)2. intn=a-entry.sec-b-entry.sec;3. if(n!=0)4. returnn;5. 6. returna-entry.nsec-b-entry.nsec;7. 为什么日志记录要按照时间戳从小到大排序呢

9、?原来,Logcat在使用时,可以指定一个参数-t ,可以指定只显示最新count条记录,超过count的记录将被丢弃,在这里的实现中,就是要把排在队列前面的多余日记记录丢弃了,因为排在前面的日志记录是最旧的,默认是显示所有的日志记录。在下面的代码中,我们还会继续分析这个过程。 二. 打开日志设备文件。 Logcat工具的入口函数main,打开日志设备文件和一些初始化的工作也是在这里进行。main函数的内容也比较多,前面的逻辑都是解析命令行参数。这里假设我们使用logcat工具时,不带任何参数。这不会影响我们分析logcat读取日志的主线,有兴趣的读取可以自行分析解析命令行参数的逻辑。 分析完

10、命令行参数以后,就开始要创建日志设备文件上下文结构体struct log_device_t了:view plain1. if(!devices)2. devices=newlog_device_t(strdup(/dev/LOGGER_LOG_MAIN),false,m);3. android:g_devCount=1;4. intaccessmode=5. (mode&O_RDONLY)?R_OK:06. |(mode&O_WRONLY)?W_OK:0;7. /onlyaddthisifitsavailable8. if(0=access(/dev/LOGGER_LOG_SYSTEM,acc

11、essmode)9. devices-next=newlog_device_t(strdup(/dev/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

12、 plain1. #defineLOGGER_LOG_MAINlog/main2. #defineLOGGER_LOG_SYSTEMlog/system 我们在Android日志系统驱动程序Logger源代码分析一文中看到,在Android日志系统驱动程序Logger中,默认是不创建/dev/log/system设备文件的。 往下看,调用setupOutput()函数来初始化输出文件:view plain1. android:setupOutput(); setupOutput()函数定义如下:view plain1. staticvoidsetupOutput()2. 3. 4. if(g_

13、outputFileName=NULL)5. g_outFD=STDOUT_FILENO;6. 7. else8. structstatstatbuf;9. 10. g_outFD=openLogFile(g_outputFileName);11. 12. if(g_outFD0)13. perror(couldntopenoutputfile);14. exit(-1);15. 16. 17. fstat(g_outFD,&statbuf);18. 19. g_outByteCount=statbuf.st_size;20. 21. 如果我们在执行logcat命令时,指定了-f 选项,日志内

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

15、 17. 18. 19. if(getLogSize)20. intsize,readable;21. 22. size=android:getLogSize(dev-fd);23. if(sizefd);29. if(readabledevice,36. size/1024,readable/1024,37. (int)LOGGER_ENTRY_MAX_LEN,(int)LOGGER_ENTRY_MAX_PAYLOAD);38. 39. 40. dev=dev-next;41. 如果执行logcat命令的目的是清空日志,即clearLog为true,则调用android:clearLog函数

16、来执行清空日志操作:view plain1. staticintclearLog(intlogfd)2. 3. returnioctl(logfd,LOGGER_FLUSH_LOG);4. 这里是通过标准的文件函数ioctl函数来执行日志清空操作,具体可以参考logger驱动程序的实现。 如果执行logcat命令的目的是获取日志内存缓冲区的大小,即getLogSize为true,通过调用android:getLogSize函数实现:view plain1. /*returnsthetotalsizeofthelogsringbuffer*/2. staticintgetLogSize(intl

17、ogfd)3. 4. returnioctl(logfd,LOGGER_GET_LOG_BUF_SIZE);5. 如果为负数,即size 0,就表示出错了,退出程序。 接着验证日志缓冲区可读内容的大小,即调用android:getLogReadableSize函数:view plain1. /*returnsthereadablesizeofthelogsringbuffer(thatis,amountofthelogconsumed)*/2. staticintgetLogReadableSize(intlogfd)3. 4. returnioctl(logfd,LOGGER_GET_LOG

18、_LEN);5. 如果返回负数,即readable next)13. if(dev-fdmax)14. max=dev-fd;15. 16. 17. 18. while(1)19. do20. timevaltimeout=0,5000/*5ms*/;/Ifweoversleepitsok,i.e.ignoreEINTR.21. FD_ZERO(&readset);22. for(dev=devices;dev;dev=dev-next)23. FD_SET(dev-fd,&readset);24. 25. result=select(max+1,&readset,NULL,NULL,slee

19、p?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,&readset)31. queued_entry_t*entry=newqueued_entry_t();32. /*NOTE:driverguaranteeswereadexactlyonefullentry*/33. ret=read(dev-fd,entry-buf,LOGGER_ENTRY_MAX_LEN);34. if(ret

20、entry.msgentry-entry.len=0;52. 53. dev-enqueue(entry);54. +queued_lines;55. 56. 57. 58. if(result=0)59. /wedidourshorttimeouttrickandtheresnothingnew60. /printeverythingwehaveandwaitformoredata61. sleep=true;62. while(true)63. chooseFirst(devices,&dev);64. if(dev=NULL)65. break;66. 67. if(g_tail_lin

21、es=0|queued_linesg_tail_lines)83. chooseFirst(devices,&dev);84. if(dev=NULL|dev-queue-next=NULL)85. break;86. 87. if(g_tail_lines=0)88. printNextEntry(dev);89. else90. skipNextEntry(dev);91. 92. -queued_lines;93. 94. 95. 96. next:97. ;98. 99. 由于可能同时打开了多个日志设备文件,这里使用select函数来同时监控哪个文件当前可读:view plain1.

22、do2. timevaltimeout=0,5000/*5ms*/;/Ifweoversleepitsok,i.e.ignoreEINTR.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,就表示有日志设备文件可读或者超时。接着,用一个for语句检查哪个设备

23、文件可读,即FD_ISSET(dev-fd, &readset)是否为true,如果为true,表明可读,就要进一步通过read函数将日志读出,注意,每次只读出一条日志记录:view plain1. for(dev=devices;dev;dev=dev-next)2. if(FD_ISSET(dev-fd,&readset)3. queued_entry_t*entry=newqueued_entry_t();4. /*NOTE:driverguaranteeswereadexactlyonefullentry*/5. ret=read(dev-fd,entry-buf,LOGGER_ENT

24、RY_MAX_LEN);6. if(retentry.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 plain1. if(result=0)2. /wedidourshorttimeouttrickandtheresnoth

25、ingnew3. /printeverythingwehaveandwaitformoredata4. sleep=true;5. while(true)6. chooseFirst(devices,&dev);7. if(dev=NULL)8. break;9. 10. if(g_tail_lines=0|queued_linesg_tail_lines)26. chooseFirst(devices,&dev);27. if(dev=NULL|dev-queue-next=NULL)28. break;29. 30. if(g_tail_lines=0)31. printNextEntry

26、(dev);32. else33. skipNextEntry(dev);34. 35. -queued_lines;36. 37. 如果result = 0,表明是等待超时了,目前没有新的日志可读,这时候就要先处理之前已经读出来的日志。调用chooseFirst选择日志队列不为空,且日志队列中的第一个日志记录的时间戳为最小的设备,即先输出最旧的日志:view plain1. staticvoidchooseFirst(log_device_t*dev,log_device_t*firstdev)2. for(*firstdev=NULL;dev!=NULL;dev=dev-next)3. i

27、f(dev-queue!=NULL&(*firstdev=NULL|cmp(dev-queue,(*firstdev)-queue)0)4. *firstdev=dev;5. 6. 7. 如果存在这样的日志设备,接着判断日志记录是应该丢弃还是输出。前面我们说过,如果执行logcat命令时,指定了参数-t ,那么就会只显示最新的count条记录,其它的旧记录将被丢弃:view plain1. if(g_tail_lines=0|queued_lines=g_tail_lines)2. printNextEntry(dev);3. else4. skipNextEntry(dev);5. g_ta

28、il_lines表示显示最新记录的条数,如果为0,就表示全部显示。如果g_tail_lines = 0或者queued_lines 0,表明有新的日志可读,这时候的处理方式与result = 0的情况不同,因为这时候还有新的日志可读,所以就不能先急着处理之前已经读出来的日志。这里,分两种情况考虑,如果能设置了只显示最新的g_tail_lines条记录,并且当前已经读出来的日志记录条数已经超过g_tail_lines,就要丢弃,剩下的先不处理,等到下次再来处理;如果没有设备显示最新的g_tail_lines条记录,即g_tail_lines = 0,这种情况就和result = 0的情况处理方式

29、一样,先处理所有已经读出的日志记录,再进入下一次循环。希望读者可以好好体会这段代码:view plain1. while(g_tail_lines=0|queued_linesg_tail_lines)2. chooseFirst(devices,&dev);3. if(dev=NULL|dev-queue-next=NULL)4. break;5. 6. if(g_tail_lines=0)7. printNextEntry(dev);8. else9. skipNextEntry(dev);10. 11. -queued_lines;12. 丢弃日志记录的函数skipNextEntry实现

30、如下:view plain1. staticvoidskipNextEntry(log_device_t*dev)2. maybePrintStart(dev);3. queued_entry_t*entry=dev-queue;4. dev-queue=entry-next;5. deleteentry;6. 这里只是简单地跳过日志队列头,这样就把最旧的日志丢弃了。 printNextEntry函数处理日志输出,下一节中继续分析。 四.输出日志设备文件的内容。 从前面的分析中看出,最终日志设备文件内容的输出是通过printNextEntry函数进行的:view plain1. staticv

31、oidprintNextEntry(log_device_t*dev)2. maybePrintStart(dev);3. if(g_printBinary)4. printBinary(&dev-queue-entry);5. else6. processBuffer(dev,&dev-queue-entry);7. 8. skipNextEntry(dev);9. g_printBinary为true时,以二进制方式输出日志内容到指定的文件中:view plain1. voidprintBinary(structlogger_entry*buf)2. 3. size_tsize=sizeof(logger_entry)+buf-len;4. intret;5. 6. do7. ret=write(g_outFD,

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论