Android應用層日誌分析

參考:http://blog.csdn.net/luoshengyang/article/details/6598703

我們在Android應用程序,一般是調用應用程序框架層的Java接口(android.util.Log)來使用日誌系統,這個Java接口通過JNI方法和系統運行庫最終調用內核驅動程序Logger把Log寫到內核空間中。按照這個調用過程,我們一步步介紹Android應用程序框架層日誌系統的源代碼。學習完這個過程之後,我們可以很好地理解Android系統的架構,即應用程序層(Application)的接口是如何一步一步地調用到內核空間的。

一. 應用程序框架層日誌系統Java接口的實現。

frameworks/base/core/java/android/util/Log.java文件中,實現日誌系統的Java接口:

................................................

public final class Log {

................................................

	/**
	 * Priority constant for the println method; use Log.v.
         */
	public static final int VERBOSE = 2;//6個日誌優先級ID

	/**
	 * Priority constant for the println method; use Log.d.
         */
	public static final int DEBUG = 3;

	/**
	 * Priority constant for the println method; use Log.i.
         */
	public static final int INFO = 4;

	/**
	 * Priority constant for the println method; use Log.w.
         */
	public static final int WARN = 5;

	/**
	 * Priority constant for the println method; use Log.e.
         */
	public static final int ERROR = 6;

	/**
	 * Priority constant for the println method.
         */
	public static final int ASSERT = 7;

.....................................................

	public static int v(String tag, String msg) {
		return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
	}

	public static int v(String tag, String msg, Throwable tr) {
		return println_native(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
	}

	public static int d(String tag, String msg) {
		return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
	}

	public static int d(String tag, String msg, Throwable tr) {
		return println_native(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
	}

	public static int i(String tag, String msg) {
		return println_native(LOG_ID_MAIN, INFO, tag, msg);
	}

	public static int i(String tag, String msg, Throwable tr) {
		return println_native(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
	}

	public static int w(String tag, String msg) {
		return println_native(LOG_ID_MAIN, WARN, tag, msg);
	}

	public static int w(String tag, String msg, Throwable tr) {
		return println_native(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
	}

	public static int w(String tag, Throwable tr) {
		return println_native(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
	}
	
	public static int e(String tag, String msg) {
		return println_native(LOG_ID_MAIN, ERROR, tag, msg);
	}

	public static int e(String tag, String msg, Throwable tr) {
		return println_native(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
	}

..................................................................
	/** @hide */ public static native int LOG_ID_MAIN = 0; //4個日誌緩衝區ID
	/** @hide */ public static native int LOG_ID_RADIO = 1;
	/** @hide */ public static native int LOG_ID_EVENTS = 2;
	/** @hide */ public static native int LOG_ID_SYSTEM = 3;

	/** @hide */ public static native int println_native(int bufID,
		int priority, String tag, String msg);
}
定義了2~7一共6個日誌優先級別ID4個日誌緩衝區ID在Logger驅動程序模塊中,定義了log_main、log_events和log_radio三個日誌緩衝區,分別對應三個設備文件/dev/log/main、/dev/log/events和/dev/log/radio。這裏的4個日誌緩衝區的前面3個ID就是對應這三個設備文件的文件描述符了

在下面的章節中,我們將看到這三個文件描述符是如何創建的。在下載下來的Android內核源代碼中,第4個日誌緩衝區LOG_ID_SYSTEM並沒有對應的設備文件,在這種情況下,它和LOG_ID_MAIN對應同一個緩衝區ID,在下面的章節中,我們同樣可以看到這兩個ID是如何對應到同一個設備文件的。

  在整個Log接口中,最關鍵的地方聲明瞭println_native本地方法,所有的Log接口都是通過調用這個本地方法來實現Log的定入。下面我們就繼續分析這個本地方法println_native。

二. 應用程序框架層日誌系統JNI方法的實現。

         在frameworks/base/core/jni/android_util_Log.cpp文件中,實現JNI方法println_native:

/* //device/libs/android_runtime/android_util_Log.cpp
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/

#define LOG_NAMESPACE "log.tag."
#define LOG_TAG "Log_println"

#include <assert.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/String8.h>

#include "jni.h"
#include "utils/misc.h"
#include "android_runtime/AndroidRuntime.h"

#define MIN(a,b) ((a<b)?a:b)

namespace android {

struct levels_t {
    jint verbose;
    jint debug;
    jint info;
    jint warn;
    jint error;
    jint assert;
};
static levels_t levels;

static int toLevel(const char* value) 
{
    switch (value[0]) {
        case 'V': return levels.verbose;
        case 'D': return levels.debug;
        case 'I': return levels.info;
        case 'W': return levels.warn;
        case 'E': return levels.error;
        case 'A': return levels.assert;
        case 'S': return -1; // SUPPRESS
    }
    return levels.info;
}

static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
#ifndef HAVE_ANDROID_OS
    return false;
#else /* HAVE_ANDROID_OS */
    int len;
    char key[PROPERTY_KEY_MAX];
    char buf[PROPERTY_VALUE_MAX];

    if (tag == NULL) {
        return false;
    }
    
    jboolean result = false;
    
    const char* chars = env->GetStringUTFChars(tag, NULL);

    if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
        jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
        char buf2[200];
        snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %d characters\n",
                chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));

        // release the chars!
        env->ReleaseStringUTFChars(tag, chars);

        env->ThrowNew(clazz, buf2);
        return false;
    } else {
        strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);
        strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);
    }
    
    env->ReleaseStringUTFChars(tag, chars);

    len = property_get(key, buf, "");
    int logLevel = toLevel(buf);
    return (logLevel >= 0 && level >= logLevel) ? true : false;
#endif /* HAVE_ANDROID_OS */
}

