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命令詳解。