Android日志系統(tǒng)Logcat源代碼簡要分析_第1頁
Android日志系統(tǒng)Logcat源代碼簡要分析_第2頁
Android日志系統(tǒng)Logcat源代碼簡要分析_第3頁
Android日志系統(tǒng)Logcat源代碼簡要分析_第4頁
Android日志系統(tǒng)Logcat源代碼簡要分析_第5頁
已閱讀5頁,還剩17頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

1、 在前面兩篇文章Android日志系統(tǒng)驅(qū)動程序Logger源代碼分析和Android應(yīng)用程序框架層和系統(tǒng)運(yùn)行庫層日志系統(tǒng)源代碼中,介紹了Android內(nèi)核空間層、系統(tǒng)運(yùn)行庫層和應(yīng)用程序框架層日志系統(tǒng)相關(guān)的源代碼,其中,后一篇文章著重介紹了日志的寫入操作。為了描述完整性,這篇文章著重介紹日志的讀取操作,這就是我們在開發(fā)Android應(yīng)用程序時(shí),經(jīng)常要用到日志查看工具Logcat了。 Logcat工具內(nèi)置在Android系統(tǒng)中,可以在主機(jī)上通過adb logcat命令來查看模擬機(jī)上日志信息。Logcat工具的用法很豐富,因此,源代碼也比較多,本文并不打算完整地介紹整個(gè)Logcat工具的源代碼,主要

2、是介紹Logcat讀取日志的主線,即從打開日志設(shè)備文件到讀取日志設(shè)備文件的日志記錄到輸出日志記錄的主要過程,希望能起到一個(gè)拋磚引玉的作用。 Logcat工具源代碼位于system/core/logcat目錄下,只有一個(gè)源代碼文件logcat.cpp,編譯后生成的可執(zhí)行文件位于out/target/product/generic/system/bin目錄下,在模擬機(jī)中,可以在/system/bin目錄下看到logcat工具。下面我們就分段來閱讀logcat.cpp源代碼文件。 一. Logcat工具的相關(guān)數(shù)據(jù)結(jié)構(gòu)。 這些數(shù)據(jù)結(jié)構(gòu)是用來保存從日志設(shè)備文件讀出來的日志記錄: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應(yīng)用程序框架層和系統(tǒng)運(yùn)行庫層日志系統(tǒng)源代碼分析一文有提到,為了方便描述,這里列出這個(gè)宏和結(jié)構(gòu)體的定義: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) 從結(jié)構(gòu)體structqueued_entry

7、_t和struct log_device_t的定義可以看出,每一個(gè)log_device_t都包含有一個(gè)queued_entry_t隊(duì)列,queued_entry_t就是對應(yīng)從日志設(shè)備文件讀取出來的一條日志記錄了,而log_device_t則是對應(yīng)一個(gè)日志設(shè)備文件上下文。在Android日志系統(tǒng)驅(qū)動程序Logger源代碼分析一文中,我們曾提到,Android日志系統(tǒng)有三個(gè)日志設(shè)備文件,分別是/dev/log/main、/dev/log/events和/dev/log/radio。 每個(gè)日志設(shè)備上下文通過其next成員指針連接起來,每個(gè)設(shè)備文件上下文的日志記錄也是通過next指針連接起來。日志記錄

8、隊(duì)例是按時(shí)間戳從小到大排列的,這個(gè)log_device_t:enqueue函數(shù)可以看出,當(dāng)要插入一條日志記錄的時(shí)候,先隊(duì)列頭開始查找,直到找到一個(gè)時(shí)間戳比當(dāng)前要插入的日志記錄的時(shí)間戳大的日志記錄的位置,然后插入當(dāng)前日志記錄。比較函數(shù)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. 為什么日志記錄要按照時(shí)間戳從小到大排序呢

9、?原來,Logcat在使用時(shí),可以指定一個(gè)參數(shù)-t ,可以指定只顯示最新count條記錄,超過count的記錄將被丟棄,在這里的實(shí)現(xiàn)中,就是要把排在隊(duì)列前面的多余日記記錄丟棄了,因?yàn)榕旁谇懊娴娜罩居涗浭亲钆f的,默認(rèn)是顯示所有的日志記錄。在下面的代碼中,我們還會繼續(xù)分析這個(gè)過程。 二. 打開日志設(shè)備文件。 Logcat工具的入口函數(shù)main,打開日志設(shè)備文件和一些初始化的工作也是在這里進(jìn)行。main函數(shù)的內(nèi)容也比較多,前面的邏輯都是解析命令行參數(shù)。這里假設(shè)我們使用logcat工具時(shí),不帶任何參數(shù)。這不會影響我們分析logcat讀取日志的主線,有興趣的讀取可以自行分析解析命令行參數(shù)的邏輯。 分析完

10、命令行參數(shù)以后,就開始要創(chuàng)建日志設(shè)備文件上下文結(jié)構(gòu)體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. 由于我們假設(shè)使用logcat時(shí),不帶任何命令行參數(shù),這里的devices變量為NULL,因此,就會默認(rèn)創(chuàng)建/dev/log/main設(shè)備上下文結(jié)構(gòu)體,如果存在/dev/log/system設(shè)備文件,也會一并創(chuàng)建。宏LOGGER_LOG_MAIN和LOGGER_LOG_SYSTEM也是定義在system/core/include/cutils/logger.h文件中:view

