Android提供的LOG機制的實現貫穿瞭Java,JNI,本地c/c++實現以及LINUX內核驅動等Android的各個層次,並且簡單明晰,是一個相當不錯的解讀案例。本系列文章針對LOG機制的內部實現機理進行解讀,本文是本系列的第五篇,解讀應用程序LogCat如何通過對設備文件的open()/select()/read()來獲取LOG信息。
從前文知道,LOG被寫入到瞭驅動的節點,那如何獲取這些LOG信息並呈現出來的呢?ANDROID裡是有個叫LogCat的應用程序被用來獲取LOG信息。LogCat不僅從設備節點處獲取LOG,並且還提供瞭很多選項供用戶來過濾、控制輸出格式等。本文隻講解如何獲取LOG部分,相關的LogCat的使用方式,可參考Android的Logcat命令詳解。
LogCat是在文件system/core/logcat/logcat.cpp中實現的。
從Logger設備驅動的實現知道,Log的讀取是阻塞的操作,亦即,有數據可用,讀出數據;否則,讀操作會被BLOCK,相應的讀進程也會被掛起等待。下面看應用程序LogCat中如何實現讀的,這可能需要不斷回頭與寫操作和驅動實現結合來看。
看具體實現之前,先看一個logcat中定義的重要的結構體log_device_t。其中的重要的成員在後面用到的時候再具體解釋。
一、打開設備節點
Android的Logcat命令詳解的命令參數-b <buffer>知道,logcat是可以通過參數來指定對哪個buffer(main/radio/event)進行操作的。Logcat的b參數解析的地方,是通過傳遞進來的參數(main/radio/event)來創建瞭一個上面的結構變量,而這些結構通過log_device_t.next鏈接起來。
if (devices) {
dev = devices;
while (dev->next) {
dev = dev->next;
}
dev->next = new log_device_t(buf, binary, optarg[0]);
} else {
devices = new log_device_t(buf, binary, optarg[0]);
}
而創建實例的時候的參數被保留瞭下來,用於後續操作。
<buf>是由LOG_FILE_DIR和optarg(-b參數)組合在一起的(為:“/dev/log/main”,“/dev/log/event”或“/dev/log/radio”),保留在device: char*;
<binary>保留在binary: bool;
<optarg[0]>是-b參數的第一個字符,保存在label: char中。
好瞭,下面就有瞭打開設備節點時的參數:
dev->fd = open(dev->device, mode);
dev->device根據-b的參數可能為“/dev/log/main”,“/dev/log/event”或“/dev/log/radio”;
mode缺省時為O_RDONLY,讀取。隻要在運行logcat時,用瞭-c參數清除log時才以O_WRONLY打開。
而打開文件的文件操作符保存在log_device_t的fd域中,用於後續的操作。
獲取Log的操作都是在readLogLines(log_device_t* devices)中實現的。
因為logcat可能會同時操作多個Buffer,而read()會阻塞讀取進程,對其他Buffer的讀取就不能進行,所以這裡用select()來判斷可讀取的Buffer。
二、select選取可讀取的Buffer
Logcat把log_device_t中的所有的buffer的文件操作符dev->fd,都放在readset中[line#7],做為select()的裡的<readfds: fd_set*>讀參數,來獲取可讀取的Buffer。這樣當任何一個Buffer上有LOG數據時,select()都會返回。當然等待過程中也忽略掉其他signal的影響。相應的代碼如下:
fd_set readset;
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);
三、讀LOG操作
select()返回之後,通過循環判定dev->fd是否在readset裡被設置(FD_ISSET)[line#3],知道哪個log buffer裡已經有數據瞭。
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);
//…
通過read()讀取[line#6]已經有數據的LOG Buffer的文件操作符dev->fd就可得到新到來的log瞭。
應用程序logcat中已經獲取瞭LOG信息,接下來對數據的處理就都可以在這裡進行瞭,可以過濾,寫文件,格式化輸入等操作。詳細的logcat的命令參數可參見Android的Logcat命令詳解。