在android开发的过程中,需要查看log信息来帮助分析。那么知晓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中的对应文件为: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函数。
文件路径: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;
}
至此,驱动已经初始化完成。可以接受来自应用的调用。
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));//写入头内容
//写入 body
while (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.len
static 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应该是将要写入数据的length
static size_t get_next_entry(struct logger_log *log, size_t off, size_t len)
{
size_t count = 0;
//这个就是要保证有足够的空间保存len长度的数据
do {
//off位置处对应的entry size
size_t nr = sizeof(struct logger_entry) +
get_entry_msg_len(log, off);//log, log->head
off = 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命令的实现方式比较简单,与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指定输出文件)
在分析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是十进制的整数,取值从0到2^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 lines
12 # 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 format
16 # (<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: int
21 # 2: long
22 # 3: string
23 # 4: list
24 # "data unit"表示数据格式,相当于data的单位:
25 # The data unit is a number taken from the following list:
26 # 1: Number of objects
27 # 2: Number of bytes
28 # 3: Number of milliseconds
29 # 4: Number of allocations
30 # 5: Id
31 # 6: Percent
32 # 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 who
138 # want to define their own log tags without conflicting with the core platform.
# 1000000-2000000的tag number是留给合作厂商或者其他开发者用户扩展用的。
根据注释就能自动生成函数的参数了。
完结~~~