/*
 * In class android.util.Log:
 *  public static native int println_native(int buffer, int priority, String tag, String msg)
 */
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
        jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
    const char* tag = NULL;
    const char* msg = NULL;

    if (msgObj == NULL) {
        jclass npeClazz;

        npeClazz = env->FindClass("java/lang/NullPointerException");
        assert(npeClazz != NULL);

        env->ThrowNew(npeClazz, "println needs a message");
        return -1;
    }

    if (bufID < 0 || bufID >= LOG_ID_MAX) {
        jclass npeClazz;

        npeClazz = env->FindClass("java/lang/NullPointerException");
        assert(npeClazz != NULL);

        env->ThrowNew(npeClazz, "bad bufID");
        return -1;
    }

    if (tagObj != NULL)
        tag = env->GetStringUTFChars(tagObj, NULL);
    msg = env->GetStringUTFChars(msgObj, NULL);

    int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);

    if (tag != NULL)
        env->ReleaseStringUTFChars(tagObj, tag);
    env->ReleaseStringUTFChars(msgObj, msg);

    return res;
}

/*
 * JNI registration.
 */
static JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};

int register_android_util_Log(JNIEnv* env)
{
    jclass clazz = env->FindClass("android/util/Log");

    if (clazz == NULL) {
        LOGE("Can't find android/util/Log");
        return -1;
    }
    
    levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
    levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
    levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
    levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
    levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
    levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
                
    return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
}

}; // namespace android
在gMethods變量中,定義了println_native本地方法對應的函數調用是android_util_Log_println_native。在android_util_Log_println_native函數中,通過了各項參數驗證正確後,就調用運行時庫函數__android_log_buf_write來實現Log的寫入操作。__android_log_buf_write函實實現在liblog庫中,它有4個參數,分別緩衝區ID、優先級別ID、Tag字符串和Msg字符串。下面運行時庫liblog中的__android_log_buf_write的實現。
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
{
    struct iovec vec[3];

    if (!tag)
        tag = "";

    /* XXX: This needs to go! */
    if (!strcmp(tag, "HTC_RIL") ||
        !strncmp(tag, "RIL", 3) || /* Any log tag with "RIL" as the prefix */
        !strcmp(tag, "AT") ||
        !strcmp(tag, "GSM") ||
        !strcmp(tag, "STK") ||
        !strcmp(tag, "CDMA") ||
        !strcmp(tag, "PHONE") ||
        !strcmp(tag, "SMS"))
            bufID = LOG_ID_RADIO;

    vec[0].iov_base   = (unsigned char *) &prio;
    vec[0].iov_len    = 1;
    vec[1].iov_base   = (void *) tag;
    vec[1].iov_len    = strlen(tag) + 1;
    vec[2].iov_base   = (void *) msg;
    vec[2].iov_len    = strlen(msg) + 1;

    return write_to_log(bufID, vec, 3);
}
函數首先是檢查傳進來的tag參數是否是爲HTC_RIL、RIL、AT、GSM、STK、CDMA、PHONE和SMS中的一個,如果是,就無條件地使用ID爲LOG_ID_RADIO的日誌緩衝區作爲寫入緩衝區,接着,把傳進來的參數prio、tag和msg分別存放在一個向量數組中,調用write_to_log函數來進入下一步操作。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_init函數:

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);
}
這裏我們可以看到,如果是第一次調write_to_log函數,write_to_log == __write_to_log_init判斷語句就會true,於是執行log_open函數打開設備文件,並把文件描述符保存在log_fds數組中。如果打開/dev/LOGGER_LOG_SYSTEM文件失敗,即log_fds[LOG_ID_SYSTEM] < 0,就把log_fds[LOG_ID_SYSTEM]設置爲log_fds[LOG_ID_MAIN],這就是我們上面描述的如果不存在ID爲LOG_ID_SYSTEM的日誌緩衝區,就把LOG_ID_SYSTEM設置爲和LOG_ID_MAIN對應的日誌緩衝區了。LOGGER_LOG_MAIN、LOGGER_LOG_RADIO、LOGGER_LOG_EVENTS和LOGGER_LOG_SYSTEM四個宏定義在system/core/include/cutils/logger.h文件中:

#define LOGGER_LOG_MAIN		"log/main"
#define LOGGER_LOG_RADIO	"log/radio"
#define LOGGER_LOG_EVENTS	"log/events"
#define LOGGER_LOG_SYSTEM	"log/system"
接着,把write_to_log函數指針指向__write_to_log_kernel函數:
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;
}
 函數調用log_writev來實現Log的寫入,注意,這裏通過一個循環來寫入Log,直到寫入成功爲止。這裏log_writev是一個宏,在文件開始的地方定義爲:
#if FAKE_LOG_DEVICE
// This will be defined when building for the host.
#define log_open(pathname, flags) fakeLogOpen(pathname, flags)
#define log_writev(filedes, vector, count) fakeLogWritev(filedes, vector, count)
#define log_close(filedes) fakeLogClose(filedes)
#else
#define log_open(pathname, flags) open(pathname, flags)
#define log_writev(filedes, vector, count) writev(filedes, vector, count)
#define log_close(filedes) close(filedes)
#endif
這裏,我們看到,一般情況下,log_writev就是writev了,這是個常見的批量文件寫入函數,就不多說了。

       至些,整個調用過程就結束了。

總結一下,

首先是從應用程序層調用應用程序框架層的Java接口,應用程序框架層的Java接口通過調用本層的JNI方法進入到系統運行庫層的C接口,系統運行庫層的C接口通過設備文件來訪問內核空間層的Logger驅動程序。這是一個典型的調用過程,很好地詮釋Android的系統架構






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章