在android开发的过程中,需要查看log信息来帮助分析。那么知晓log的原理就是比较重要的了。

Framework 中的Log

Framework中的Log比较简单,主要就是封装接口,在接口中调用println_native函数。下面只以其中的一个进行分析。

public static int v(String tag, String msg) {   if (tag == null) {tag = "NullTag"; } if (msg == null) {msg = "NullMsg"; }return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);}

默认情况下,log id为main,这个ID的作用后面会提到。这个函数会检查tag & msg。之后就是调用println_native。

JNI中的Log

在JNI中的对应文件为:android_util_Log.cpp

 static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,         jint bufID, jint priority, jstring tagObj, jstring msgObj) {int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg); }

在这里需要说明一下BufID & priority

  typedef enum android_LogPriority {      ANDROID_LOG_UNKNOWN = 0,      ANDROID_LOG_DEFAULT,    // only for SetMinPriority()      ANDROID_LOG_VERBOSE,      ANDROID_LOG_DEBUG,      ANDROID_LOG_INFO,      ANDROID_LOG_WARN,      ANDROID_LOG_ERROR,      ANDROID_LOG_FATAL,      ANDROID_LOG_SILENT,     // only for SetMinPriority(); must be last } android_LogPriority; typedef enum {     LOG_ID_MAIN = 0,     LOG_ID_RADIO = 1,     LOG_ID_EVENTS = 2,     LOG_ID_SYSTEM = 3,      LOG_ID_MAX} log_id_t;

上面对应的输入那种等级的打印信息。下面的对应打印哪种log。用过 logcat -b system/main/events/main来进行log输出。

int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg){    struct iovec vec[3];    char tmp_tag[32];    if (!tag)        tag = "";//特殊处理radio的log    /* XXX: This needs to go! */    if ((bufID != LOG_ID_RADIO) &&         (!strcmp(tag, "HTC_RIL") ||        !strncmp(tag, "RIL", 3) || /* Any log tag with "RIL" as the prefix */        !strncmp(tag, "IMS", 3) || /* Any log tag with "IMS" 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;            // Inform third party apps/ril/radio.. to use Rlog or RLOG            snprintf(tmp_tag, sizeof(tmp_tag), "use-Rlog/RLOG-%s", tag);            tag = tmp_tag;    }    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);}

这部分是使用linux中的iov的数据格式进行传递的。最后调用write_to_log函数,是一个函数指针。下面看一下这个函数的定义

static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __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;···    return write_to_log(log_id, vec, nr);}

在java层加载这个库时write_to_log函数指针指向的是__write_to_log_init。这时候就会打开对应的设备。这些设备有

#define LOGGER_LOG_RADIO"log_radio"/* radio-related messages */#define LOGGER_LOG_EVENTS"log_events"/* system/hardware events */#define LOGGER_LOG_SYSTEM"log_system"/* system/framework messages */#define LOGGER_LOG_MAIN"log_main"/* everything else */

设备打开之后,这个函数指针指向__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;}#define log_writev(filedes, vector, count) writev(filedes, vector, count)

最终调用linux中的writev函数。

Log驱动分析

文件路径:common/drivers/staging/android/logger.c

初始化函数分析

驱动初始化函数

#define LOGGER_LOG_RADIO"log_radio"/* radio-related messages */#define LOGGER_LOG_EVENTS"log_events"/* system/hardware events */#define LOGGER_LOG_SYSTEM"log_system"/* system/framework messages */#define LOGGER_LOG_MAIN"log_main"/* everything else */static int __init logger_init(void){int ret;ret = create_log(LOGGER_LOG_MAIN, 256*1024);if (unlikely(ret))goto out;ret = create_log(LOGGER_LOG_EVENTS, 256*1024);if (unlikely(ret))goto out;ret = create_log(LOGGER_LOG_RADIO, 256*1024);if (unlikely(ret))goto out;ret = create_log(LOGGER_LOG_SYSTEM, 256*1024);if (unlikely(ret))goto out;out:return ret;}device_initcall(logger_init);

可以看到驱动中注册的名字与Jni中打开的文件描述符是一一对应的。所以当jni中调用open函数时将会调用驱动中对应的open函数。
再driver中注册的函数有

static const struct file_operations logger_fops = {.owner = THIS_MODULE,.read = logger_read,.aio_write = logger_aio_write,.poll = logger_poll,.unlocked_ioctl = logger_ioctl,.compat_ioctl = logger_ioctl,.open = logger_open,.release = logger_release,};

