Android6.0 Log的工作机制
Android6.0log新機制
Android6.0后Android 日志系統做了很大的改變,但是對于應用層改變是透明的,原因是由于日志系統只是針對底層做了相應改變。之前的系統通過讀寫設備文件的方式記錄Android系統日志,而現在主要使用socket進程間通信讀寫日志。醉心于技術的你苦于網絡上沒有詳細介紹Android6.0日志的文章。通過本文你將了解到整個Android6.0的日志抓取原理,并能理解為什么會在logcat時會出現日志漏抓,日志抓取停止的問題。本文針對Android日志源碼進行宏觀上進行了分析,讀者可以通過該文章輕松的看懂整個Android6.0的log抓取機制。本文花費了我將近兩個星期的時間寫作,希望讀者們轉載的時候注明出處。
pdf文檔:xuyusong/AndroidLog
聯系我:xuyusong1109@gmail.com
Logcat用法
Android為我們提供了一個十分方便的命令行工具來讀日志。Logcat 是一個命令行工具,用于轉儲系統消息日志,其中包括設備引發錯誤時的堆疊追蹤以及從您的應用使用?Log?類編寫的消息。在Android6.0中logcat又增加了幾個命令來管理日志。接下下來詳細介紹一下logcat的各個命令的用法。
命令行語法
[adb] logcat [<option>] ... [<filter-spec>] ...
您可以 adb 命令的形式運行 logcat,或在模擬器或所連接設備的 shell 提示符中直接運行。若要使用 adb 查看日志輸出,請導航到 SDK platform-tools/ 目錄并執行:
$ adb logcat啟動 logcat
以下是通過 ADB shell 運行 logcat 的一般用法:
[adb] logcat [<option>] ... [<filter-spec>] ...您可以從開發計算機或通過模擬器/設備實例中的遠程 adb shell 使用 logcat 命令。在開發計算機中查看日志輸出可使用
$ adb logcat從遠程 adb shell 查看日志輸出可使用
# logcat選項
下表介紹的是 logcat 的命令行選項。
-c
清除(刷新)整個日志并退出。
-d
將日志轉儲到屏幕并退出。
-f <filename>
將日志消息輸出寫入 <filename>。默認值為 stdout。
-g
打印指定日志緩沖區的大小并退出。
-n <count>
將已旋轉日志的最大數量設置為 <count>。默認值為 4。 需要使用 -r 選項。
-r <kbytes>
每輸出 <kbytes> 時旋轉日志文件。默認值為 16。需要使用 -f 選項。
-s
將默認過濾器規則設為靜默式。
-v <format>
設置日志消息的輸出格式。默認值為 brief 格式有關支持的格式列表。
-t <count>
打印最近的<count> 條日志數據(Android6.0新)
-t <time>
打印特定時間點的日志(Android6.0新)
-g
打印出環形緩沖區的大小并推出(Android6.0新)
-L
打印上一次重啟的日志信息(Android6.0新)
-b <buffer>
請求環形緩沖區,'main','system','radio',
'events','crash '或'all'。 多個-b參數
允許和結果交錯。 默認值為
-b main –b system –b crash。(Android6.0新)
-B
輸出為二進制文件(Android6.0新)
-S
輸出統計日志的情況(Android6.0新)
-G <size>
設置環形緩沖區的日志大小,可以添加后綴用K或者M(Android6.0新)
-p
打印設置的白名單和黑名單(Android6.0新)
-P <list>
設置黑名單和白名單(Android6.0新)
過濾日志輸出
每個 Android 日志消息都有與其關聯的標記和優先級。
· 日志消息的標記是一個簡短的字符串,其表示消息所源自的系統組件(例如,“View”代表視圖系統)。
· 優先級由以下某個字符值表示(按從最低到最高優先級的順序排列):
o V — 詳細(最低優先級)
o D — 調試
o I — 信息
o W — 警告
o E — 錯誤
o F — 致命
o S — 靜默(最高優先級,不會打印任何內容)
通過運行 logcat 并觀察每條消息的前兩列,您可以獲取系統中使用的標記列表及優先級,格式為 <priority>/<tag>。
下面是 logcat 輸出的一個示例,其表明消息與優先級“I”和標記“ActivityManager”相關:
I/ActivityManager( 585): Starting activity: Intent { action=android.intent.action...}若要將日志輸出降低到可管理的水平,您可以使用過濾器表達式限制日志輸出。過濾器表達式允許您向系統表明您感興趣的標記-優先級組合—系統針對指定的標記阻止其他消息。
過濾器表達式遵循 tag:priority ... 這個格式,其中 tag 表示感興趣的標記,priority 表示將該標記報告的最低優先級。將優先級等于或高于指定優先級的標記的消息寫入日志。您可以在一個過濾器表達式中提供任意數量的 tag:priority 規則。一系列規則使用空格分隔。
下面是一個過濾器表達式的示例,該表達式將阻止除了帶有標記“ActivityManager”、優先級等于或高于“信息”的日志消息以及帶有標記“MyApp”、優先級等于或高于“調試”的日志消息外的所有其他日志消息。
adb logcat ActivityManager:I MyApp:D *:S上述表達式中最后一個元素 *:S 將所有標記的優先級設為“靜默”,從而確保系統僅顯示帶有“ActivityManager”和“MyApp”標記的日志消息。使用 *:S 可有效地確保日志輸出受限于您已明確指定的過濾器 — 它允許過濾器充當日志輸出的“白名單”。
以下過濾器表達式顯示所有標記上優先級等于或高于“警告”的所有日志消息:
adb logcat *:W如果您從開發計算機運行 logcat(相對于在遠程 adb shell 運行它),您也可以通過導出環境變量 ANDROID_LOG_TAGS 的值設置默認過濾器表達式:
export ANDROID_LOG_TAGS="ActivityManager:I MyApp:D *:S"請注意,如果您從遠程 shell 或使用 adb shell logcat 運行 logcat,系統不會將 ANDROID_LOG_TAGS 過濾器導出到模擬器/設備實例。
控制日志輸出格式
除標記和優先級外,日志消息還包含許多元數據字段。您可以修改消息的輸出格式,以便它們可顯示特定的元數據字段。為此,您可以使用 -v 選項,并指定下面列出的支持的輸出格式之一。
· brief — 顯示優先級/標記以及發出消息的進程的 PID(默認格式)。
· process — 僅顯示 PID。
· tag — 僅顯示優先級/標記。
· raw — 顯示原始日志消息,不顯示其他元數據字段。
· time — 顯示日期、調用時間、優先級/標記以及發出消息的進程的 PID。
· threadtime — 顯示日期、調用時間、優先級、標記以及發出消息的線程的 PID 和 TID。
· long — 顯示所有元數據字段,并使用空白行分隔消息。
啟動 logcat 時,您可以使用 -v 選項指定您需要的輸出格式:
[adb] logcat [-v <format>]下面的例子展示如何生成 thread 輸出格式的消息:
adb logcat -v thread請注意,使用 -v 選項,您只能指定一個輸出格式。
查看日志緩沖區
Android 日志系統保留日志消息的多個循環緩沖區,而不是發送到默認循環緩沖區的所有日志消息。如需查看其他日志消息,您可以使用 -b 選項運行 logcat 命令,以請求查看循環緩沖區。您可以查看下列備用緩沖區的任意一個:
· radio — 查看包含無線裝置/電話相關消息的緩沖區。
· events — 查看包含事件相關消息的緩沖區。
· main — 查看主要日志緩沖區(默認值)包括了system和crash系統默認
· kernel— 查看內核日志
以下是 -b 選項的用法:
[adb] logcat [-b <buffer>]以下示例展示如何查看包含無線裝置和電話消息的日志緩沖區。
adb logcat -b radio查看 stdout 和 stderr
默認情況下,Android 系統將 stdout 和 stderr(System.out 和 System.err)輸出發送到 /dev/null。在運行 Dalvik VM 的進程中,您可以讓系統將輸出的副本寫入日志文件。在此情況下,系統使用日志標記 stdout 和 stderr(優先級都是 I)將消息寫入日志。
要通過此方式路由輸出,您需要停止運行的模擬器/設備實例,然后使用 shell 命令 setprop 以啟用輸出重定向。下面是具體做法:
$ adb shell stop $ adb shell setprop log.redirect-stdio true $ adb shell start系統保留此設置,直至您終止模擬器/設備實例。若要在模擬器/設備實例上將此設置用作默認值,您可以在設備上向 /data/local.prop 添加一個條目。
通過代碼記錄日志
Log?類允許您在 logcat 工具中顯示的代碼中創建日志條目。常用的日志記錄方法包括:
·?Log.v(String, String)(詳細)
·?Log.d(String, String)(調試)
·?Log.i(String, String)(信息)
·?Log.w(String, String)(警告)
·?Log.e(String, String)(錯誤)
例如,使用以下調用:
Log.i("MyActivity", "MyClass.getView() — get item number " + position);logcat 輸出類似于如下:
I/MyActivity( 1557): MyClass.getView() — get item number 1Socket客戶端的讀寫
Logcat是一個C++可執行程序,其位于Android系統中/system/bin/logcat,他是整個日志系統讀取的入口,logcat入口是下面的main函數:
/system/core/logcat/logcat.cpp
ret = getopt(argc, argv, ":cdDLt:T:gG:sQf:r:n:v:b:BSpP:K"); 00572: switch(ret) { 00573: case 's': 00574: // default to all silent 00575: android_log_addFilterRule(g_logformat, "*:s"); 00576: break; 00578: case 'c': 00579: clearLog = 1; 00580: mode |= ANDROID_LOG_WRONLY; 00581: break; 00587: case 'd': 00588: mode |= ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK; 00589: break; 00591: case 't': 00594: case 'T': 00628: // MStar Android Patch Begin 00629: case 'K': 00630: android::g_kmsg = 1; 00631: break; 00632: // MStar Android Patch End 00633: …………. 00831: 00832: default: 00833: logcat_panic(true, "Unrecognized Option %c\n", optopt); 00834: break;etopt被用來解析命令行選項參數。
#include <unistd.h>
extern char *optarg; //選項的參數指針
extern int optind, //下一次調用getopt的時,從optind存儲的位置處重新開始檢查選項。
extern int opterr, //當opterr=0時,getopt不向stderr輸出錯誤信息。
extern int optopt; //當命令行選項字符不包括在optstring中或者選項缺少必要的參數時,該選項存儲在optopt 中,getopt返回'?’。
int getopt(int argc, char * const argv[], const char *optstring);
調用一次,返回一個選項。 在命令行選項參數再也檢查不到optstring中包含的選項時,返回-1,同時optind儲存第一個不包含選項的命令行參數。
首先說一下什么是選項,什么是參數。
1.單個字符,表示選項,
2.單個字符后接一個冒號:表示該選項后必須跟一個參數。參數緊跟在選項后或者以空格隔開。該參數的指針賦給optarg。
3 單個字符后跟兩個冒號,表示該選項后必須跟一個參數。參數必須緊跟在選項后不能以空格隔開。該參數的指針賦給optarg。(這個特性是GNU的擴張)。
獲取得到命令行參數后對參數進行解析。不同的參數給予不同的操作模式。比如說logcat –b main,b即為選項,main參數。
getopt(argc,?argv, ":cdDLt:T:gG:sQf:r:n:v:b:BSpP:K")有冒號即代表其后必須接參數
讀取日志
Android6.0通過使用socket進行日志的讀寫操作,其中socket又包含了客戶端和服務端,作為logcat是讀取日志的通道,自然需要實現的socket通信的客戶端。
重要結構體
要了解其中log日志怎么在各個程序傳送到控制臺的,必須了解整個日志讀寫全過程。首先我們的日志將會被封裝成什么樣的結構發送到客戶端,同時客戶端將會發送怎么樣的結構去請求我們需要的日志的。
00049: struct log_device_t { 00050: const char* device; 00051: bool binary; 00052: struct logger *logger; 00053: struct logger_list *logger_list; 00054: bool printed; 00056: log_device_t* next; 00058: log_device_t(const char* d, bool b) { 00059: device = d; 00060: binary = b; 00061: next = NULL; 00062: printed = false; 00063: logger = NULL; 00064: logger_list = NULL; 00065: } };不難發現,上面的結構體是一個組成鏈表的結構體,該鏈表是用來記錄需要處理的日志設備,每個日志設備又對應不同的內存,其中的命名為下面的數組:
\system\core\liblog\log_read.c
00205: static const char *LOG_NAME[LOG_ID_MAX] = { 00206: [LOG_ID_MAIN] = "main", 00207: [LOG_ID_RADIO] = "radio", 00208: [LOG_ID_EVENTS] = "events", 00209: [LOG_ID_SYSTEM] = "system", 00210: [LOG_ID_CRASH] = "crash", 00211: [LOG_ID_KERNEL] = "kernel", 00212: };以上的數據便是記錄在一個log_id_t中,便是如下這個結構體中的log_id_t中,這個結構體是組成logger_list的一個節點,因為一條命令中可能指定多個緩沖區的數據:例如logcat –b main kernel
\system\core\liblog\log_read.c
00255: struct logger { 00256: struct listnode node; 00257: struct logger_list *top; 00258: log_id_t id; 00259: };如下便是logger_list。
list_add_tail(&logger_list->node, &logger->node);
\system\core\liblog\log_read.c
00246: struct logger_list { 00247: struct listnode node; 00248: int mode; 00249: unsigned int tail; 00250: log_time start; 00251: pid_t pid; 00252: int sock; 00253: };Logger_list便記錄了這個設備需要進行操作的pid、socket、mode(操作的模式)。其中不同的操作數又對應不同的操作模式:比如
00587: case 'd': 00588: mode |= ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK;上面定義的模式 便是使用只讀模式和非阻塞模式讀取日志。阻塞和非阻塞模式請自行百度了解,這里不展開。
如上只是了解了其中一個參數-b中讀取不同緩沖區中數據的例子,其他參數比較簡單,不做展開。
socket讀取日志
那么拿到對應的參數和操作方法后,我們該如何去讀取對應的日志呢,答案就是一個死循環。
\system\core\logcat\logcat.cpp
01071: while (1) { 01072: // MStar Android Patch Begin 01073: if (android::g_kmsg) { 01074: int ret = 0; 01075: char kernel_buffer[1024]; 01076: if ((ret = klogctl(KLOG_SIZE_UNREAD, kernel_buffer, sizeof(kernel_buffer))) > 0) { 01077: if ((ret = klogctl(KLOG_READ, kernel_buffer, sizeof(kernel_buffer))) > 0) { 01078: if (android::printKernelBuffer(kernel_buffer, ret) < 0) { 01079: perror("write kernel log error"); 01080: } 01081: } 01082: } 01083: } 01084: // MStar Android Patch End 01086: struct log_msg log_msg; 01087: log_device_t* d; 01088: int ret = android_logger_list_read(logger_list, &log_msg); 01094: if (ret < 0) { 01095: if (ret == ‐EAGAIN) { 01096: break; 01097: } 01098: // MStar Android Patch Begin 01099: if ((connectRetryCount < 10) && (ret == ‐ECONNREFUSED)) { 01100: connectRetryCount++; 01101: printf("read: Connection refused , retry 10 times(%d).", connectRetryCount); 01102: usleep(100000); 01103: continue; 01104: } 01105: // MStar Android Patch End 01107: if (ret == ‐EIO) { 01108: logcat_panic(false, "read: unexpected EOF!\n"); 01109: } 01116: for(d = devices; d; d = d‐>next) { 01117: if (android_name_to_log_id(d‐>device) == log_msg.id()) { 01118: break; 01119: } 01120: } 01136: } ? end while 1 ?該循環中主要是由android_logger_list_read(logger_list, &log_msg);讀取,并將結果寫入到了log_msg中返回。這個讀取過程是怎樣的呢:
00739: /* Read from the selected logs */ 00740: int android_logger_list_read(struct logger_list *logger_list, 00741: struct log_msg *log_msg) 00742: { 00743: int ret, e; 00744: struct logger *logger; 00745: struct sigaction ignore; 00746: struct sigaction old_sigaction; 00747: unsigned int old_alarm = 0; 00764: if (logger_list‐>sock < 0) { 00765: char buffer[256], *cp, c; 00767: int sock = socket_local_client("logdr", 00768: ANDROID_SOCKET_NAMESPACE_RESERVED,SOCK_SEQPACKET); 00774: return sock; 00775: } 00776: 00777: strcpy(buffer, 00778: (logger_list‐>mode & ANDROID_LOG_NONBLOCK) ? "dumpAndClose" : "stream"); 00779: cp = buffer + strlen(buffer); . . . . . . 00816: if (logger_list‐>mode & ANDROID_LOG_NONBLOCK) { 00817: /* Deal with an unresponsive logd */ 00818: //sigaction是一個函數,可以用來查詢或設置信號處理方式。 00819: sigaction(SIGALRM, &ignore, &old_sigaction); 00820: old_alarm = alarm(30); 00821: } 00822: ret = write(sock, buffer, cp ‐ buffer); 00832: if (ret <= 0) { 00833: close(sock); 00834: if ((ret == ‐1) && e) { 00835: return ‐e; 00836: } 00837: if (ret == 0) { 00838: return ‐EIO; 00839: } 00840: return ret; 00841: } 00842: 00843: logger_list‐>sock = sock; 00844: } ? end if logger_list‐>sock<0 ? 00846: ret = 0; 00847: while(1) { 00848: memset(log_msg, 0, sizeof(*log_msg)); 00849: 00850: if (logger_list‐>mode & ANDROID_LOG_NONBLOCK) { 00851: /* particularily useful if tombstone is reporting for logd */ 00852: sigaction(SIGALRM, &ignore, &old_sigaction); 00853: old_alarm = alarm(30); 00854: } 00855: /* NOTE: SOCK_SEQPACKET guarantees we read exactly one full entry */ 00856: ret = recv(logger_list‐>sock, log_msg, LOGGER_ENTRY_MAX_LEN, 0); 00863: alarm(old_alarm); 00874: logger_for_each(logger, logger_list) { 00875: if (log_msg‐>entry.lid == logger‐>id) { 00876: return ret; 00877: } 00878: } 00879: } ? end while 1 ? 00880: /* NOTREACH */ 00881: return ret;上面便是,整個讀取日志的過程,其主要是新建一個socket連接,通過socket發送請求參數,服務端返回日志的形式進行。
分為以下三步:
第一步:新建連接:
int?sock?=socket_local_client("logdr",ANDROID_SOCKET_NAMESPACE_RESERVED,SOCK_SEQPACKET);返回一個socket句柄。并且設置連接屬性,SOCK_SEQPACKET使用數據包的形式進行通信,能確保其傳輸的可靠性。
第二步:設置參數:
strcpy(buffer, (logger_list‐>mode & ANDROID_LOG_NONBLOCK) ? "dumpAndClose" : "stream");
ret = write(sock, buffer, cp ‐ buffer);
這兩句話就是往服務器設置我們需要讀取日志的方式。我們使用非阻塞,模式讀取日志。除此之外我們還應根據需要設置其讀取的進程、時間、緩沖區等等參數。
第三步:讀取日志:
/* NOTE: SOCK_SEQPACKET guarantees we read exactly one full entry */
ret = recv(logger_list‐>sock,?log_msg, LOGGER_ENTRY_MAX_LEN, 0);
從注釋中可以看到,我們使用SOCK_SEQPACKET模式傳輸能確保我們能得到一個完整日志實體。通過recv函數可以將套接字中的數據讀取出來。并存放到對用的buff中即log_msg。
如上便是整個日志的讀取流程。如下流程圖所示:
圖一 客戶端讀取日志
日志寫入過程
當然是用工Log類的讀者都知道,我們在Java或者Android代碼中簡單的使用Log.i(“myTAG”,”this is my log”);類似這樣的例子就可以往系統中寫入日志,那么這些日志被寫到哪里了,如何寫入的呢。
首先我們看framework層中是如何定義這些接口的:
\frameworks\base\core\java\android\util\Log.java
public static int i(String tag, String msg) { return println_native(LOG_ID_MAIN, INFO, tag, msg); }首先在framework中調用本地方法?println_native(LOG_ID_MAIN, INFO,?tag,?msg);,這個方法被注冊在Java虛擬機中:
\frameworks\base\core\jni\android_util_Log.cpp
00114: static JNINativeMethod gMethods[] = { 00115: /* name, signature, funcPtr */ 00116: { "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable }, 00117: { "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) 00117: android_util_Log_println_native }, 00118: };在本地方法中注冊println_native為android_util_Log_println_native方法,其中具體實現就調用了寫日志的方法:
\frameworks\base\core\jni\android_util_Log.cpp
00082: static jint android_util_Log_println_native(JNIEnv* env, jobject clazz, 00083: jint bufID, jint priority, jstring tagObj, jstring msgObj) 00101: . . . . . . . . 00102: int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg); 00103: 00104: if (tag != NULL) 00105: env‐>ReleaseStringUTFChars(tagObj, tag); 00106: env‐>ReleaseStringUTFChars(msgObj, msg); 00107: 00108: return res; 00109: } ? end android_util_Log_println_native ?其中主要的方法便是__android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg)他調用了底層寫日志的方法。
圖二 各個緩沖區對應類
其中bufID確定使用哪個緩沖區(main、system等)中的數據,priority代表日志的優先級(I、E等),tag表示標簽,msg表示寫入的日志。不同緩沖區的Log對應不同的類,比如main對應的類為Log而system對應的為Slog,在頂層實現方面,Android6.0沒有改變,這里不過多介紹。我們詳細看一下__android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);的實現原理。其中先調用了__write_to_log_initialize初始化連接,然后調用__write_to_log_daemon往服務端寫日志。
__write_to_log_initialize函數:
00094: static int __write_to_log_initialize() 00095: { 00098: #if FAKE_LOG_DEVICE 00100: for (i = 0; i < LOG_ID_MAX; i++) { 00101: char buf[sizeof("/dev/log_system")]; 00102: snprintf(buf, sizeof(buf), "/dev/log_%s", android_log_id_to_name(i)); 00103: log_fds[i] = fakeLogOpen(buf, O_WRONLY); 00104: } 00106: #else 00112: if (logd_fd < 0) { 00113: i = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0)); 00114: if (i < 0) { 00115: ret = ‐errno; 00144: } ? end if i<0 ? else if (TEMP_FAILURE_RETRY(fcntl(i, F_SETFL, O_NONBLOCK)) < 0) { 00145: ret = ‐errno; 00146: close(i); 00147: } else { 00148: struct sockaddr_un un; 00149: memset(&un, 0, sizeof(struct sockaddr_un)); 00155: un.sun_family = AF_UNIX; 00156: strcpy(un.sun_path, "/dev/socket/logdw"); 00158: if (TEMP_FAILURE_RETRY(connect(i, (struct sockaddr *)&un, 00159: sizeof(struct sockaddr_un))) < 0) { 00160: ret = ‐errno; 00161: close(i); 00162: } else { 00163: logd_fd = i; 00164: } 00165: }其中使用到很多進程通信相關的機制,如下做簡單介紹:
一、創建socket流程
(1)創建socket,類型為AF_LOCAL或AF_UNIX(地址族),PF_UNIX或者PF_LOCAL(協議族),均表示用于進程通信:
創建套接字需要使用 socket 系統調用,其原型如下:
int socket(int domain, int type, int protocol);
其中,domain 參數指定協議族,對于本地套接字來說,其值須被置為 AF_UNIX 枚舉值;type 參數指定套接字類型,protocol 參數指定具體協議;type 參數可被設置為 SOCK_STREAM(流式套接字)或 SOCK_DGRAM(數據報式套接字),protocol 字段應被設置為 0;其返回值為生成的套接字描述符。
對于本地套接字來說,流式套接字(SOCK_STREAM)是一個有順序的、可靠的雙向字節流,相當于在本地進程之間建立起一條數據通道;數據報式套接字(SOCK_DGRAM)相當于單純的發送消息,在進程通信過程中,理論上可能會有信息丟失、復制或者不按先后次序到達的情況,但由于其在本地通信,不通過外界網絡,這些情況出現的概率很小。
二、命名socket。
SOCK_STREAM 式本地套接字的通信雙方均需要具有本地地址,其中服務器端的本地地址需要明確指定,指定方法是使用 struct sockaddr_un 類型的變量。
struct sockaddr_un {sa_family_t sun_family; /* AF_UNIX */char sun_path[UNIX_PATH_MAX]; /* 路徑名 */ };這里面有一個很關鍵的東西,socket進程通信命名方式有兩種。一是普通的命名,socket會根據此命名創建一個同名的socket文件,客戶端連接的時候通過讀取該socket文件連接到socket服務端。這種方式的弊端是服務端必須對socket文件的路徑具備寫權限,客戶端必須知道socket文件路徑,且必須對該路徑有讀權限。
另外一種命名方式是抽象命名空間,這種方式不需要創建socket文件,只需要命名一個全局名字,即可讓客戶端根據此名字進行連接。后者的實現過程與前者的差別是,后者在對地址結構成員sun_path數組賦值的時候,必須把第一個字節置0,即sun_path[0] = 0,下面用代碼說明:
第一種方式:
1. //name the server socket 2. server_addr.sun_family = AF_UNIX; 3. strcpy(server_addr.sun_path," /dev/socket/logdw "); 4. server_len = sizeof(struct sockaddr_un); 5. client_len = server_len;第二種方式:
1. //name the socket 2. server_addr.sun_family = AF_UNIX; 3. strcpy(server_addr.sun_path, SERVER_NAME); 4. server_addr.sun_path[0]=0; 5. //server_len = sizeof(server_addr); 6. server_len = strlen(SERVER_NAME) + offsetof(struct sockaddr_un, sun_path);1、獲取文件的flags,即open函數的第二個參數:
flags = fcntl(fd,F_GETFL,0);
2、設置文件的flags:
fcntl(fd,F_SETFL,flags);
3、增加文件的某個flags,比如文件是阻塞的,想設置成非阻塞:
flags = fcntl(fd,F_GETFL,0);
flags |= O_NONBLOCK;
fcntl(fd,F_SETFL,flags);
4、取消文件的某個flags,比如文件是非阻塞的,想設置成為阻塞:
flags = fcntl(fd,F_GETFL,0);
flags &= ~O_NONBLOCK;
fcntl(fd,F_SETFL,flags);
有了上述基本知識,我們可以明顯的看出我們log讀取方式就是使用的本地進程間通信的方式,而且采取協議SOCK_DGRAM,使用數據報的方式,而在建立連接的過程中,采用的第二種方式,通過文件的方式與服務端取得連接,細心的讀者可能會發現在讀取日志的時候,獲取連接是使用這么一段代碼:
\system\core\liblog\log_read.c
00112: case ANDROID_SOCKET_NAMESPACE_RESERVED: 00113: namelen = strlen(name) + strlen(ANDROID_RESERVED_SOCKET_PREFIX); 00114: /* unix_path_max appears to be missing on linux */ 00115: if (namelen > sizeof(*p_addr) 00116: ‐ offsetof(struct sockaddr_un, sun_path) ‐ 1) { 00117: goto ?error; 00118: }使用的是第一種方式與服務器建立連接的,那究竟為什么在讀取的時候不和寫入的時候用同一種方式呢,由于日志寫入的過程是在程序中進行的,然而日志的讀取是通過logcat命令去獲取的,當你用普通用戶去運行命令時,如果對/dev/socket/logdw下的文件沒有讀寫的權限,就不能獲取日志了,因此在讀取日志的過程中使用的第二種方式。相關參數初始化完成后,調用__write_to_log_daemon寫日志。
__write_to_log_daemon函數:
\system\core\liblog\logd_write.c
00172: static int __write_to_log_daemon(log_id_t log_id, struct iovec *vec, size_t nr) 00173: { 00174: ssize_t ret; 00175: #if FAKE_LOG_DEVICE 00176: int log_fd; 00177: 00178: if (/*(int)log_id >= 0 &&*/ (int)log_id < (int)LOG_ID_MAX) { 00179: log_fd = log_fds[(int)log_id]; 00180: } else { 00181: return ‐EBADF; 00182: } 00183: do { 00184: ret = fakeLogWritev(log_fd, vec, nr); 00185: if (ret < 0) { 00186: ret = ‐errno; 00187: } 00188: } while (ret == ‐EINTR); 00189: #else 00190: static const unsigned header_length = 2; 00191: struct iovec newVec[nr + header_length]; 00192: android_log_header_t header; 00193: android_pmsg_log_header_t pmsg_header; 00194: struct timespec ts; 00195: size_t i, payload_size; 00196: static uid_t last_uid = AID_ROOT; /* logd *always* starts up as AID_ROOT */ 00197: static pid_t last_pid = (pid_t) ‐1; 00198: static atomic_int_fast32_t dropped; 00266: header.id = log_id; 00267: . . . . 00300: /* 00301: * The write below could be lost, but will never block. 00302: * 00303: * To logd, we drop the pmsg_header 00304: * 00305: * ENOTCONN occurs if logd dies. 00306: * EAGAIN occurs if logd is overloaded. 00307: */ 00308: ret = TEMP_FAILURE_RETRY(writev(logd_fd, newVec + 1, i ‐ 1)); 00309: if (ret < 0) { 00310: ret = ‐errno; 00311: if (ret == ‐ENOTCONN) { 00315: close(logd_fd); 00316: logd_fd = ‐1; 00317: ret = __write_to_log_initialize(); 00326: ret = TEMP_FAILURE_RETRY(writev(logd_fd, newVec + 1, i ‐ 1)); 00327: if (ret < 0) { 00328: ret = ‐errno; 00329: } 00330: } ? end if ret==‐ENOTCONN ? 00331: } ? end if ret<0 ? 00332: 00333: if (ret > (ssize_t)sizeof(header)) { 00334: ret ‐= sizeof(header); 00335: } else if (ret == ‐EAGAIN) { 00336: atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed); 00337: } 00338: #endif 00340: return ret; 00341: } ? end __write_to_log_daemon ?當然剛開始,我們都只是出事化一些參數,這里不做分析,我們關心的只是一些重要的點,首先在初始化的時候我們回去到了一個socket?logd_fd句柄,他是關于進程logd的一個句柄,這個進程便是socket進程,用于守護整個日志系統的通信,當然這個進程是由服務端啟動和維護的,之后在講解服務端的時候會具體講解到。對應于讀取函數recv(logger_list‐>sock,?log_msg, LOGGER_ENTRY_MAX_LEN, 0);在寫入日志時 我們用到的是writev(logd_fd, newVec + 1, i ‐ 1);用于向服務端寫入相應數據。其中需要注意的一點:函數返回
由于使用的是SOCK_DGRAM數據報的形式,所有的寫入操作都是可能丟失的,但是這個函數不會阻塞,當logd進程如果被意外殺死,返回ENOTCONN,表示連接錯誤,并且重新初始化連接,返回EAGAIN表示socket過載,至于為什么會過載,在服務端會詳細講解到,執行atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed);在單線程中刪除當前操作,這便是整個日志寫入過程。
流程如圖所示:
圖三 客戶端寫日志
Socket服務端的啟動和讀寫
講了半天只是講了客戶端,之前一直提到服務端,那么服務端是如何啟動的?啟動的時機是什么?
Socket服務啟動
Socket服務是一個logd服務,是在開機啟動的。
圖四 logd進程
開機時系統會先啟動Linux內核,在此過程中會先啟動init進程。該進程中,主要任務:1、創建文件系統(只創建啟動必須的,其他的在init.rc 創建)
2、屬性服務
3、init.rc文件解析
4、啟動服務
圖五 開機init進程
那么我們所說的socket服務端進程在什么地方啟動的呢,就在init.rc文件之中創建的:
Init.rc文件
啟動logd服務
on load_all_props_action
load_all_props
start logd
start logd-reinit
創建用于logcat連接服務端 socket服務,并且在/dev/socket下建立了logd、logdr、logdw三個文件。用于logcat用來連接。
service logd /system/bin/logdclass coresocket logd stream 0666 logd logdsocket logdr seqpacket 0666 logd logdsocket logdw dgram 0222 logd logd group root system那么我們說的如上所示的幾種模式創建的socket服務
SOCK_STREAM: 提供面向連接的穩定數據傳輸,即TCP協議。
SOCK_DGRAM: 使用不連續不可靠的數據包連接。
SOCK_SEQPACKET: 提供連續可靠的數據包連接。
SOCK_RAW: 提供原始網絡協議存取。
SOCK_RDM: 提供可靠的數據包連接。
SOCK_PACKET: 與網絡驅動程序直接通信。
而socket這個設備文件是在init進程中創建的:
\system\core\init\init.cpp
01033: int main(int argc, char** argv) { 01049: // Get the basic filesystem setup we need put together in the initramdisk 01050: // on / and then we'll let the rc file figure out the rest. 01051: if (is_first_stage) { 01052: mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); 01053: mkdir("/dev/pts", 0755); 01054: mkdir("/dev/socket", 0755); 01055: mount("devpts", "/dev/pts", "devpts", 0, NULL); 01056: mount("proc", "/proc", "proc", 0, NULL); 01057: mount("sysfs", "/sys", "sysfs", 0, NULL); 01058: }而在每次啟動服務的時候,都會通過init.cpp中的service_start方法,其中就有創建socket服務的方法:create_socket();其中的定義為:
\system\core\init\util.cpp
00091: int create_socket(const char *name, int type, mode_t perm, uid_t uid, 00092: gid_t gid, const char *socketcon) 00093: { 00094: struct sockaddr_un addr; 00098: if (socketcon) 00099: setsockcreatecon(socketcon); 00100: 00101: fd = socket(PF_UNIX, type, 0); 00110: memset(&addr, 0 , sizeof(addr)); 00111: addr.sun_family = AF_UNIX; 00112: snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s", 00113: name); 00119: } 00128: ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr)); 00129: if (ret) { 00131: goto ?out_unlink; 00132: } 00137: chown(addr.sun_path, uid, gid); 00138: chmod(addr.sun_path, perm); 00139: 00140: INFO("Created socket '%s' with mode '%o', user '%d', group '%d'\n", 00141: addr.sun_path, perm, uid, gid); 00143: return fd; 00145: out_unlink: 00146: unlink(addr.sun_path); 00147: out_close: 00148: close(fd); 00149: return ‐1; 00150: } ? end create_socket ?如上所示在init進程中創建了socket連接,addr.sun_family = AF_UNIX表示使用來進行進程間通信的。snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s",?name); ANDROID_SOCKET_DIR=”/dev/socket” 根據在init.rc文件中定義的logd、logdr、logdw三個socket,通過bind后就會在/dev/socket下新建這三個文件,提供給logcat連接服務端。除了構建這些參數外,還啟動了logd這個可執行文件。
圖六 socket服務啟動
這個文件是在system/core/logd下編譯,其中的Makefile是:
LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE:= logdLOCAL_SRC_FILES := \main.cpp \LogCommand.cpp \CommandListener.cpp \LogListener.cpp \LogReader.cpp \FlushCommand.cpp \LogBuffer.cpp \LogBufferElement.cpp \LogTimes.cpp \LogStatistics.cpp \LogWhiteBlackList.cpp \libaudit.c \LogAudit.cpp \LogKlog.cpp \event.logtagsLOCAL_SHARED_LIBRARIES := \libsysutils \liblog \libcutils \libutils# This is what we want to do: # event_logtags = $(shell \ # sed -n \ # "s/^\([0-9]*\)[ \t]*$1[ \t].*/-D`echo $1 | tr a-z A-Z`_LOG_TAG=\1/p" \ # $(LOCAL_PATH)/$2/event.logtags) # event_flag := $(call event_logtags,auditd) # event_flag += $(call event_logtags,logd) # so make sure we do not regret hard-coding it as follows: event_flag := -DAUDITD_LOG_TAG=1003 -DLOGD_LOG_TAG=1004LOCAL_CFLAGS := -Werror $(event_flag)include $(BUILD_EXECUTABLE)include $(CLEAR_VARS)LOCAL_MODULE := logpersist.start LOCAL_MODULE_TAGS := debug LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_MODULE_PATH := $(bin_dir) LOCAL_SRC_FILES := logpersist ALL_TOOLS := logpersist.start logpersist.stop logpersist.cat LOCAL_POST_INSTALL_CMD := $(hide) $(foreach t,$(filter-out $(LOCAL_MODULE),$(ALL_TOOLS)),ln -sf $(LOCAL_MODULE) $(TARGET_OUT)/bin/$(t);) include $(BUILD_PREBUILT)include $(call first-makefiles-under,$(LOCAL_PATH))上面講整個logd下的文件編譯成為了一個名為logd的可執行文件,然后我們在init.rc中以服務的方式去啟動它,如此整個socket服務端也就完全啟動。
服務端監聽器啟動
那么這個服務端會處理什么呢:
1、首先是能夠處理由logcat發送過來的控制臺命令,是通過logd文件標識的socket來就收和處理的。比如獲取緩沖區的大小,緩沖區的日志情況等等。
2、其次是能夠監聽來自客戶的讀日志的請求,并且處理返回相應的值。
3、最后是能夠監聽所有來自系統或者應用等程序中寫日志的請求。
啟動監聽:在logd的mian.cpp中啟動了三個監聽器,用來監聽上面的三個方面的請求。
\system\core\logd\main.cpp
00331: int main(int argc, char *argv[]) { 00332: int fdPmesg = ‐1; 00333: bool klogd = property_get_bool_svelte("logd.klogd"); 00334: if (klogd) { 00335: fdPmesg = open("/proc/kmsg", O_RDONLY | O_NDELAY); 00336: } 00337: fdDmesg = open("/dev/kmsg", O_WRONLY); 00339: // issue reinit command. KISS argument parsing. 00340: if ((argc > 1) && argv[1] && !strcmp(argv[1], "‐‐reinit")) { 00341: int sock = TEMP_FAILURE_RETRY( 00342: socket_local_client("logd", 00343: ANDROID_SOCKET_NAMESPACE_RESERVED, 00344: SOCK_STREAM)); 00345: if (sock < 0) { 00346: return ‐errno; 00347: } 00404: LastLogTimes *times = new LastLogTimes(); 00406: // LogBuffer is the object which is responsible for holding all 00407: // log entries. 00408: 00409: logBuf = new LogBuffer(times); 00410: 00411: signal(SIGHUP, reinit_signal_handler); 00412: 00413: if (property_get_bool_svelte("logd.statistics")) { 00414: logBuf‐>enableStatistics(); 00415: } 00417: // LogReader listens on /dev/socket/logdr. When a client 00418: // connects, log entries in the LogBuffer are written to the client. 00419: 00420: LogReader *reader = new LogReader(logBuf); 00421: if (reader‐>startListener()) { 00422: exit(1); 00423: } 00425: // LogListener listens on /dev/socket/logdw for client 00426: // initiated log messages. New log entries are added to LogBuffer 00427: // and LogReader is notified to send updates to connected clients. 00429: LogListener *swl = new LogListener(logBuf, reader); 00430: // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value 00431: if (swl‐>startListener(600)) { 00432: exit(1); 00433: } 00435: // Command listener listens on /dev/socket/logd for incoming logd 00436: // administrative commands. 00438: CommandListener *cl = new CommandListener(logBuf, reader, swl); 00439: if (cl‐>startListener()) { 00440: exit(1); 00441: } 00472: TEMP_FAILURE_RETRY(pause()); 00473: 00474: exit(0); 00475: } ? end main ?三個監聽器分別為
(一)LogReader *reader?=?new?LogReader(logBuf);
reader‐>startListener():監聽/dev/socket/logdr上的socket客戶端,每當有讀請求到時就返回log entries給客戶端。
(二) LogListener *swl?=?new?LogListener(logBuf, reader);
swl‐>startListener(600):監聽/dev/socket/logdw上的socket客戶端請求,每當有日志寫入時,就添加log entries 到LogBuffer中,而且通知logReader讀取日志。其中600表示負載,表示能同時處理600個來自客戶端的寫入請求,如果需要寫入的日志較多,可以適當的增加這個值,這個值的最大值在/proc/sys/net/unix/max_dgram_qlen文件中修改。
(三)CommandListener *cl?=?new?CommandListener(logBuf, reader, swl);
cl‐>startListener(): 監聽/dev/socket/logd來自客戶端的命令,用于管理客戶端的命令。其管理命令有如下這些:
\system\core\logd\CommandListener.cpp
00035: CommandListener::CommandListener(LogBuffer *buf, LogReader * /*reader*/, 00036: LogListener * /*swl*/) : 00037: FrameworkListener(getLogSocket()), 00038: mBuf(*buf) { 00039: // registerCmd(new ShutdownCmd(buf, writer, swl)); 00040: registerCmd(new ClearCmd(buf)); 00041: registerCmd(new GetBufSizeCmd(buf)); 00042: registerCmd(new SetBufSizeCmd(buf)); 00043: registerCmd(new GetBufSizeUsedCmd(buf)); 00044: registerCmd(new GetStatisticsCmd(buf)); 00045: registerCmd(new SetPruneListCmd(buf)); 00046: registerCmd(new GetPruneListCmd(buf)); 00047: registerCmd(new ReinitCmd()); 00048: }當然在客戶端也必須有對應的寫入操作:客戶端 只需每次調用send_log_msg就可以向服務端發送命令行指令,比如向服務端發送清除日志命令:
\system\core\liblog\log_read.c
00411: int android_logger_clear(struct logger *logger) 00412: { 00413: char buf[512]; 00414: 00415: if (logger‐>top‐>mode & ANDROID_LOG_PSTORE) { 00416: if (uid_has_log_permission(get_best_effective_uid())) { 00417: return unlink("/sys/fs/pstore/pmsg‐ramoops‐0"); 00418: }. . . . 00422: return check_log_success(buf, 00423: send_log_msg(logger, "clear %d", buf, sizeof(buf))); 00424: }客戶端發送指令:
\system\core\liblog\log_read.c
00283: static ssize_t send_log_msg(struct logger *logger, 00284: const char *msg, char *buf, size_t buf_size) 00285: { 00286: ssize_t ret; 00287: size_t len; 00290: int sock = socket_local_client("logd", ANDROID_SOCKET_NAMESPACE_RESERVED, 00291: SOCK_STREAM); 00296: if (msg) { 00297: snprintf(buf, buf_size, msg, logger ? logger‐>id : (unsigned) ‐1); 00298: } 00299: 00300: len = strlen(buf) + 1; 00301: ret = TEMP_FAILURE_RETRY(write(sock, buf, len)); 00305: 00306: len = buf_size; 00307: cp = buf; 00308: while ((ret = TEMP_FAILURE_RETRY(read(sock, cp, len))) > 0) { 00309: struct pollfd p;. . . . 00339: done: 00340: if ((ret == ‐1) && errno) { 00341: errno_save = errno; 00342: } 00343: close(sock); 00344: if (errno_save) { 00345: errno = errno_save; 00346: } 00347: return ret; 00348: } ? end send_log_msg ?具體的過程這里不多做解釋,與讀寫日志的流程一致。當然細心的讀者可能會發現,這個日志系統還有一個監聽器,專門使用來監聽內核日志的:
00443: // LogAudit listens on NETLINK_AUDIT socket for selinux 00444: // initiated log messages. New log entries are added to LogBuffer 00445: // and LogReader is notified to send updates to connected clients. 00446: 00447: bool auditd = property_get_bool("logd.auditd", true); 00448: 00449: LogAudit *al = NULL; 00450: if (auditd) { 00451: bool dmesg = property_get_bool("logd.auditd.dmesg", true); 00452: al = new LogAudit(logBuf, reader, dmesg ? fdDmesg : ‐1); 00453: } 00454: 00455: LogKlog *kl = NULL; 00456: if (klogd) { 00457: kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != NULL); 00458: } 00459: 00460: readDmesg(al, kl); 00461: 00462: // failure is an option ... messages are in dmesg (required by standard) 00463: 00464: if (kl && kl‐>startListener()) { 00465: delete kl; 00466: } 00467: 00468: if (al && al‐>startListener()) { 00469: delete al; 00470: }其中涉及到seLinux權限系統,需要有權限的用戶才能讀取,而且系統會有專門的文件(dev/dmsg)記錄該日志,我們只需讀取該文件就行,這里不多展開。我們只詳細講解普通日志的讀寫過程:
環形buffer初始化
首先我們發現有一個專門的LogBuffer?logBuf?=?new?LogBuffer(times);用來存儲日志,其初始化過程為:
主要是給LogBuffer設置大小。
00095: void LogBuffer::init() { 00096: //256k set in /packages/apps/Settings/src/com/android/settings/DevelopmentSettings.java 00097: static const char global_tuneable[] = "persist.logd.size"; // Settings App 00098: // 00099: static const char global_default[] = "ro.logd.size"; // BoardConfig.mk 00100: 00101: unsigned long default_size = property_get_size(global_tuneable); 00102: if (!default_size) { 00103: default_size = property_get_size(global_default); 00104: } 00105: 00106: log_id_for_each(i) { 00107: char key[PROP_NAME_MAX]; 00108: 00109: snprintf(key, sizeof(key), "%s.%s", 00110: global_tuneable, android_log_id_to_name(i)); 00111: unsigned long property_size = property_get_size(key); 00112: 00113: if (!property_size) { 00114: snprintf(key, sizeof(key), "%s.%s", 00115: global_default, android_log_id_to_name(i)); 00116: property_size = property_get_size(key); 00117: } 00118: 00119: if (!property_size) { 00120: property_size = default_size; 00121: } 00122: 00123: if (!property_size) { 00124: property_size = LOG_BUFFER_SIZE; 00125: } 00126: 00127: if (setSize(i, property_size)) { 00128: setSize(i, LOG_BUFFER_MIN_SIZE); 00129: } 00130: } 00131: } ? end init ?經過全局搜索發現persist.logd.size在/apps/Settings/src/com/android/settings/DevelopmentSettings.java中定義的其大小為256K而ro.logd.size并沒有在makefile文件中定義,除此之外注意log_id_for_each(i) 是為每個緩沖區定義大小,最后我們分析可以得出,對每種類型的buffer都設置了256K大小的空間,這點可一個通過logcat –g 驗證。
圖七 查看環形緩沖區
有了這個空間后,我們就可以往這個空間寫數據,而且,還可以從這個空間往外讀取數據,這個空間相當于一個環形空間,一旦被初始化后,就一直可以重復利用,有些時候會出現,寫得太快,而讀取太慢導致有些日志數據會被覆蓋的可能,這是正常情況,讀者可以通過過濾一些日志的方法來避免日志被覆蓋的可能。
響應客戶端讀取日志
有了這個buffer后,如何使用呢,首先我們講解如何在監聽到客戶端讀操作的時候,如何將buffer中的日志寫回客戶端。從監聽器上我們發現通過一個LogReader *reader?=?new?LogReader(logBuf)這么一個類進行處理的。
1、提供一個接口供外部用來提示我們是否需要提醒客戶端有新的日志寫入,需要讀取日志了。
\system\core\logd\LogReader.cpp
00033: // all of our listening sockets. 00034: void LogReader::notifyNewLog() { 00035: FlushCommand command(*this); 00036: runOnEachSocket(&command); 00037: }2、提供客戶端讀取日志的接口,并能接收客戶端請求參數,返回對應的日志。
00039: bool LogReader::onDataAvailable(SocketClient *cli) { 00040: static bool name_set; 00041: if (!name_set) {//響應日志讀操作 00042: prctl(PR_SET_NAME, "logd.reader"); 00043: name_set = true; 00044: }//讀取客戶端傳入參數 00048: int len = read(cli‐>getSocket(), buffer, sizeof(buffer) ‐ 1); 00049: if (len <= 0) { 00050: doSocketDelete(cli); 00051: return false; 00052: } 00053: buffer[len] = '\0'; 00054: . . . . 省略一些參數設置 00151: logbuf().flushTo(cli, sequence, FlushCommand::hasReadLogs(cli), 00152: logFindStart.callback, &logFindStart); 00153: 00154: if (!logFindStart.found()) { 00155: if (nonBlock) { 00156: doSocketDelete(cli); 00157: return false; 00158: } 00159: sequence = LogBufferElement::getCurrentSequence(); 00160: } 00161: } ? end if start!=log_time.EPOCH ? 00162: 00163: FlushCommand command(*this, nonBlock, tail, logMask, pid, sequence);客戶 00164: command.runSocketCommand(cli); 00165: return true; 00166: } ? end onDataAvailable ?先通過read(cli‐>getSocket(), buffer, sizeof(buffer) ‐ 1)獲取到客戶端傳入的數據在command.runSocketCommand(cli);向客戶端寫日志。具體寫入方法是:
00048: void FlushCommand::runSocketCommand(SocketClient *client) { 00049: LogTimeEntry *entry = NULL; 00050: LastLogTimes × = mReader.logbuf().mTimes;. . . 00074: entry = new LogTimeEntry(mReader, client, mNonBlock, mTail, mLogMask, mPid, mStart); 00075: times.push_back(entry); 00076: } 00077: 00078: client‐>incRef(); 00079: 00080: // release client and entry reference counts once done 00081: entry‐>startReader_Locked(); 00082: LogTimeEntry::unlock(); 00083: } ? end runSocketCommand ?times.push_back(entry);會將整個讀取到的日志回寫到客戶端中。客戶端收到后輸出對應位置。
流程圖:
圖八 服務端響應客戶端讀日志
響應客戶端讀取日志
當客戶端需要讀取日志時,向服務端發送請求,監聽器通過LogListener *swl?=?new?LogListener(logBuf, reader);監聽客戶端請求,其具體實現方式為:
\system\core\logd\LogListener.cpp
bool LogListener::onDataAvailable(SocketClient *cli) { 00038: static bool name_set; 00039: if (!name_set) { //響應寫操作 00040: prctl(PR_SET_NAME, "logd.writer"); 00041: name_set = true; 00042: } 00060: int socket = cli‐>getSocket();//接收數據 00062: ssize_t n = recvmsg(socket, &hdr, 0);. . . . 省略其中數據的處理 //將日志寫入緩沖區 00101: if (logbuf‐>log((log_id_t)header‐>id, header‐>realtime, 00102: cred‐>uid, cred‐>pid, header‐>tid, msg, 00103: ((size_t) n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX) >= 0) { //通知所有連接的客戶端有新日志寫入,可以讀了 00104: reader‐>notifyNewLog(); 00105: } 00106: 00107: return true;} ? end onDataAvailable ?第一步:從socket中獲取到客戶端傳入的數據
第二步:初始化好數據
第三步:將日志寫入到對應的buffer中
第四步:通知所有客戶端,有新日志寫入可以讀取。
寫入buffer的logbuf‐>log((log_id_t)header‐>id, header‐>realtime,cred‐>uid, cred‐>pid, header‐>tid, msg,((size_t) n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX)函數實現:其主要用來寫LogBuffer的。
\system\core\logd\LogBuffer.cpp
00139: int LogBuffer::log(log_id_t log_id, log_time realtime, 00140: uid_t uid, pid_t pid, pid_t tid, 00141: const char *msg, unsigned short len) { . . . . .省略參數設置 00197: if (!end_set || (end <= entry‐>mEnd)) { 00198: end = entry‐>mEnd; 00199: end_set = true; 00200: } 00201: } 00202: t++; 00203: } 00204: 00205: if (end_always 00206: || (end_set && (end >= (*last)‐>getSequence()))) { 00207: mLogElements.push_back(elem); 00208: } else { 00209: mLogElements.insert(last,elem); 00210: } 00211: 00212: LogTimeEntry::unlock(); 00213: } ? end else ? 00214: 00215: stats.add(elem); 00216: maybePrune(log_id); 00217: pthread_mutex_unlock(&mLogElementsLock); 00218: 00219: return len; 00220: } ? end log ?上面的代碼省略了許多具體的實現,但是讀者可以發現最終調用了mLogElements.insert(last,elem)方法,在buffer中插入了對應的日志實體。這個log方法主要是用來對logbuffer進行寫操作,要了解更多對buffer的操作請,詳細閱讀\system\core\logd\LogBuffer.cpp文件。
流程圖:
圖九 服務端響應客戶端寫
原文地址:?https://zhuanlan.zhihu.com/p/24372024
總結
以上是生活随笔為你收集整理的Android6.0 Log的工作机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ANDROID L日志系统——JAVAA
- 下一篇: Andriod 破解之道(一)