解读Android LOG机制的实现
轉(zhuǎn)載自http://www.cnblogs.com/hoys/archive/2011/09/30/2196199.html
?
Android提供了用戶級(jí)輕量的LOG機(jī)制,它的實(shí)現(xiàn)貫穿了Java,JNI,本地c/c++實(shí)現(xiàn)以及LINUX內(nèi)核驅(qū)動(dòng)等Android的各個(gè)層次,而且足夠簡(jiǎn)單清晰,是一個(gè)相當(dāng)不錯(cuò)的解讀案例。本系列文章針對(duì)LOG機(jī)制的內(nèi)部實(shí)現(xiàn)機(jī)理進(jìn)行解讀,本文是系列的第一篇,解讀LOG機(jī)制的實(shí)現(xiàn)架構(gòu)。
?
(1)LOG的實(shí)現(xiàn)架構(gòu)
?
LOG的運(yùn)行環(huán)境
?
??? 下圖是Android官方網(wǎng)站上給出的Android的Debug環(huán)境。
?
?
??? Android的LOG機(jī)制當(dāng)然也在這個(gè)環(huán)境中運(yùn)行。我們重點(diǎn)關(guān)注Emulator和Device上運(yùn)行的部分,App VMs產(chǎn)生LOG信息,并與ADB Device Daemon交互輸出這些信息,而ADB Device Daemon又通過相應(yīng)的協(xié)議通過USB(Device)或本地連接(Emulator),與PC上運(yùn)行的ADB Host Daemon交互,通過PC上的調(diào)試工具呈現(xiàn)給用戶。JDWP Debugger、DDMS、ADB Host Daemon以及ADB Device Daemon之間的交互與其使用的協(xié)議,不在本文討論范圍之內(nèi)。本文討論的內(nèi)容運(yùn)行在Emulator/Device上,產(chǎn)生LOG信息,并通過程序LogCat輸出。
?
?
?
LOG的實(shí)現(xiàn)架構(gòu)
?
??? Android中LOG的實(shí)現(xiàn)架構(gòu)如下圖所示,這基本上也是Android的某個(gè)模塊實(shí)現(xiàn)各個(gè)層次的經(jīng)典架構(gòu)。
?
?
??? Android應(yīng)用程序通過Framework提供的機(jī)制操作;Java領(lǐng)域需要本地c/c++提供服務(wù)的地方,通過JNI實(shí)現(xiàn);JNI調(diào)用底層庫(kù);庫(kù)函數(shù)通過操作映射的設(shè)備文件操作設(shè)備,LINUX kernel中的Driver完成相應(yīng)的操作。另外,拋開Java和JNI,LINUX上用戶域的c/c++程序,也可以通過操作設(shè)備文件來完成。?
?
??? Android的LOG也是這樣實(shí)現(xiàn)的,并將在本系列文章中分別講述。應(yīng)用程序通過android.util.Log里的各種靜態(tài)方法,輸出LOG信息[系列之二中具體講述];Log通過JNI接口調(diào)用c/c++的實(shí)現(xiàn),而本地實(shí)現(xiàn)的寫LOG,也基本就是寫信息到設(shè)備文件[系列之三中具體講述];設(shè)備文件是Android為了LOG機(jī)制而寫的LINUX的一個(gè)輕量級(jí)的驅(qū)動(dòng)logger[系列之四中具體講述];LOG信息的顯示可以是Emulator/Device上運(yùn)行的LogCat程序[系列之五中具體講述];另外,Android的本地實(shí)現(xiàn)庫(kù)也可利用現(xiàn)有機(jī)制,在c/c++的空間 直接輸出LOG[系列之六中具體講述]。
(2)JAVA域輸出LOG
LOG輸出幫助類
?
Android的Java程序通過android.util.Log類來輸出Log,下圖列出了我們常用的Log的靜態(tài)方法。
?
?
一般,要輸出Log信息,可直接調(diào)用Log.v()/Log.d()/Log.i()/Log.w()/Log.e()等類方法。這里之所以有這么多有區(qū)分的方法,這也是Log的分類。Log的分類就如同Log的靜態(tài)常量成員定義的那樣,而Log的優(yōu)先級(jí)按照數(shù)字大小排列,數(shù)字大的優(yōu)先級(jí)高。而Log.wtf()記錄的則是非常致命的FAULT信息(What? a Terrible Failure),報(bào)這個(gè)錯(cuò)誤,不光是在Log里記錄,還要在界面上有提示,并可能殺死當(dāng)前的進(jìn)程。
?
有了這些分類,如果要輸出的LOG優(yōu)先級(jí)低于當(dāng)前設(shè)置的優(yōu)先級(jí),則該Log信息不會(huì)顯示。一般的,在Java程序中用Log的方法打印Log之前,應(yīng)先用isLoggable()判斷一下,該級(jí)別是否能被記錄。
?
另外,用Log.println()能達(dá)到與Log.v()/Log.d()/…等方法同樣的輸出效果,只是在用它時(shí),要指定對(duì)應(yīng)的優(yōu)先級(jí)。
?
?
?
類Log的實(shí)現(xiàn)
?
類android.util.Log的實(shí)現(xiàn)是比較簡(jiǎn)單的。
?
?
類android.util.Log的構(gòu)造函數(shù)是私有的,并不會(huì)被實(shí)例化,只是提供了靜態(tài)的屬性和方法。
?
而android.util.Log的各種Log記錄方法的實(shí)現(xiàn)都依賴于native的實(shí)現(xiàn)println_native(),Log.v()/Log.d()/Log.i()/Log.w()/Log.e()最終都是調(diào)用了println_native()。如Log.d()的實(shí)現(xiàn):
?
??? public static int d(String tag, String msg) {
??????? return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
??? }
?
Native方法println_native()是通過JNI在c/c++中實(shí)現(xiàn)的,詳情參閱本系列之三:JNI及c/c++ 域?qū)懺O(shè)備文件。
(3)JNI及c/c++域?qū)懺O(shè)備文件
?
類Log的JNI實(shí)現(xiàn)
?
由前文知道,類android.util.Log有兩個(gè)Native方法,需要通過JNI在c/c++中實(shí)現(xiàn)。
?
<pre class="java" name="code">public static native boolean isLoggable(String tag, int level);
?
public static native int println_native(int bufID,
??????????? int priority, String tag, String msg);
?
?
?
這兩個(gè)方法是在frameworks/base/core/jni/android_util_log.cpp中實(shí)現(xiàn)的。如何實(shí)現(xiàn)JNI的,在這里不做表述。不過最終這兩個(gè)方法分別轉(zhuǎn)入了下列兩個(gè)c/c++函數(shù)的調(diào)用。
?
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
?
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
??????? jint bufID, jint priority, jstring tagObj, jstring msgObj)
?
?
isLoggable()的實(shí)現(xiàn)
?
isLoggable的實(shí)現(xiàn)是比較<level>(來自參數(shù))與當(dāng)前property里設(shè)定的“l(fā)og.tag.<tag>”(<tag>來自參數(shù))的值,大于或等于都是可記錄的。程序?qū)崿F(xiàn)片斷如下:
?
??? // LOG_NAMESPACE : “l(fā)og.tag.”
??? // chars: convert from param<tag>
??? strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);
??? strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);
?
??? len = property_get(key, buf, "");
??? int logLevel = toLevel(buf);
?
??? return (logLevel >= 0 && level >= logLevel) ? true : false;
?
?
println_native()的實(shí)現(xiàn)
?
函數(shù)android_util_Log_println_native() [文件android_util.Log.cpp中]調(diào)用了__android_log_buf_write()[文件system/core/liblog/logd_write.c中]。__android_log_buf_write()組織了參數(shù),又調(diào)用了write_to_log這個(gè)函數(shù)指針。
?
write_to_log這個(gè)函數(shù)指針是實(shí)現(xiàn)的關(guān)鍵。
?
看write_to_log的定義:
?
static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);
static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;
?
write_to_log初始是指向__write_to_log_init()這個(gè)函數(shù)的。所以第一次執(zhí)行write_to_log的時(shí)候是執(zhí)行了__write_to_log_init()。而如果write_to_log不是第一次被執(zhí)行,它已經(jīng)在__write_to_log_init()里被修改指向了__write_to_log_kernel()。
?
先看__write_to_log_init()的實(shí)現(xiàn):
?
static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
{
#ifdef HAVE_PTHREADS
??? pthread_mutex_lock(&log_init_lock);
#endif
?
??? if (write_to_log == __write_to_log_init) {
??????? log_fds[LOG_ID_MAIN] = log_open("/dev/"LOGGER_LOG_MAIN, O_WRONLY);
??????? log_fds[LOG_ID_RADIO] = log_open("/dev/"LOGGER_LOG_RADIO, O_WRONLY);
??????? log_fds[LOG_ID_EVENTS] = log_open("/dev/"LOGGER_LOG_EVENTS, O_WRONLY);
?? ?????log_fds[LOG_ID_SYSTEM] = log_open("/dev/"LOGGER_LOG_SYSTEM, O_WRONLY);
?
??????? write_to_log = __write_to_log_kernel;
?
??????? if (log_fds[LOG_ID_MAIN] < 0 || log_fds[LOG_ID_RADIO] < 0 ||
??????????????? log_fds[LOG_ID_EVENTS] < 0) {
??????????? log_close(log_fds[LOG_ID_MAIN]);
??????????? log_close(log_fds[LOG_ID_RADIO]);
??????????? log_close(log_fds[LOG_ID_EVENTS]);
??????????? log_fds[LOG_ID_MAIN] = -1;
??????????? log_fds[LOG_ID_RADIO] = -1;
??????????? log_fds[LOG_ID_EVENTS] = -1;
??????????? write_to_log = __write_to_log_null;
??????? }
?
??????? if (log_fds[LOG_ID_SYSTEM] < 0) {
??????????? log_fds[LOG_ID_SYSTEM] = log_fds[LOG_ID_MAIN];
??????? }
??? }
?
#ifdef HAVE_PTHREADS
??? pthread_mutex_unlock(&log_init_lock);
#endif
?
??? return write_to_log(log_id, vec, nr);
}
?
基本上就是做互斥訪問的保護(hù),然后如果是第一次調(diào)用(write_to_log還指向__write_to_log_init()),就打開相應(yīng)的設(shè)備文件,獲取描述符,并把write_to_log指向__write_to_log_kernel()。再在__write_to_log_kernel()中具體執(zhí)行寫入文件操作。
?
看__write_to_kernel()的實(shí)現(xiàn),基本就是寫操作:
?
static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr)
{
??? ssize_t ret;
??? int log_fd;
?
??? if (/*(int)log_id >= 0 &&*/ (int)log_id < (int)LOG_ID_MAX) {
??????? log_fd = log_fds[(int)log_id];
??? } else {
??????? return EBADF;
??? }
?
??? do {
??????? ret = log_writev(log_fd, vec, nr);
??? } while (ret < 0 && errno == EINTR);
?
??? return ret;
}
?
?
總結(jié)一下,println_native()的操作,就是打開設(shè)備文件(如果還沒打開),然后寫入數(shù)據(jù)。而具體怎么寫入的,要看Log的設(shè)備驅(qū)動(dòng)Logger的實(shí)現(xiàn)。
(4)LOG設(shè)備驅(qū)動(dòng)Logger
Log的驅(qū)動(dòng)是在kernel/drivers/staging/android/Logger.c中實(shí)現(xiàn)的。
?
一、初始化
?
看一個(gè)LINUX驅(qū)動(dòng),先看它如何初始化的。
?
static int __init init_log(struct logger_log *log)
{
??????? int ret;
?
??????? ret = misc_register(&log->misc);
??????? if (unlikely(ret)) {
??????????????? printk(KERN_ERR "logger: failed to register misc "
??????????????????????????????? "device for log '%s'!\n", log->misc.name);
??????????????? return ret;
??????? }
?
??????? printk(KERN_INFO "logger: created %luK log '%s'\n",
????????????? ?????????(unsigned long) log->size >> 10, log->misc.name);
?
??????? return 0;
}
?
static int __init logger_init(void)
{
??????? int ret;
?
??????? ret = init_log(&log_main);
??????? if (unlikely(ret))
??????????????? goto out;
?
??????? ret = init_log(&log_events);
??????? if (unlikely(ret))
??????????????? goto out;
?
??????? ret = init_log(&log_radio);
??????? if (unlikely(ret))
??????????????? goto out;
?
??????? ret = init_log(&log_system);
??????? if (unlikely(ret))
???????? ???????goto out;
?
out:
??????? return ret;
}
?
device_initcall(logger_init);
?
?
整個(gè)Logger驅(qū)動(dòng)的入口點(diǎn)就是Logger_init(),它用init_log(struct logger_log *log)初始化了log_main, log_events, log_radio和log_system四個(gè)logger_log類型的結(jié)構(gòu),而這四個(gè)結(jié)構(gòu)變量分別記錄著log的四個(gè)存儲(chǔ)體。Logger從這四個(gè)變量實(shí)現(xiàn)了同種設(shè)備的四個(gè)驅(qū)動(dòng),而log的驅(qū)動(dòng)是MISC類型的驅(qū)動(dòng),通過misc_register()向系統(tǒng)注冊(cè)。四次注冊(cè)之后,它們對(duì)應(yīng)的MINOR ID將是不同的,Looger也是通過minor來區(qū)分是哪一個(gè)驅(qū)動(dòng)的。
?
static struct logger_log *get_log_from_minor(int minor)
{
??????? if (log_main.misc.minor == minor)
??????????????? return &log_main;
??????? if (log_events.misc.minor == minor)
??????????????? return &log_events;
??????? if (log_radio.misc.minor == minor)
??????????????? return &log_radio;
??????? if (log_system.misc.minor == minor)
??????????????? return &log_system;
??????? return NULL;
}
?
?
本文將以log_main來講解Logger驅(qū)動(dòng)的實(shí)現(xiàn)。
?
?
?
二、關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
?
上節(jié)中,提到了log_main這個(gè)結(jié)構(gòu)體變量,現(xiàn)在來看它的定義。
?
?
Log_main里保存了Logger操作必須的變量。buffer指向的真是一個(gè)靜態(tài)數(shù)組,用來存放用來讀寫的數(shù)據(jù),Logger用它組成了一個(gè)邏輯上的循環(huán)隊(duì)列,寫者可以往w_off指向的地方寫東西,而一旦有內(nèi)容,會(huì)通知等待隊(duì)列wq里的讀者們來讀取內(nèi)容。因?yàn)閎uffer實(shí)現(xiàn)的是循環(huán)隊(duì)列,所以buffer的大小size經(jīng)常用來做除高位的運(yùn)算,一定要是一個(gè)2次冪的數(shù)字。mutex用來保護(hù)log_main這個(gè)關(guān)鍵資源的。Logger是MISC類型的驅(qū)動(dòng),它保留著一個(gè)miscdevice類型的變量misc。misc里面也有最為關(guān)鍵的file_operations結(jié)構(gòu),這正是應(yīng)用程序通過文件操作,與驅(qū)動(dòng)打交道的入口。
?
?
?
三、Logger實(shí)現(xiàn)的功能
?
從上面log_main的類型定義就能看出,Logger實(shí)現(xiàn)了什么。一句話概括Logger就是實(shí)現(xiàn)了讀寫者,并實(shí)現(xiàn)同步操作。不過,Logger的讀寫者有些特殊,寫者寫操作不會(huì)被阻塞,也不會(huì)寫滿溢出,也就是寫時(shí)只要有內(nèi)容可以不停的寫,超出Buffer就覆蓋舊的[與應(yīng)用程序具體的寫操作結(jié)合來看];讀者因?yàn)橐x的內(nèi)容為空就會(huì)被阻塞掛起,而一旦有內(nèi)容,所有被掛起的讀者都會(huì)被喚醒[與應(yīng)用程序具體的讀操作結(jié)合來看]。
?
?
?
下面看具體實(shí)現(xiàn)的時(shí)候,就分別從讀者和寫者的角度去看。
?
?
?
?
3.1. 寫者的實(shí)現(xiàn)
?
看二小節(jié)圖中的關(guān)鍵結(jié)構(gòu)logger_fops: file_operations,寫者的關(guān)鍵實(shí)現(xiàn)就看open、release和write這幾個(gè)函數(shù)的實(shí)現(xiàn)了,它們被分別賦值給了logger_open() / logger_release() / logger_aio_write()。
?
?
logger_open()為寫者做的工作就是,通過minor id獲得logger_log的實(shí)例,然后賦值給函數(shù)參數(shù)中傳遞進(jìn)來的file的private_data中。
?
logger_release()不需要為寫者做的什么工作。
?
?
logger_poll()因?yàn)閷懖恍枰蛔枞K赃@里檢測(cè)到是因?yàn)榉且驗(yàn)樽x而打開的文件(!(file->f_mode &FMODE_READ))時(shí),就直接返回POLLOUT | POLLWRNORM。無論怎樣都可寫。
?
logger_aio_write()是寫數(shù)據(jù)(也就是log信息)的關(guān)鍵。這里是通過異步IO的方法,應(yīng)用程序通過write()/writev()和aio_write()時(shí)都能調(diào)用到這個(gè)方法。
?
記錄log信息時(shí),寫log用的接口是writev(),寫的是vec形式的數(shù)據(jù),這邊寫的過程中來的當(dāng)然也是vec數(shù)據(jù)了,另外,寫具體之間,還寫入了類型為logger_entry的數(shù)據(jù),來記錄時(shí)間等信息。寫數(shù)據(jù)到具體buffer時(shí)因?yàn)榇鎯?chǔ)的位置可能不是連續(xù)的,而寫在buffer的結(jié)尾和開頭位置,所以要做判斷,并可能要有兩次寫的buffer的動(dòng)作。參數(shù)里的數(shù)據(jù)來自用戶空間,不能在內(nèi)核空間直接使用,要用copy_from_user()。寫完之后,用wake_up_interruptible(&log->wq)喚醒所有在掛起等待的讀者。
?
?
?
3.2. 讀者的實(shí)現(xiàn)
?
看二小節(jié)圖中的關(guān)鍵結(jié)構(gòu)logger_fops: file_operations,寫者的關(guān)鍵實(shí)現(xiàn)就看open、release和read這幾個(gè)函數(shù)的實(shí)現(xiàn)了,它們被分別賦值給了logger_open() / logger_release() / logger_read()。
?
?
logger_open() 為讀者做的工作就是,通過minor id獲得logger_log的實(shí)例,然后動(dòng)態(tài)申請(qǐng)一個(gè)logger_reader類型的讀者,并把它加入到logger_log的讀者列表readers的結(jié)尾,再賦值給函數(shù)參數(shù)中傳遞進(jìn)來的file的private_data中。
?
logger_release() 與logger_open()對(duì)應(yīng),將這個(gè)讀者從讀者列表logger_log.readers中移除,并釋放掉這個(gè)動(dòng)態(tài)申請(qǐng)的實(shí)例。
?
logger_poll()因?yàn)閼?yīng)用讀之前會(huì)調(diào)用poll()/select()查看是否可以寫。所以這里會(huì)用poll_wait()把參數(shù)中的poll_table加入到logger_log.wq中,并且如果有內(nèi)容可讀,才設(shè)置可讀標(biāo)志|= POLLIN |POLLRDNORM。
?
?
logger_read() 是讀數(shù)據(jù)(也就是log信息)的關(guān)鍵。
?
讀數(shù)據(jù)之前,要先保證有數(shù)據(jù),否則該讀者就要被掛起在logger_log的等待隊(duì)列wq上。從具體buffer讀數(shù)據(jù)到時(shí)因?yàn)榇鎯?chǔ)的位置可能不是連續(xù)的,存儲(chǔ)在buffer的結(jié)尾和開頭位置,所以要做判斷,并可能要有兩次讀去buffer的動(dòng)作。數(shù)據(jù)來自內(nèi)核空間,要通過用戶空間的參數(shù)里傳遞出去,需要copy_to_user()。
?
?
?
3.3 循環(huán)隊(duì)列的實(shí)現(xiàn)
?
這個(gè)是數(shù)據(jù)結(jié)構(gòu)里最經(jīng)典的案例了,這里不再具體解釋如何實(shí)現(xiàn),只是列出重要結(jié)構(gòu),只是希望讀者還記得數(shù)據(jù)結(jié)構(gòu)里邏輯結(jié)構(gòu)和物理結(jié)構(gòu)的說法。
?
隊(duì)列大小:log_main.size
寫頭:log_main.w_off
讀頭:logger_reader.r_off
隊(duì)列為空判斷:log_main.w_off == logger_reader.r_off
隊(duì)列為滿判斷:不需要
?
?
?
3.4 ioctl的實(shí)現(xiàn)
?
Logger提供給應(yīng)用程序通過ioctl()來獲取信息或控制LOGbuffer的功能。Logger是把logger_ioctl通過file_operations注冊(cè)到文件系統(tǒng)中來實(shí)現(xiàn)這一功能的。Logger_ioctl()提供了下列ioctl控制命令:LOGGER_GET_LOG_BUF_SIZE / LOGGER_GET_LOG_LEN/ LOGGER_GET_NEXT_ENTRY_LEN / LOGGER_FLUSH_LOG。實(shí)現(xiàn)很簡(jiǎn)單:
?
LOGGER_GET_LOG_BUF_SIZE獲取Buffer的大小,直接返回logger_log.size即可;
?
LOGGER_GET_LOG_LEN只對(duì)讀有效,獲取當(dāng)前LOG的大小,存儲(chǔ)連續(xù)的話就是log->w_off -reader->r_off,否則就是(log->size -reader->r_off) + log->w_off;
?
LOGGER_GET_NEXT_ENTRY_LEN獲取Entry的長(zhǎng)度,只對(duì)讀有效。
?
LOGGER_FLUSH_LOG只對(duì)寫打開有效。所謂FLUSH LOG,直接重置每個(gè)reader的r_off,并設(shè)置新reader要訪問用的head即可。
從前文知道,LOG被寫入到了驅(qū)動(dòng)的節(jié)點(diǎn),那如何獲取這些LOG信息并呈現(xiàn)出來的呢?ANDROID里是有個(gè)叫LogCat的應(yīng)用程序被用來獲取LOG信息。LogCat不僅從設(shè)備節(jié)點(diǎn)處獲取LOG,并且還提供了很多選項(xiàng)供用戶來過濾、控制輸出格式等。本文只講解如何獲取LOG部分,相關(guān)的LogCat的使用方式,可參考Android的Logcat命令詳解。
?
LogCat是在文件system/core/logcat/logcat.cpp中實(shí)現(xiàn)的。
?
(5)獲取LOG的應(yīng)用程序LogCat
從Logger設(shè)備驅(qū)動(dòng)的實(shí)現(xiàn)知道,Log的讀取是阻塞的操作,亦即,有數(shù)據(jù)可用,讀出數(shù)據(jù);否則,讀操作會(huì)被BLOCK,相應(yīng)的讀進(jìn)程也會(huì)被掛起等待。下面看應(yīng)用程序LogCat中如何實(shí)現(xiàn)讀的,這可能需要不斷回頭與寫操作和驅(qū)動(dòng)實(shí)現(xiàn)結(jié)合來看。
?
看具體實(shí)現(xiàn)之前,先看一個(gè)logcat中定義的重要的結(jié)構(gòu)體log_device_t。其中的重要的成員在后面用到的時(shí)候再具體解釋。
?
?
?
?
一、打開設(shè)備節(jié)點(diǎn)
?
Android的Logcat命令詳解的命令參數(shù)-b <buffer>知道,logcat是可以通過參數(shù)來指定對(duì)哪個(gè)buffer(main/radio/event)進(jìn)行操作的。Logcat的b參數(shù)解析的地方,是通過傳遞進(jìn)來的參數(shù)(main/radio/event)來創(chuàng)建了一個(gè)上面的結(jié)構(gòu)變量,而這些結(jié)構(gòu)通過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]);
??????????????? }
?
?
而創(chuàng)建實(shí)例的時(shí)候的參數(shù)被保留了下來,用于后續(xù)操作。
?
<buf>是由LOG_FILE_DIR和optarg(-b參數(shù))組合在一起的(為:“/dev/log/main”,“/dev/log/event”或“/dev/log/radio”),保留在device: char*;
?
<binary>保留在binary: bool;
?
<optarg[0]>是-b參數(shù)的第一個(gè)字符,保存在label: char中。
?
好了,下面就有了打開設(shè)備節(jié)點(diǎn)時(shí)的參數(shù):
?
dev->fd = open(dev->device, mode);
?
dev->device根據(jù)-b的參數(shù)可能為“/dev/log/main”,“/dev/log/event”或“/dev/log/radio”;
?
mode缺省時(shí)為O_RDONLY,讀取。只要在運(yùn)行l(wèi)ogcat時(shí),用了-c參數(shù)清除log時(shí)才以O(shè)_WRONLY打開。
?
而打開文件的文件操作符保存在log_device_t的fd域中,用于后續(xù)的操作。
?
?
?
獲取Log的操作都是在readLogLines(log_device_t* devices)中實(shí)現(xiàn)的。
?
?
?
因?yàn)閘ogcat可能會(huì)同時(shí)操作多個(gè)Buffer,而read()會(huì)阻塞讀取進(jìn)程,對(duì)其他Buffer的讀取就不能進(jìn)行,所以這里用select()來判斷可讀取的Buffer。
?
二、select選取可讀取的Buffer
?
Logcat把log_device_t中的所有的buffer的文件操作符dev->fd,都放在readset中[line#7],做為select()的里的<readfds: fd_set*>讀參數(shù),來獲取可讀取的Buffer。這樣當(dāng)任何一個(gè)Buffer上有LOG數(shù)據(jù)時(shí),select()都會(huì)返回。當(dāng)然等待過程中也忽略掉其他signal的影響。相應(yīng)的代碼如下:
?
?????? 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()返回之后,通過循環(huán)判定dev->fd是否在readset里被設(shè)置(FD_ISSET)[line#3],知道哪個(gè)log buffer里已經(jīng)有數(shù)據(jù)了。
?
??????? 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]已經(jīng)有數(shù)據(jù)的LOG Buffer的文件操作符dev->fd就可得到新到來的log了。
?
?
?
應(yīng)用程序logcat中已經(jīng)獲取了LOG信息,接下來對(duì)數(shù)據(jù)的處理就都可以在這里進(jìn)行了,可以過濾,寫文件,格式化輸入等操作。詳細(xì)的logcat的命令參數(shù)可參見Android的Logcat命令詳解.
(6)c/c++域使用LOG
c/c++本地庫(kù)中實(shí)現(xiàn)LOG輸出
?
通過前面的文章知道Android的Java中通過android.util.Log輸出Log信息,那Android的本地c/c++程序能不能也通過這樣的機(jī)制來記錄Log呢?再回頭看Log現(xiàn)有的c/c++的本地實(shí)現(xiàn),答案當(dāng)然是肯定的,而且是相當(dāng)簡(jiǎn)單。Android直接在頭文件(system/core/include/cutils/log.h)里定義了一些宏就可以很好的實(shí)現(xiàn)了。
?
因?yàn)?#xff0c;LOG分了VERBOSE/DEBUG/INFO/WARN/ERROR/ASSERT等類別,簡(jiǎn)單起見,以DEBUG為例的實(shí)現(xiàn)來說明。
?
#ifndef LOGD
#define LOGD(...) LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#endif
?
#ifndef LOGD_IF
#define LOGD_IF(cond, ...) \
??? ( (CONDITION(cond)) \
??? ? LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__) \
??? : (void)0 )
#endif
?
#ifndef LOG
#define LOG(priority, tag, ...) \
??? LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
#endif
?
#ifndef LOG_PRI
#define LOG_PRI(priority, tag, ...)???????????????????????????????????? \
??? ({????????????????????????????????????????????????????????????????? \
?????? if (((priority == ANDROID_LOG_VERBOSE) && (LOG_NDEBUG == 0)) ||? \
?????????? ((priority == ANDROID_LOG_DEBUG) && (LOG_NDDEBUG == 0))? ||? \
?????????? ((priority == ANDROID_LOG_INFO) && (LOG_NIDEBUG == 0))?? ||? \
??????????? (priority == ANDROID_LOG_WARN)????????????????????????? ||? \
??????????? (priority == ANDROID_LOG_ERROR)?????????????????? ??????||? \
??????????? (priority == ANDROID_LOG_FATAL))??????????????????????????? \
??????????????? (void)android_printLog(priority, tag, __VA_ARGS__);???? \
??? })
#endif
?
#define android_printLog(prio, tag, fmt...) \
__android_log_print(prio, tag, fmt)
?
?
而這一系列宏,最后還是用到了函數(shù)__android_log_print()
?
int __android_log_print(int prio, const char *tag, const char *fmt, ...)
{
??? va_list ap;
??? char buf[LOG_BUF_SIZE];
?
??? va_start(ap, fmt);
??? vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
??? va_end(ap);
?
??? return __android_log_write(prio, tag, buf);
}
?
這里還是調(diào)到了函數(shù)__android_log_write()。這個(gè)函數(shù)應(yīng)該很熟悉吧,正是前文敘及的c/c++本地函數(shù)實(shí)現(xiàn)寫設(shè)備文件的地方。
?
?
?
c/c++程序中記錄Log的做法
?
要在c/c++中記錄Log通常的做法是:
?
定義自己的TAG_LOG宏;包含頭文件log.h;然后在需要記錄Log的地方直接用LOGV/LOGD/LOGI/LOGW/LOGE即可。
?
比如,文件lights.c中就在開頭這樣寫,
?
#define LOG_TAG "lights"
#include <cutils/log.h>
?
?
然后在該文件的后續(xù)部分,大量的用了LOGV/LOGE, etc來記錄LOG。
轉(zhuǎn)載于:https://www.cnblogs.com/fwycmengsoft/archive/2011/11/21/2257183.html
總結(jié)
以上是生活随笔為你收集整理的解读Android LOG机制的实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 新闻视频 36:整合首页 用到 Repe
- 下一篇: SQL 2005清除事务日志