所以jni中调用vwrite函数时就会调用到logger_aio_write函数。
下面继续分析初始化函数中涉及到的create_log

static int __init create_log(char *log_name, int size){int ret = 0;struct logger_log *log;unsigned char *buffer;//分配size大小的空间,用来存储log信息buffer = vmalloc(size);log = kzalloc(sizeof(struct logger_log), GFP_KERNEL);log->buffer = buffer;log->misc.minor = MISC_DYNAMIC_MINOR;log->misc.name = kstrdup(log_name, GFP_KERNEL);if (log->misc.name == NULL) {ret = -ENOMEM;goto out_free_log;}log->misc.fops = &logger_fops;log->misc.parent = NULL;init_waitqueue_head(&log->wq);INIT_LIST_HEAD(&log->readers);mutex_init(&log->mutex);log->w_off = 0;log->head = 0;log->size = size;INIT_LIST_HEAD(&log->logs);list_add_tail(&log->logs, &log_list);//将log->logs放入到log_list中//注册misc类型的设备/* finally, initialize the misc device for this log */ret = misc_register(&log->misc);···return ret;}

至此,驱动已经初始化完成。可以接受来自应用的调用。

logger_aio_write函数分析
static ssize_t logger_aio_write(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t ppos){struct logger_log *log = file_get_log(iocb->ki_filp);size_t orig;struct logger_entry header;struct timespec now;ssize_t ret = 0;now = current_kernel_time();//保存写用户的相关信息header.pid = current->tgid;header.tid = current->pid;header.sec = now.tv_sec;header.nsec = now.tv_nsec;header.euid = current_euid();header.len = min_t(size_t, iocb->ki_nbytes, LOGGER_ENTRY_MAX_PAYLOAD);header.hdr_size = sizeof(struct logger_entry);···orig = log->w_off;/* * Fix up any readers, pulling them forward to the first readable * entry after (what will be) the new write offset. We do this now * because if we partially fail, we can end up with clobbered log * entries that encroach on readable buffer. */ //在写入log信息之前需要调整readers的一些信息,因为写入的信息有可能会覆盖掉readers正在读入位置的信息fix_up_readers(log, sizeof(struct logger_entry) + header.len);do_write_log(log, &header, sizeof(struct logger_entry));//写入头内容//写入 bodywhile (nr_segs-- > 0) {size_t len;ssize_t nr;/* figure out how much of this vector we can keep */len = min_t(size_t, iov->iov_len, header.len - ret);/* write out this segment's payload */nr = do_write_log_from_user(log, iov->iov_base, len);if (unlikely(nr < 0)) {//如果写入payload的话,就恢复之前的位置log->w_off = orig;mutex_unlock(&log->mutex);return nr;}iov++;ret += nr;}mutex_unlock(&log->mutex);//写完数据之后,唤醒readers继续进行读取/* wake up any blocked readers */wake_up_interruptible(&log->wq);return ret;}

其他

在这部分分析的时候,如何重新定位reader的位置比较模糊。下面将分析时的笔记给出

 /* 如果head/readers指向的位置即将被覆盖的话,调整指针,指向/读取下一个entry */ //log, sizeof(struct logger_entry) + header.lenstatic void fix_up_readers(struct logger_log *log, size_t len){size_t old = log->w_off;size_t new = logger_offset(log, old + len);//new代表的是写入之后的w_off位置struct logger_reader *reader;if (is_between(old, new, log->head))//也就是当前read的位置将要被覆盖log->head = get_next_entry(log, log->head, len);//这种情况对应的应该是head正读取的数据将要被覆盖,所以将head指向下个数据。//上面这条执行完成之后,log->head应该是指向len之后的第一个有效的entry//如果有reading的reader的话,也要一起调整。list_for_each_entry(reader, &log->readers, list)if (is_between(old, new, reader->r_off))reader->r_off = get_next_entry(log, reader->r_off, len);}//获取下一个entry的起始位置static size_t logger_offset(struct logger_log *log, size_t n){return n & (log->size - 1);}/* * do_write_log - writes 'len' bytes from 'buf' to 'log' * 将buf中的内容拷贝到log中 * 这个里面size没有变,所以这个size应该是之的所有buffer的size * The caller needs to hold log->mutex. */static void do_write_log(struct logger_log *log, const void *buf, size_t count){size_t len;len = min(count, log->size - log->w_off);//查看还能写多少memcpy(log->buffer + log->w_off, buf, len);//将最小的写入/** 1,如果count < size - w_off 也就是说buffer中能够放得下。这种情况上面执行完之后 count == len* 2。如果count > size - w_off 也就是说buffer剩余空间放不下,那么应该是要覆盖之前的*/if (count != len)memcpy(log->buffer, buf + len, count - len);log->w_off = logger_offset(log, log->w_off + count);}/* * get_next_entry - return the offset of the first valid entry at least 'len' * bytes after 'off'. * 返回off len bytes之后的第一个有效的 off * Caller must hold log->mutex. *///log, log->head, len // len应该是将要写入数据的lengthstatic size_t get_next_entry(struct logger_log *log, size_t off, size_t len){size_t count = 0;//这个就是要保证有足够的空间保存len长度的数据do {//off位置处对应的entry sizesize_t nr = sizeof(struct logger_entry) +get_entry_msg_len(log, off);//log, log->headoff = logger_offset(log, off + nr);count += nr;} while (count < len);return off;} //获取off位置的entry header //只是获取entry的头部分static struct logger_entry *get_entry_header(struct logger_log *log,size_t off, struct logger_entry *scratch){size_t len = min(sizeof(struct logger_entry), log->size - off);if (len != sizeof(struct logger_entry)) {memcpy(((void *) scratch), log->buffer + off, len);memcpy(((void *) scratch) + len, log->buffer,sizeof(struct logger_entry) - len);return scratch;}return (struct logger_entry *) (log->buffer + off);}

比较难懂的部分,在代码中已经添加注释,此处就不在赘述。
上面就是log驱动的分析。

Logcat命令分析

logcat命令的实现方式比较简单,与logd的实现方式基本相同。下面主要看一下他的main函数:

int main(int argc, char **argv){····for (;;) {····ret = getopt(argc, argv, "cdt:gsQf:r::n:v:b:B");···switch(ret) {//这里面处理命令行里面的参数}····}//如果没有指定查看的log的话,就打开main/system。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++;        }    }····android::setupOutput();//设置log输出到什么位置。一般为STDOUT···dev = devices;    while (dev) {        dev->fd = open(dev->device, mode);···readable = android::getLogReadableSize(dev->fd);···dev = dev->next;···}···if (needBinary)//logcat -b events时为真        android::g_eventTagMap = android_openEventTagMap(EVENT_TAG_MAP_FILE);//读取log信息。    android::readLogLines(devices);}

总体来说步骤如下:
1.打开文件节点。
2.将节点中的信息输出到stdout或者文件中(通过-f指定输出文件)

EventLogTags.logtags

在分析main/system的log时比较容易。通过Log.d/e/w/i等方式就可以将log写入到驱动的缓存中。但是events要比较特殊一些。下面就介绍一下。
在android sdk中存在着EventLogTags.logtags文件,基本格式如下:

frameworks/base/services/java/com/android/server/EventLogTags.logtags  1 # See system/core/logcat/event.logtags for a description of the format of this file.  2   3 option java_package com.android.server  4   5 # ---------------------------  6 # BatteryService.java  7 # ---------------------------  8 2722 battery_level (level|1|6),(voltage|1|1),(temperature|1|1)  9 2723 battery_status (status|1|5),(health|1|5),(present|1|5),(plugged|1|5),(technology|3) 10 # This is logged when battery goes from discharging to charging. 11 # It lets us count the total amount of time between charges and the discharge level 12 2730 battery_discharge (duration|2|3),(minLevel|1|6),(maxLevel|1|6)···

在系统编译时,将会根据这个文件自动生成java文件。以这个为例,生成的java文件路径为:out/target/common/obj/JAVA_LIBRARIES/services_intermediates/src/com/android/server/EventLogTags.java

  1 /* This file is auto-generated.  DO NOT MODIFY.  2  * Source file: frameworks/base/services/java/com/android/server/EventLogTags.logtags  3  */  4   5 package com.android.server;  6   7 /**  8  * @hide  9  */ 10 public class EventLogTags { 11   private EventLogTags() { }  // don't instantiate 12  13   /** 2722 battery_level (level|1|6),(voltage|1|1),(temperature|1|1) */ 14   public static final int BATTERY_LEVEL = 2722; 15  16   /** 2723 battery_status (status|1|5),(health|1|5),(present|1|5),(plugged|1|5),(technology|3) */ 17   public static final int BATTERY_STATUS = 2723; 18  19   /** 2730 battery_discharge (duration|2|3),(minLevel|1|6),(maxLevel|1|6) */ 20   public static final int BATTERY_DISCHARGE = 2730;···211   public static void writeBatteryLevel(int level, int voltage, int temperature) {212     android.util.EventLog.writeEvent(BATTERY_LEVEL, level, voltage, temperature);213   }214 215   public static void writeBatteryStatus(int status, int health, int present, int plugged, String technology) {216     android.util.EventLog.writeEvent(BATTERY_STATUS, status, health, present, plugged, technology);217   }218 219   public static void writeBatteryDischarge(long duration, int minlevel, int maxlevel) {220     android.util.EventLog.writeEvent(BATTERY_DISCHARGE, duration, minlevel, maxlevel);221   }···}

从生成的文件看,还自动生成了函数。那么这些函数的参数是如何生成的呢。
细心观察可以发现,在EventLogTags.logtags里面定义了很多参数,例如:
2722 battery_level (level|1|6),(voltage|1|1),(temperature|1|1)
会根据2722 battery_level生成宏定义/函数名。函数的参数就是根据(level|1|6),(voltage|1|1),(temperature|1|1)生成的。
下面看一下这个是如何定义的

1 # The entries in this file map a sparse set of log tag numbers to tag names.  2 # This is installed on the device, in /system/etc, and parsed by logcat.  3 #  4 # Tag numbers are decimal integers, from 0 to 2^31.  (Let's leave the  5 # negative values alone for now.)  6 # Tag numbers是十进制的整数,取值从02^31  7 # Tag names are one or more ASCII letters and numbers or underscores, i.e.  8 # "[A-Z][a-z][0-9]_".  Do not include spaces or punctuation (the former  9 # impacts log readability, the latter makes regex searches more annoying).10 # Tag names由1到多个ASCII码的字母和下划线组成,为了方便在log中搜索,name中避免使用空格和标点11 # Tag numbers and names are separated by whitespace.  Blank lines and lines12 # starting with '#' are ignored.13 # Tag numbers和names之间用空格隔开,空白和#开头的行会被忽略14 # Optionally, after the tag names can be put a description for the value(s)15 # of the tag. Description are in the format16 #    (<name>|data type[|data unit])     #  根据需要,tag names后面可以加上这个tag的values来描述这个tag的打印格式。17 # Multiple values are separated by commas.18 # values之间用逗号隔开,每个value的格式如下19 # The data type is a number from the following values:20 # 1: int21 # 2: long22 # 3: string23 # 4: list24 # "data unit"表示数据格式,相当于data的单位:25 # The data unit is a number taken from the following list:26 # 1: Number of objects27 # 2: Number of bytes28 # 3: Number of milliseconds29 # 4: Number of allocations30 # 5: Id31 # 6: Percent32 # Default value for data of type int/long is 2 (bytes).33 #34 # TODO: generate ".java" and ".h" files with integer constants from this file.         ... ...137 # NOTE - the range 1000000-2000000 is reserved for partners and others who138 # want to define their own log tags without conflicting with the core platform.# 1000000-2000000的tag number是留给合作厂商或者其他开发者用户扩展用的。

根据注释就能自动生成函数的参数了。

完结~~~

更多相关文章

  1. 安卓复习.Part1
  2. Android(安卓)Overlay学习 一
  3. Android(安卓)应用布局文件的命名规则
  4. [AS3.6.1]Kotlin学习笔记3
  5. Android简单实现app每月签到功能
  6. Android获取文件夹路径 /data/data/
  7. Android(安卓)录音实现方法、仿微信语音、麦克风录音、发送语音
  8. Android——selector背景选择器
  9. 【Android(安卓)Studio】将一个Module直接复制到另一个Project中

随机推荐

  1. 基于android和arduino 的小车控制
  2. Android(安卓)Tv 背景动态改变,带过渡效果
  3. Android(安卓)O 开发者预览版新特性
  4. 转Android系统架构
  5. android Kotlin 委托
  6. Android(安卓)辅助功能(无障碍)自定义开发
  7. android实现分享给好友功能
  8. Android(安卓)创建与解析XML(一)—— 概述
  9. Android编译环境(1) - 编译Native C的hel
  10. 如何降低android应用程序的耗电量