Logcat工具的初始化過程是從文件logcat.cpp中的函數main開始的,它會打開日志設備和解析命令行參數。這個函數的實現代碼比較長,我們分段來閱讀。
**system/core/logcat/logcat.cpp**
~~~
static AndroidLogFormat * g_logformat;
static bool g_nonblock = false;
static int g_tail_lines = 0;
static const char * g_outputFileName = NULL;
static int g_logRotateSizeKBytes = 0; // 0 means "no log rotation"
static int g_maxRotatedLogs = DEFAULT_MAX_ROTATED_LOGS; // 0 means "unbounded"
static int g_outFD = -1;
static off_t g_outByteCount = 0;
static int g_printBinary = 0;
static int g_devCount = 0;
static EventTagMap* g_eventTagMap = NULL;
int main(int argc, char **argv)
{
int err;
int hasSetLogFormat = 0;
......
const char *forceFilters = NULL;
log_device_t* devices = NULL;
log_device_t* dev;
bool needBinary = false;
g_logformat = android_log_format_new();
~~~
第25行調用函數android_log_format_new來創建一個全局的日志記錄輸出格式和輸出過濾器對象g_logformat。
**system/core/liblog/logprint.c**
~~~
AndroidLogFormat *android_log_format_new()
{
AndroidLogFormat *p_ret;
p_ret = calloc(1, sizeof(AndroidLogFormat));
p_ret->global_pri = ANDROID_LOG_VERBOSE;
p_ret->format = FORMAT_BRIEF;
return p_ret;
}
~~~
從函數android_log_format_new的實現就可以看出,全局變量g_logformat指定的日志記錄輸出格式為FORMAT_BRIEF,而指定的全局默認日志記錄過濾優先級為ANDROID_LOG_VERBOSE。
**system/core/logcat/logcat.cpp**
~~~
for (;;) {
int ret;
ret = getopt(argc, argv, "cdt:gsQf:r::n:v:b:B");
......
switch(ret) {
......
case 'd':
g_nonblock = true;
break;
case 't':
g_nonblock = true;
g_tail_lines = atoi(optarg);
break;
......
case 'b': {
char* buf = (char*) malloc(strlen(LOG_FILE_DIR) + strlen(optarg) + 1);
strcpy(buf, LOG_FILE_DIR);
strcat(buf, optarg);
bool binary = strcmp(optarg, "events") == 0;
if (binary) {
needBinary = true;
}
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]);
}
android::g_devCount++;
}
break;
case 'B':
android::g_printBinary = 1;
break;
case 'f':
// redirect output to a file
android::g_outputFileName = optarg;
break;
case 'r':
if (optarg == NULL) {
android::g_logRotateSizeKBytes
= DEFAULT_LOG_ROTATE_SIZE_KBYTES;
} else {
long logRotateSize;
char *lastDigit;
if (!isdigit(optarg[0])) {
......
}
android::g_logRotateSizeKBytes = atoi(optarg);
}
break;
case 'n':
if (!isdigit(optarg[0])) {
......
}
android::g_maxRotatedLogs = atoi(optarg);
break;
case 'v':
err = setLogFormat(optarg);
......
hasSetLogFormat = 1;
break;
......
}
}
~~~
函數第26行到第102行的for循環依次對命令行參數進行解析。命令行參數的字符串解析是通過調用函數getopt來實現的,它的返回值ret表示命令行中的一個選項,而選項對應的值保存在變量optarg中。接下來,我們就分別對命令行中的選項d、t、b、B、f、r、n和v進行介紹。
如果使用選項d來啟動Logcat工具,那么函數第35行就會把全局變量g_nonblock的值設置為true,表示當Logger日志驅動程序中沒有日志記錄可讀時,Logcat工具就直接退出。
如果使用選項t來啟動Logcat工具,那么函數第39行就會將選項后面的數字保存在全局變量g_tail_lines中,同時第38行會將全局變量g_nonblock的值設置為true。其中,全局變量g_tail_lines表示Logcat工具每次在輸出日志記錄時,只輸出最新的日志記錄條數。
如果使用選項b來啟動Logcat工具,那么函數第52行到第60行代碼就會將選項后面的字符串取出來,并且為它創建一個log_device_t結構體,表示Logcat工具要打開的日志設備。選項后面的字符串的取值為“main”、“radio”或者 “events”,表示要打開的日志設備分別為/dev/log/main、/dev/log/radio或者/dev/log/events。當選項后面的字符串為“events”時,函數第49行會將變量needBinary的值設置為true,表示Logcat工具要解析目標設備上的/system/etc/event-log-tags文件的內容,以便可以將日志設備 /dev/log/events的內容從二進制格式轉換為文本格式。每次創建一個log_device_t結構體時,全局變量g_devCount的值就會加1,表示Logcat工具所打開的日志設備個數。
> 如果在啟動Logcat工具時,沒有使用選項b,那么Logcat工具默認打開的日志設備就為/dev/log/main。
如果使用選項B來啟動Logcat工具,那么函數第65行就會將全局變量g_printBinary的值設置為1,表示要以二進制格式來輸出日志記錄。這時候就不需要對日志記錄的內容進行解析了。
如果使用選項f來啟動Logcat工具,那么函數第70行就會將選項后面的字符串取出來,并且保存在全局變量g_outputFileName中,用作日志記錄的輸出文件名稱。當輸入到文件g_outputFileName的日志記錄的大小(單位是字節)達到設定的量時,Logcat工具就會將接下來的日志記錄輸出到另外一個文件中,直到這個文件的日志記錄的大小也達到設定的量為止。用作日志記錄輸出的文件的命名方式是遵循一定的規律的。例如,假設選項f后面的字符串為 “logfile”,那么第一個日志記錄輸出文件的名稱就為“logfile”,接下來的日志記錄輸出文件的名稱依次為“logfile.1”、“logfile.2”、“logfile.3”等。日志記錄輸出文件的大小由選項r來指定,而日志記錄輸出文件的個數由選項n來指定。當日志記錄輸出文件的個數達到設定的值時,并且每一個日志記錄輸出文件的大小也達到設定的值時,Logcat工具就會循環使用已有的日志記錄輸出文件,即將原來的日志記錄輸出文件的內容擦掉,然后將新的日志記錄寫入到這些文件中。
如果使用選項r來啟動Logcat工具,那么函數第84行就會將選項后面的數字保存在全局變量g_logRotateSizeKBytes中,用來表示每一個日志記錄輸出文件的最大容量。如果該選項后面沒有指定數字,那么全局變量g_logRotateSizeKBytes的值就默認設置為DEFAULT_LOG_ROTATE_SIZE_KBYTES。
**system/core/logcat/logcat.cpp**
~~~
#define DEFAULT_LOG_ROTATE_SIZE_KBYTES 16
~~~
全局變量g_logRotateSizeKBytes的值被初始化為0,表示所有的日志記錄輸出文件的容量沒有限制,這會導致所有的日志記錄都輸出到文件g_outputFileName中。
如果使用選項n來啟動Logcat工具,那么函數第92行就會將選項后面的數字保存在全局變量g_maxRotatedLogs中,表示日志記錄輸出文件的最大個數。全局變量g_maxRotateLogs的值被初始化為DEFAULT_MAX_ROTATED_LOGS。
**system/core/logcat/logcat.cpp**
~~~
#define DEFAULT_MAX_ROTATED_LOGS 16
~~~
如果使用選項v來啟動Logcat工具,那么函數第95行就會調用函數setLogFormat將選項后面的字符串轉換為相應的AndroidLogPrintFormat枚舉值。該選項后面的字符串取值為“brief”、“process”、“tag”、“thread”、“raw”、“time”、“threadtime”或者“long”,分別對應于枚舉AndroidLogPrintFormat中的每一個值。
函數setLogFormat的實現如下所示。
**system/core/logcat/logcat.cpp**
~~~
static int setLogFormat(const char * formatString)
{
static AndroidLogPrintFormat format;
format = android_log_formatFromString(formatString);
if (format == FORMAT_OFF) {
// FORMAT_OFF means invalid string
return -1;
}
android_log_setPrintFormat(g_logformat, format);
return 0;
}
~~~
第5行調用函數android_log_formatFromString將字符串formatString轉換為一個AndroidLogPrintFormat枚舉值。
**system/core/liblog/logprint.c**、
~~~
AndroidLogPrintFormat android_log_formatFromString(const char * formatString)
{
static AndroidLogPrintFormat format;
if (strcmp(formatString, "brief") == 0) format = FORMAT_BRIEF;
else if (strcmp(formatString, "process") == 0) format = FORMAT_PROCESS;
else if (strcmp(formatString, "tag") == 0) format = FORMAT_TAG;
else if (strcmp(formatString, "thread") == 0) format = FORMAT_THREAD;
else if (strcmp(formatString, "raw") == 0) format = FORMAT_RAW;
else if (strcmp(formatString, "time") == 0) format = FORMAT_TIME;
else if (strcmp(formatString, "threadtime") == 0) format = FORMAT_THREADTIME;
else if (strcmp(formatString, "long") == 0) format = FORMAT_LONG;
else format = FORMAT_OFF;
return format;
}
~~~
如果參數formatString是一個非法字符串,那么函數android_log_formatFromString就會將它轉換為一個FORMAT_OFF值;否則,就通過第5行到第12行代碼來得到正確的AndroidLogPrintFormat枚舉值。
回到函數setLogFormat中,將前面得到的日志記錄輸出格式保存在變量format中,接著第12行繼續調用函數android_log_setPrintFormat將變量format的值設置到全局變量g_logformat的成員變量format中,用來描述Logcat工具的日志記錄輸出格式。
**system/core/liblog/logprint.c**
~~~
void android_log_setPrintFormat(AndroidLogFormat *p_format,
AndroidLogPrintFormat format)
{
p_format->format=format;
}
~~~
回到main函數中,如果指定了選項v,并且成功地對它進行了解析,那么函數第98行就會將變量hasSetLogFormat的值設置為1,表示設置了Logcat工具的日志記錄輸出格式。
函數main解析完成命令行參數后,繼續往下執行。
**system/core/logcat/logcat.cpp**
~~~
if (!devices) {
devices = new log_device_t(strdup("/dev/"LOGGER_LOG_MAIN), false, 'm');
android::g_devCount = 1;
int accessmode =
(mode & O_RDONLY) ? R_OK : 0
| (mode & O_WRONLY) ? W_OK : 0;
// only add this if it's available
if (0 == access("/dev/"LOGGER_LOG_SYSTEM, accessmode)) {
devices->next = new log_device_t(strdup("/dev/"LOGGER_LOG_SYSTEM), false, 's');
android::g_devCount++;
}
}
~~~
如果啟動Logcat工具時,沒有指定選項b,那么變量devices的值就會等于NULL。這時候Logcat工具就會默認打開日志設備/dev/log/main來讀取它的日志記錄。如果日志設備/dev/log/system也存在,那么Logcat工具也會一起打開它來讀取日志記錄。
打開要讀取日志記錄的日志設備之后,函數main繼續往下執行。
**system/core/logcat/logcat.cpp**
~~~
android::setupOutput();
~~~
第115行調用函數setupOutput來設置日志記錄是輸出到文件中還是打印到標準輸出中,它的實現如下所示。
**system/core/logcat/logcat.cpp**
~~~
static void setupOutput()
{
if (g_outputFileName == NULL) {
g_outFD = STDOUT_FILENO;
} else {
struct stat statbuf;
g_outFD = openLogFile(g_outputFileName);
if (g_outFD < 0) {
perror ("couldn't open output file");
exit(-1);
}
fstat(g_outFD, &statbuf);
g_outByteCount = statbuf.st_size;
}
}
~~~
第4行判斷全局變量g_outputFileName的值是否等于NULL。如果是,則說明要把日志記錄打印到標準輸出中,因此就將全局變量g_outFD指向標準輸出文件描述符STDOUT_FILENO;否則,就要將日志記錄輸出到文件g_outputFileName中。如果是將日志記錄輸出到文件中,那么函數第10行調用openLogFile函數以APPEND方式打開日志記錄輸出文件g_outputFileName。
**system/core/logcat/logcat.cpp**
~~~
static int openLogFile (const char *pathname)
{
return open(g_outputFileName, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
}
~~~
回到函數setupOutput中,第17行獲得當前日志記錄輸出文件g_outputFileName的大小,并且將它保存在全局變量g_outByteCount中。接下來Logcat工具往文件g_outputFileName輸出日志記錄時,就會相應地增加全局變量g_outByteCount的值。當全局變量g_outByteCount的值達到g_logRotateSizeKBytes時,Logcat工具就會將后面的日志記錄輸出到另外一個文件中。
回到函數main中,繼續往下執行。
**system/core/logcat/logcat.cpp**
~~~
if (hasSetLogFormat == 0) {
const char* logFormat = getenv("ANDROID_PRINTF_LOG");
if (logFormat != NULL) {
err = setLogFormat(logFormat);
......
}
}
~~~
如果啟動Logcat工具時,沒有指定選項v,那么變量hasSetLogFormat的值就會等于0,表示沒有指定日志記錄的輸出格式。這時候Logcat工具就會檢查環境變量ANDROID_PRINTF_LOG的值。如果環境變量ANDROID_PRINTF_LOG存在,那么就會調用函數setLogFormat將它的值作為Logcat工具的日志記錄輸出格式。
設置好日志記錄的輸出格式之后,函數main繼續往下執行。
**system/core/logcat/logcat.cpp**
~~~
if (forceFilters) {
err = android_log_addFilterString(g_logformat, forceFilters);
......
} else if (argc == optind) {
// Add from environment variable
char *env_tags_orig = getenv("ANDROID_LOG_TAGS");
if (env_tags_orig != NULL) {
err = android_log_addFilterString(g_logformat, env_tags_orig);
......
}
} else {
// Add from commandline
for (int i = optind ; i < argc ; i++) {
err = android_log_addFilterString(g_logformat, argv[i]);
......
}
}
~~~
在啟動Logcat工具時,我們可以指定一個不公開的選項Q,這時候Logcat工具就會去讀取目標設備上的/proc/cmdline文件,檢查里面有沒有設置日志記錄輸出過濾器。如果設置了,那么變量forceFilters就指向這個日志記錄輸出過濾器,因此,函數第125行就會調用函數android_log_addFilterString將它增加到Logcat工具的日志記錄輸出過濾器列表中。
如果啟動Logcat工具時,沒有指定選項Q,那么Logcat工具就會繼續檢查命令行是否還帶有其他額外的參數。如果沒有,即第127行的if語句為true,那么Logcat工具就會將環境變量ANDROID_LOG_TAGS的值作為日志記錄輸出的一個過濾器來使用。
如果啟動Logcat工具時,沒有指定選項Q,但是命令行帶有額外的參數,那么函數第137行到第140行的for循環就會檢查這些額外的參數是否是一個日志記錄輸出過濾表達式。例如,當使用下面的命令來啟動Logcat工具時:
`USER@MACHINE:~/Android$ adb logcat LOGTAG:I`
最后一個參數“LOGTAG:I”就稱為日志記錄輸出過濾表達式。日志記錄輸出過濾表達式的格式為“[:priority]”,其中,tag為任意字符串,表示一個日志記錄標簽;priority是一個字符,表示一個日志記錄優先級。合法的priority字符為數字‘0’到‘9’,或者字母‘v’、‘d’、‘i’、‘w’、‘f’、‘s’和‘*’,它們的具體含義在后面會進一步解釋。這時候函數第138行就會調用函數android_log_addFilterString來解析這些日志記錄輸出過濾表達式,并且將它增加到Logcat工具的日志記錄輸出過濾器列表中。
函數android_log_addFilterString的實現如下所示。
**system/core/liblog/logprint.c**
~~~
/**
* filterString: a comma/whitespace-separated set of filter expressions
*
* eg "AT:d *:i"
*
* returns 0 on success and -1 on invalid expression
*
* Assumes single threaded execution
*
*/
int android_log_addFilterString(AndroidLogFormat *p_format,
const char *filterString)
{
char *filterStringCopy = strdup (filterString);
char *p_cur = filterStringCopy;
char *p_ret;
int err;
// Yes, I'm using strsep
while (NULL != (p_ret = strsep(&p_cur, " \t,"))) {
// ignore whitespace-only entries
if(p_ret[0] != '\0') {
err = android_log_addFilterRule(p_format, p_ret);
if (err < 0) {
goto error;
}
}
}
free (filterStringCopy);
return 0;
error:
free (filterStringCopy);
return -1;
}
~~~
第二個參數filterString可以同時包括若干個日志記錄輸出過濾表達式,它們以空格、制表符或者逗號分割。每一個日志記錄輸出過濾表達式都是使用函數android_log_addFilterRule來解析的,它的實現如下所示。
**system/core/liblog/logprint.c**
~~~
/**
* filterExpression: a single filter expression
* eg "AT:d"
*
* returns 0 on success and -1 on invalid expression
*
* Assumes single threaded execution
*/
int android_log_addFilterRule(AndroidLogFormat *p_format,
const char *filterExpression)
{
size_t i=0;
size_t tagNameLength;
android_LogPriority pri = ANDROID_LOG_DEFAULT;
tagNameLength = strcspn(filterExpression, ":");
if (tagNameLength == 0) {
goto error;
}
if(filterExpression[tagNameLength] == ':') {
pri = filterCharToPri(filterExpression[tagNameLength+1]);
if (pri == ANDROID_LOG_UNKNOWN) {
goto error;
}
}
if(0 == strncmp("*", filterExpression, tagNameLength)) {
// This filter expression refers to the global filter
// The default level for this is DEBUG if the priority
// is unspecified
if (pri == ANDROID_LOG_DEFAULT) {
pri = ANDROID_LOG_DEBUG;
}
p_format->global_pri = pri;
} else {
// for filter expressions that don't refer to the global
// filter, the default is verbose if the priority is unspecified
if (pri == ANDROID_LOG_DEFAULT) {
pri = ANDROID_LOG_VERBOSE;
}
char *tagName;
// Presently HAVE_STRNDUP is never defined, so the second case is always taken
// Darwin doesn't have strnup, everything else does
#ifdef HAVE_STRNDUP
tagName = strndup(filterExpression, tagNameLength);
#else
//a few extra bytes copied...
tagName = strdup(filterExpression);
tagName[tagNameLength] = '\0';
#endif /*HAVE_STRNDUP*/
FilterInfo *p_fi = filterinfo_new(tagName, pri);
free(tagName);
p_fi->p_next = p_format->filters;
p_format->filters = p_fi;
}
return 0;
error:
return -1;
}
~~~
第17行取得日志記錄輸出過濾表達式中冒號的位置,接著第24行就調用函數filterCharToPri將冒號后面的字符轉換為android_LogPriority枚舉值。
**system/core/liblog/logprint.c**
~~~
/*
* Note: also accepts 0-9 priorities
* returns ANDROID_LOG_UNKNOWN if the character is unrecognized
*/
static android_LogPriority filterCharToPri (char c)
{
android_LogPriority pri;
c = tolower(c);
if (c >= '0' && c <= '9') {
if (c >= ('0'+ANDROID_LOG_SILENT)) {
pri = ANDROID_LOG_VERBOSE;
} else {
pri = (android_LogPriority)(c - '0');
}
} else if (c == 'v') {
pri = ANDROID_LOG_VERBOSE;
} else if (c == 'd') {
pri = ANDROID_LOG_DEBUG;
} else if (c == 'i') {
pri = ANDROID_LOG_INFO;
} else if (c == 'w') {
pri = ANDROID_LOG_WARN;
} else if (c == 'e') {
pri = ANDROID_LOG_ERROR;
} else if (c == 'f') {
pri = ANDROID_LOG_FATAL;
} else if (c == 's') {
pri = ANDROID_LOG_SILENT;
} else if (c == '*') {
pri = ANDROID_LOG_DEFAULT;
} else {
pri = ANDROID_LOG_UNKNOWN;
}
return pri;
}
~~~
從函數filterCharToPri的實現就可以看出,字符“v”、“d”、“i”、“w”、“e”、“f”、“s”和“*”對應的日志記錄優先級分別為ANDROID_LOG_VERBOSE、ANDROID_LOG_DEBUG、ANDROID_LOG_INFO、ANDROID_LOG_WARN、ANDROID_LOG_ERROR、ANDROID_LOG_FATAL、ANDROID_LOG_SILENT和ANDROID_LOG_DEFAULT。此外,字符“0”到“7”對應的日志記錄優先級分別為ANDROID_LOG_UNKNOWN、ANDROID_LOG_DEFAULT、ANDROID_LOG_VERBOSE、ANDROID_LOG_DEBUG、ANDROID_LOG_INFO、ANDROID_LOG_WARN、ANDROID_LOG_ERROR和ANDROID_LOG_FATAL,而字符“8”和“9”對應的日志記錄優先級均為ANDROID_LOG_VERBOSE。
回到函數android_log_addFilterRule中,如果日志記錄輸出過濾表達式中的日志記錄標簽值為“*”,即第31行的if語句為true,就表示要將它的日志記錄優先級設置為全局的日志記錄過濾優先級;否則,就為該日志記錄輸出過濾表達式創建一個日志記錄輸出過濾器,并且增加到Logcat工具的日志記錄輸出過濾器列表中,如第59行到第63行代碼所示。
設置好日志記錄輸出過濾器列表之后,回到函數main中,繼續往下執行。
**system/core/logcat/logcat.cpp**
~~~
dev = devices;
while (dev) {
dev->fd = open(dev->device, mode);
......
dev = dev->next;
}
~~~
函數第143行到148行的while循環依次調用函數open來打開保存在devices列表中的各個日志設備,并且將得到的文件描述符保存在對應的日志設備的成員變量fd中。
日志設備打開之后,函數main繼續往下執行。
**system/core/logcat/logcat.cpp**
~~~
if (needBinary)
android::g_eventTagMap = android_openEventTagMap(EVENT_TAG_MAP_FILE);
~~~
函數第149行判斷打開的日志設備的日志記錄格式是否包含二進制格式,即是否打開了日志設備/dev/log/events。如果是,即第149行的if語句為true,那么第150行就會調用函數android_openEventTagMap來解析目標設備上的/system/etc/event-log-tags文件,因為Logcat工具需要根據它的內容來解析類型為events的日志記錄。函數android_openEventTagMap的返回值是一個EventTagMap結構體,保存在全局變量g_eventTagMap中。
EVENT_TAG_MAP_FILE是一個宏定義,它的值為“/system/etc/event-log-tags”,如下所示。
**system/core/include/cutils/event_tag_map.h**
`#define EVENT_TAG_MAP_FILE "/system/etc/event-log-tags"`
函數android_openEventTagMap的實現如下所示。
**system/core/liblog/event_tag_map.c**
~~~
/*
* Open the map file and allocate a structure to manage it.
*
* We create a private mapping because we want to terminate the log tag
* strings with '\0'.
*/
EventTagMap* android_openEventTagMap(const char* fileName)
{
EventTagMap* newTagMap;
off_t end;
int fd = -1;
newTagMap = calloc(1, sizeof(EventTagMap));
if (newTagMap == NULL)
return NULL;
fd = open(fileName, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "%s: unable to open map '%s': %s\n",
OUT_TAG, fileName, strerror(errno));
goto fail;
}
end = lseek(fd, 0L, SEEK_END);
(void) lseek(fd, 0L, SEEK_SET);
if (end < 0) {
fprintf(stderr, "%s: unable to seek map '%s'\n", OUT_TAG, fileName);
goto fail;
}
newTagMap->mapAddr = mmap(NULL, end, PROT_READ | PROT_WRITE, MAP_PRIVATE,
fd, 0);
if (newTagMap->mapAddr == MAP_FAILED) {
fprintf(stderr, "%s: mmap(%s) failed: %s\n",
OUT_TAG, fileName, strerror(errno));
goto fail;
}
newTagMap->mapLen = end;
if (processFile(newTagMap) != 0)
goto fail;
return newTagMap;
fail:
android_closeEventTagMap(newTagMap);
if (fd >= 0)
close(fd);
return NULL;
}
~~~
第13行首先創建一個EventTagMap結構體對象,接著第17行打開文件/system/etc/event-log-tags,第24行得到該文件的大小,第31行將該文件的內容映射到內存中,最后第41行調用函數processFile來解析文件/system/etc/event-log-tags中的內容。
**system/core/liblog/event_tag_map.c**
~~~
/*
* Crunch through the file, parsing the contents and creating a tag index.
*/
static int processFile(EventTagMap* map)
{
EventTag* tagArray = NULL;
/* get a tag count */
map->numTags = countMapLines(map);
if (map->numTags < 0)
return -1;
//printf("+++ found %d tags\n", map->numTags);
/* allocate storage for the tag index array */
map->tagArray = calloc(1, sizeof(EventTag) * map->numTags);
if (map->tagArray == NULL)
return -1;
/* parse the file, null-terminating tag strings */
if (parseMapLines(map) != 0) {
fprintf(stderr, "%s: file parse failed\n", OUT_TAG);
return -1;
}
/* sort the tags and check for duplicates */
if (sortTags(map) != 0)
return -1;
return 0;
}
~~~
第9行調用函數countMapLines來計算文件/system/etc/event-log-tags的行數,接著第16行根據這個行數來分配一個EventTag結構體數組,其中,每一行的內容都對應一個EventTag結構體。第21行調用函數parseMapLines來解析文件/system/etc/event-log-tags的內容。函數countMapLines主要是根據文件中的換行符‘\n’來計算行數,而且會忽略文件中的空白行和注釋行。函數parseMapLines對文件中的每一行內容進行解析,從而得到類型為events的日志記錄標簽描述表。下面我們就通過一個例子來說明函數parseMapLines的工作原理。
當目標設備上的文件/system/etc/event-log-tags包含以下一行內容時:
`2722 battery_level (level|1|6),(voltage|1|1),(temperature|1|1)`
函數parseMapLines就會將字符串“2722”轉換為數字2722,并且將它保存在一個EventTag結構體的成員變量tagIndex中,然后再將字符“battery_level”在內存中的地址保存在同一個EventTag結構體的成員變量tagStr中,從而將日志記錄標簽號2722與描述字符串“battery_level”關聯起來。
函數parseMapLines執行完成之后,我們就得到了類型為events的日志記錄的日志標簽描述表,它保存在一個EventTagMap結構體內部的數組tagArray中。為了使后面能夠根據日志標簽號快速找到對應的日志標簽描述字符串,函數processFile的第27行調用函數sortTags對這個數組進行排序。
**system/core/liblog/event_tag_map.c**
~~~
/*
* Sort the EventTag array so we can do fast lookups by tag index.
* the sort we do a quick check for duplicate tag indices.
*
* Returns 0 on success.
*/
static int sortTags(EventTagMap* map)
{
int i;
qsort(map->tagArray, map->numTags, sizeof(EventTag), compareEventTags);
for (i = 1; i < map->numTags; i++) {
if (map->tagArray[i].tagIndex == map->tagArray[i-1].tagIndex) {
fprintf(stderr, "%s: duplicate tag entries (%d:%s and %d:%s)\n",
OUT_TAG,
map->tagArray[i].tagIndex, map->tagArray[i].tagStr,
map->tagArray[i-1].tagIndex, map->tagArray[i-1].tagStr);
return -1;
}
}
return 0;
}
~~~
第11行調用快排函數qsort對該日志標簽描述數組進行排序,指定的比較函數為compareEventTags,它的實現如下所示。
**system/core/liblog/event_tag_map.c**
~~~
/*
* Compare two EventTags.
*/
static int compareEventTags(const void* v1, const void* v2)
{
const EventTag* tag1 = (const EventTag*) v1;
const EventTag* tag2 = (const EventTag*) v2;
return tag1->tagIndex - tag2->tagIndex;
}
~~~
從這里就可以看出,日志標簽描述數組的元素是按照日志標簽號從小到大排列的。
回到函數sortTags中,第13行到第21行的for循環檢查前面獲得的日志標簽描述數組的合法性,即檢查它里面是否有重復的日志標簽號。如果有,就會返回錯誤碼-1,表示目標設備上的文件/system/etc/event-log-tags的內容有問題。
解析完成目標設備上的文件/system/etc/event-log-tags之后,回到函數main中,繼續往下執行。
**system/core/logcat/logcat.cpp**
~~~
android::readLogLines(devices);
return 0;
}
~~~
第151行調用函數readLogLines開始讀取前面打開的日志設備的日志記錄。接下來,我們就分析這些日志記錄的讀取過程。
- 文章概述
- 下載Android源碼以及查看源碼
- win10 平臺通過VMware Workstation安裝Ubuntu
- Linux系統安裝Ubuntu編譯Android源碼
- Eclipse快捷鍵大全
- 前言
- 第一篇 初識Android系統
- 第一章 準備知識
- 1.1 Linux內核參考書籍
- 1.2 Android應用程序參考書籍
- 1.3 下載、編譯和運行Android源代碼
- 1.3.1 下載Android源代碼
- 1.3.2 編譯Android源代碼
- 1.3.3 運行Android模擬器
- 1.4 下載、編譯和運行Android內核源代碼
- 1.4.1 下載Android內核源代碼
- 1.4.2 編譯Android內核源代碼
- 1.4.3 運行Android模擬器
- 1.5 開發第一個Android應用程序
- 1.6 單獨編譯和打包Android應用程序模塊
- 1.6.1 導入單獨編譯模塊的mmm命令
- 1.6.2 單獨編譯Android應用程序模塊
- 1.6.3 重新打包Android系統鏡像文件
- 第二章 硬件抽象層
- 2.1 開發Android硬件驅動程序
- 2.1.1 實現內核驅動程序模塊
- 2.1.2 修改內核Kconfig文件
- 2.1.3 修改內核Makefile文件
- 2.1.4 編譯內核驅動程序模塊
- 2.1.5 驗證內核驅動程序模塊
- 2.2 開發C可執行程序驗證Android硬件驅動程序
- 2.3 開發Android硬件抽象層模塊
- 2.3.1 硬件抽象層模塊編寫規范
- 2.3.1.1 硬件抽象層模塊文件命名規范
- 2.3.1.2 硬件抽象層模塊結構體定義規范
- 2.3.2 編寫硬件抽象層模塊接口
- 2.3.3 硬件抽象層模塊的加載過程
- 2.3.4 處理硬件設備訪問權限問題
- 2.4 開發Android硬件訪問服務
- 2.4.1 定義硬件訪問服務接口
- 2.4.2 實現硬件訪問服務
- 2.4.3 實現硬件訪問服務的JNI方法
- 2.4.4 啟動硬件訪問服務
- 2.5 開發Android應用程序來使用硬件訪問服務
- 第三章 智能指針
- 3.1 輕量級指針
- 3.1.1 實現原理分析
- 3.1.2 使用實例分析
- 3.2 強指針和弱指針
- 3.2.1 強指針的實現原理分析
- 3.2.2 弱指針的實現原理分析
- 3.2.3 應用實例分析
- 第二篇 Android專用驅動系統
- 第四章 Logger日志系統
- 4.1 Logger日志格式
- 4.2 Logger日志驅動程序
- 4.2.1 基礎數據結構
- 4.2.2 日志設備的初始化過程
- 4.2.3 日志設備文件的打開過程
- 4.2.4 日志記錄的讀取過程
- 4.2.5 日志記錄的寫入過程
- 4.3 運行時庫層日志庫
- 4.4 C/C++日志寫入接口
- 4.5 Java日志寫入接口
- 4.6 Logcat工具分析
- 4.6.1 基礎數據結構
- 4.6.2 初始化過程
- 4.6.3 日志記錄的讀取過程
- 4.6.4 日志記錄的輸出過程
- 第五章 Binder進程間通信系統
- 5.1 Binder驅動程序
- 5.1.1 基礎數據結構
- 5.1.2 Binder設備的初始化過程
- 5.1.3 Binder設備文件的打開過程
- 5.1.4 設備文件內存映射過程
- 5.1.5 內核緩沖區管理
- 5.1.5.1 分配內核緩沖區
- 5.1.5.2 釋放內核緩沖區
- 5.1.5.3 查詢內核緩沖區
- 5.2 Binder進程間通信庫
- 5.3 Binder進程間通信應用實例
- 5.4 Binder對象引用計數技術
- 5.4.1 Binder本地對象的生命周期
- 5.4.2 Binder實體對象的生命周期
- 5.4.3 Binder引用對象的生命周期
- 5.4.4 Binder代理對象的生命周期
- 5.5 Binder對象死亡通知機制
- 5.5.1 注冊死亡接收通知
- 5.5.2 發送死亡接收通知
- 5.5.3 注銷死亡接收通知
- 5.6 Service Manager的啟動過程
- 5.6.1 打開和映射Binder設備文件
- 5.6.2 注冊成為Binder上下文管理者
- 5.6.3 循環等待Client進程請求
- 5.7 Service Manager代理對象接口的獲取過程
- 5.8 Service的啟動過程
- 5.8.1 注冊Service組件
- 5.8.1.1 封裝通信數據為Parcel對象
- 5.8.1.2 發送和處理BC_TRANSACTION命令協議
- 5.8.1.3 發送和處理BR_TRANSACTION返回協議
- 5.8.1.4 發送和處理BC_REPLY命令協議
- 5.8.1.5 發送和處理BR_REPLY返回協議
- 5.8.2 循環等待Client進程請求
- 5.9 Service代理對象接口的獲取過程
- 5.10 Binder進程間通信機制的Java實現接口
- 5.10.1 獲取Service Manager的Java代理對象接口
- 5.10.2 AIDL服務接口解析
- 5.10.3 Java服務的啟動過程
- 5.10.4 獲取Java服務的代理對象接口
- 5.10.5 Java服務的調用過程
- 第六章 Ashmem匿名共享內存系統
- 6.1 Ashmem驅動程序
- 6.1.1 相關數據結構
- 6.1.2 設備初始化過程
- 6.1.3 設備文件打開過程
- 6.1.4 設備文件內存映射過程
- 6.1.5 內存塊的鎖定和解鎖過程
- 6.1.6 解鎖狀態內存塊的回收過程
- 6.2 運行時庫cutils的匿名共享內存接口
- 6.3 匿名共享內存的C++訪問接口
- 6.3.1 MemoryHeapBase
- 6.3.1.1 Server端的實現
- 6.3.1.2 Client端的實現
- 6.3.2 MemoryBase
- 6.3.2.1 Server端的實現
- 6.3.2.2 Client端的實現
- 6.3.3 應用實例
- 6.4 匿名共享內存的Java訪問接口
- 6.4.1 MemoryFile
- 6.4.2 應用實例
- 6.5 匿名共享內存的共享原理分析
- 第三篇 Android應用程序框架篇
- 第七章 Activity組件的啟動過程
- 7.1 Activity組件應用實例
- 7.2 根Activity的啟動過程
- 7.3 Activity在進程內的啟動過程
- 7.4 Activity在新進程中的啟動過程
- 第八章 Service組件的啟動過程
- 8.1 Service組件應用實例
- 8.2 Service在新進程中的啟動過程
- 8.3 Service在進程內的綁定過程
- 第九章 Android系統廣播機制
- 9.1 廣播應用實例
- 9.2 廣播接收者的注冊過程
- 9.3 廣播的發送過程
- 第十章 Content Provider組件的實現原理
- 10.1 Content Provider組件應用實例
- 10.1.1 ArticlesProvider
- 10.1.2 Article
- 10.2 Content Provider組件的啟動過程
- 10.3 Content Provider組件的數據共享原理
- 10.4 Content Provider組件的數據更新通知機制
- 10.4.1 內容觀察者的注冊過程
- 10.4.2 數據更新的通知過程
- 第十一章 Zygote和System進程的啟動過程
- 11.1 Zygote進程的啟動腳本
- 11.2 Zygote進程的啟動過程
- 11.3 System進程的啟動過程
- 第十二章 Android應用程序進程的啟動過程
- 12.1 應用程序進程的創建過程
- 12.2 Binder線程池的啟動過程
- 12.3 消息循環的創建過程
- 第十三章 Android應用程序的消息處理機制
- 13.1 創建線程消息隊列
- 13.2 線程消息循環過程
- 13.3 線程消息發送過程
- 13.4 線程消息處理過程
- 第十四章 Android應用程序的鍵盤消息處理機制
- 14.1 InputManager的啟動過程
- 14.1.1 創建InputManager
- 14.1.2 啟動InputManager
- 14.1.3 啟動InputDispatcher
- 14.1.4 啟動InputReader
- 14.2 InputChannel的注冊過程
- 14.2.1 創建InputChannel
- 14.2.2 注冊Server端InputChannel
- 14.2.3 注冊當前激活窗口
- 14.2.4 注冊Client端InputChannel
- 14.3 鍵盤消息的分發過程
- 14.3.1 InputReader處理鍵盤事件
- 14.3.2 InputDispatcher分發鍵盤事件
- 14.3.3 當前激活的窗口獲得鍵盤消息
- 14.3.4 InputDispatcher獲得鍵盤事件處理完成通知
- 14.4 InputChannel的注銷過程
- 14.4.1 銷毀應用程序窗口
- 14.4.2 注銷Client端InputChannel
- 14.4.3 注銷Server端InputChannel
- 第十五章 Android應用程序線程的消息循環模型
- 15.1 應用程序主線程消息循環模型
- 15.2 界面無關的應用程序子線程消息循環模型
- 15.3 界面相關的應用程序子線程消息循環模型
- 第十六章 Android應用程序的安裝和顯示過程
- 16.1 應用程序的安裝過程
- 16.2 應用程序的顯示過程