12、 plain1. #defineLOGGER_LOG_MAINlog/main2. #defineLOGGER_LOG_SYSTEMlog/system 我們在Android日志系統(tǒng)驅(qū)動程序Logger源代碼分析一文中看到,在Android日志系統(tǒng)驅(qū)動程序Logger中,默認(rèn)是不創(chuàng)建/dev/log/system設(shè)備文件的。 往下看,調(diào)用setupOutput()函數(shù)來初始化輸出文件:view plain1. android:setupOutput(); setupOutput()函數(shù)定義如下: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. 如果我們在執(zhí)行l(wèi)ogcat命令時(shí),指定了-f 選項(xiàng),日志內(nèi)

14、容就輸出到filename文件中,否則,就輸出到標(biāo)準(zhǔn)輸出控制臺去了。 再接下來,就是打開日志設(shè)備文件了: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. 如果執(zhí)行l(wèi)ogcat命令的目的是清空日志,即clearLog為true,則調(diào)用android:clearLog函數(shù)

16、來執(zhí)行清空日志操作:view plain1. staticintclearLog(intlogfd)2. 3. returnioctl(logfd,LOGGER_FLUSH_LOG);4. 這里是通過標(biāo)準(zhǔn)的文件函數(shù)ioctl函數(shù)來執(zhí)行日志清空操作,具體可以參考logger驅(qū)動程序的實(shí)現(xiàn)。 如果執(zhí)行l(wèi)ogcat命令的目的是獲取日志內(nèi)存緩沖區(qū)的大小,即getLogSize為true,通過調(diào)用android:getLogSize函數(shù)實(shí)現(xiàn):view plain1. /*returnsthetotalsizeofthelogsringbuffer*/2. staticintgetLogSize(intl

17、ogfd)3. 4. returnioctl(logfd,LOGGER_GET_LOG_BUF_SIZE);5. 如果為負(fù)數(shù),即size 0,就表示出錯了,退出程序。 接著驗(yàn)證日志緩沖區(qū)可讀內(nèi)容的大小,即調(diào)用android:getLogReadableSize函數(shù):view plain1. /*returnsthereadablesizeofthelogsringbuffer(thatis,amountofthelogconsumed)*/2. staticintgetLogReadableSize(intlogfd)3. 4. returnioctl(logfd,LOGGER_GET_LOG

18、_LEN);5. 如果返回負(fù)數(shù),即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. 由于可能同時(shí)打開了多個(gè)日志設(shè)備文件,這里使用select函數(shù)來同時(shí)監(jiān)控哪個(gè)文件當(dāng)前可讀: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,就表示有日志設(shè)備文件可讀或者超時(shí)。接著,用一個(gè)for語句檢查哪個(gè)設(shè)備

23、文件可讀,即FD_ISSET(dev-fd, &readset)是否為true,如果為true,表明可讀,就要進(jìn)一步通過read函數(shù)將日志讀出,注意,每次只讀出一條日志記錄: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. 調(diào)用read函數(shù)之前,先創(chuàng)建一個(gè)日志記錄項(xiàng)entry,接著調(diào)用read函數(shù)將日志讀到entry-buf中,最后調(diào)用dev-enqueue(entry)將日志記錄加入到日志隊(duì)例中去。同時(shí),把當(dāng)前的日志記錄數(shù)保存在queued_lines變量中。 繼續(xù)進(jìn)一步處理日志: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,表明是等待超時(shí)了,目前沒有新的日志可讀,這時(shí)候就要先處理之前已經(jīng)讀出來的日志。調(diào)用chooseFirst選擇日志隊(duì)列不為空,且日志隊(duì)列中的第一個(gè)日志記錄的時(shí)間戳為最小的設(shè)備,即先輸出最舊的日志: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. 如果存在這樣的日志設(shè)備,接著判斷日志記錄是應(yīng)該丟棄還是輸出。前面我們說過,如果執(zhí)行l(wèi)ogcat命令時(shí),指定了參數(shù)-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表示顯示最新記錄的條數(shù),如果為0,就表示全部顯示。如果g_tail_lines = 0或者queued_lines 0,表明有新的日志可讀,這時(shí)候的處理方式與result = 0的情況不同,因?yàn)檫@時(shí)候還有新的日志可讀,所以就不能先急著處理之前已經(jīng)讀出來的日志。這里,分兩種情況考慮,如果能設(shè)置了只顯示最新的g_tail_lines條記錄,并且當(dāng)前已經(jīng)讀出來的日志記錄條數(shù)已經(jīng)超過g_tail_lines,就要丟棄,剩下的先不處理,等到下次再來處理;如果沒有設(shè)備顯示最新的g_tail_lines條記錄,即g_tail_lines = 0,這種情況就和result = 0的情況處理方式

29、一樣,先處理所有已經(jīng)讀出的日志記錄,再進(jìn)入下一次循環(huán)。希望讀者可以好好體會這段代碼: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. 丟棄日志記錄的函數(shù)skipNextEntry實(shí)現(xiàn)

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. 這里只是簡單地跳過日志隊(duì)列頭,這樣就把最舊的日志丟棄了。 printNextEntry函數(shù)處理日志輸出,下一節(jié)中繼續(xù)分析。 四.輸出日志設(shè)備文件的內(nèi)容。 從前面的分析中看出,最終日志設(shè)備文件內(nèi)容的輸出是通過printNextEntry函數(shù)進(jìn)行的: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時(shí),以二進(jìn)制方式輸出日志內(nèi)容到指定的文件中: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. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論