Android的log机制小结

本文是对Android的log机制学习的小结,内容包括对log框架的理解、写log、读log和log的驱动这几个部分。

一、log框架

log机制包括三部分:底层驱动、读和写。关于写log,我们可以在Java文件中,或者jni层的C/C++代码中添加类似log.d()这样的代码来实现写log,通过logcat命令来输出我们想要查看的log,驱动的任务则是真正地帮助我们实现读和写。

http://blog.csdn.net/mickeyfirst/article/details/6233885

引用这张图来说明log的框架。我们在JavaProgram或者Native Program中通过android.util.Log或者System.out或者log.h为我们提供的一下方法如Log.d或者ALOGD,就能把log添加进去,最终这些log都被底层驱动写到了如下四个设备中:

/dev/log/main

/dev/log/radio

/dev/log/event

/dev/log/system

之后我们可以通过logcat命令或者Eclipse中的logcat来查看和过滤log,不过这些最本质上是调用logcat命令,Eclipse中的logcat或者adblogcat命令都是对通过adbd进程远程调用logcat命令罢了。这就是基本的框架,下面将详细分析各个部分。

二、写log

这里我们把写log的方式简单分为两类:Javaprogram和Native Program。首先来看Java层的log写入。以最简单的android.util.Log为例。

public staticfinal int VERBOSE = 2;public static final int DEBUG = 3;public static final int INFO = 4;public static final int WARN = 5;public static final int ERROR = 6;publicstatic final int ASSERT = 7;
log分级别,对应的数字越大,则级别越高,这在过滤log时会用到。这个文件提供了许多公共的静态方法,我们可以直接通过类名调用。下面是Log.d()方法:

publicstatic int d(String tag, String msg) {

return println_native(LOG_ID_MAIN,DEBUG, tag, msg);

}

这个方法大家都很熟悉,不再叙述,只请注意一点:LOG_ID_MAIN

这个类中的其它方法最终也会调用这个本地方法:

publicstatic native int println_native(int bufID, int priority, String tag, Stringmsg);

它的实现则在jni层:android_util_Log.cpp文件中,有如下定义:

staticJNINativeMethod gMethods[] = {    { "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 },};
从这里可以看出,println_native这个方法对应的是该文件中的android_util_Log_println_native方法。继续看这个方法,发现它最终调用logd_write.c文件中__android_log_buf_write方法,调用层次如下:

__android_log_buf_write

write_to_log

__write_to_log_init

__write_to_log_kernel

log_writev

writev

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);#defineLOGGER_LOG_MAIN          "log/main"#defineLOGGER_LOG_RADIO              "log/radio"#defineLOGGER_LOG_EVENTS     "log/events"#defineLOGGER_LOG_SYSTEM     "log/system"
这些调用过程比较简单,在不断调用的过程中也对数据进行了一些判断和处理。这里需要说的是,一般我们使用Log.d方法输出的log,都会写入到/dev/log/main中,因为这个函数定义中传入了 LOG_ID_MAIN这个参数。而对于以slog.d这种方式添加的log,一般都写到了/dev/log/system,因为它的定义如下:

publicstatic int d(String tag, String msg) {

return Log.println_native(Log.LOG_ID_SYSTEM,Log.DEBUG, tag, msg);

}

而/dev/log/radio中一般都是些和通讯相关的log会保存在这里,/dev/log/event则保存了一些事件相关的log,再具体的我不是很清楚。

我们通过一步步跟踪方法调用,最终找到了writev这个方法。这里先留个疑问,接下来再看C/C++文件中使用log。以ALOGD方法为例:

system/core/include/cutils/log.h

ALOGD

(void)ALOG(LOG_DEBUG,LOG_TAG, __VA_ARGS__)

LOG_PRI(ANDROID_##priority,tag, __VA_ARGS__)

android_printLog(priority,tag, __VA_ARGS__)

__android_log_print(prio,tag, fmt)

__android_log_print

__android_log_write

write_to_log

……

writev

最终也会调用writev这个方法(在开始写log之前还调用了open这个方法去打开设备),但是再向下就不能继续跟踪了,之后就会执行驱动层的相关代码。在调用驱动层的代码时,会根据传入的filedes这个参数找到对应设备的驱动,这里这个值为/dev/log/main对应的那个设备。

三、log的底层驱动

相关文件位置:

kernel/drivers/staging/android/logger.c

kernel/drivers/staging/android/logger.h

首先,前面已多次提到,被驱动的设备有四个,位置在dev/log下。

[email protected]:~$adb shell

[email protected]:/ #ll dev/log

crw-rw-rw-root log 10,46 2013-01-01 08:00 events

crw-rw-rw-root log 10,47 2013-01-01 08:00 main

crw-rw-rw-root log 10,45 2013-01-01 08:00 radio

crw-rw-rw-root log 10,44 2013-01-01 08:00 system

其中的c 标示这是一个字符设备文件characterdevice ,也表示硬件设备,但是数据是以字节流发送的,这些设备包括终端设备和串口设备。我们不能利用adb pull命令将其取出来。这里的10是主设备号,表明它是misc设备。我们可以通过logcat–g –b main这样的命令来查看各个设备的大小信息

[email protected]:/ #logcat -g -b main

/dev/log/main:ring buffer is 256Kb (255Kb consumed), max entry is 5120b, max payload is 4076b

这里显示/dev/log/main这个设备的buffer大小为256Kb,没条log包含的内容最大为4076b。我们不断地向这个设备中写log,当它的buffer不够用时,它就会从头开始写,也意味着之前的log就会被覆盖掉,这些可以在代码中看到。

然后,我们来看设备驱动的主要工作。我对驱动的理解比较肤浅,就是应该要做一些初始化的工作,然后提供读和写的借口给上层,所以我也只关注这些内容。关于初始化工作,请看如下代码:

static conststruct 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,};
这里声明了一个static结构体变量logger_fops,它是file_operations结构体,这个file_operations结构体似乎在linux系统中很常见,这里我们只需要知道它为上层调用提供了接口。举例:上层想要往这个设备文件里写内容的话,就可以调用read方法,当然在使用read方法时还传入了一个与设备名称绑定的值,可能是 LOG_ID_MAIN这样的值,它指向了/dev/log/main这个设备,因此就会调用我们这个驱动的与read关联的方法,就是logger_read。而如果用户调用了aio_write方法时,就会执行这里的logger_aio_write方法。不过logger_aio_write方法的注释中有提到,当用户调用aio_write、write或者writev方法时,都会执行logger_aio_write方法。这首先解释了我们在之前的疑问:在logd_write.c文件中最终走到了writev之后就再看不到更底层的代码了,现在知道是到了驱动层。至于为何使用哪个write方法都会走这里,我也不清楚,猜测是不是因为它只提供了一个write方法,所以只能走这里。

下面的代码是四个设备的创建(个人理解):

#defineDEFINE_LOGGER_DEVICE(VAR, NAME, SIZE) \static unsignedchar _buf_ ##VAR[SIZE]; \static structlogger_log VAR = { \.buffer = _buf_ ## VAR, \.misc = { \        .minor = MISC_DYNAMIC_MINOR, \        .name = NAME, \        .fops = &logger_fops, \        .parent = NULL, \}, \.wq = __WAIT_QUEUE_HEAD_INITIALIZER(VAR .wq), \.readers = LIST_HEAD_INIT(VAR .readers), \.mutex = __MUTEX_INITIALIZER(VAR .mutex), \.w_off = 0, \.head = 0, \.size = SIZE, \}; DEFINE_LOGGER_DEVICE(log_main,LOGGER_LOG_MAIN,256*1024)DEFINE_LOGGER_DEVICE(log_events,LOGGER_LOG_EVENTS,256*1024)DEFINE_LOGGER_DEVICE(log_radio,LOGGER_LOG_RADIO,256*1024)DEFINE_LOGGER_DEVICE(log_system,LOGGER_LOG_SYSTEM,256*1024)
这里每个设备的buffer大小都是256Kb,它们都是misc设备,且在.fops= &logger_fops中指明了上层调用这个设备的关联的方法或接口为刚才提到的logger_fops静态结构体变量。

device_initcall(logger_init);这里指明初始化方法为logger_init,之后调用init_log方法,通过misc_register这个方法来注册misc设备,这里的细节我也不清楚。

这些应该就是初始化工作了,稍微总结一下就能得到如下关心的结果:当上层调用read、writev和open方法时,就会执行这里的logger_read、logger_aio_write和logger_open方法。当然,在调用时一定也在参数中指明了操作的设备是哪个,这可以通过类似LOG_ID_MAIN这样的量来知名。了解这些就够了。

为了更深入地了解对log的操作,我们来简单分析下logger_aio_write中的关键代码。

log的读取和写入都是以一条记录为单位进行的,每条记录包含的内容都在logger_entry这个结构体中定义,包含进程和线程ID,时间,target和输出信息等内容。可是我们知道,我们在添加log时只是指定了优先级、target和输出信息这些内容,并未给出进程和线程id、时间等信息,这些都是在这个方法中添加的:

structlogger_entry header;

struct timespecnow;

now =current_kernel_time();

header.pid = current->tgid; //进程id

header.tid = current->pid; //线程id

header.sec = now.tv_sec; //时间s

header.nsec = now.tv_nsec; //时间ns

header.euid = current_euid();

header.len = min_t(size_t, iocb->ki_left,LOGGER_ENTRY_MAX_PAYLOAD);

header.hdr_size = sizeof(struct logger_entry);

target和输出内容等信息已经被包装在了structiovec *iov中,所以只要将这里的信息和header中的内容写入到设备的buf中即可:log->buffer。关于这些结构体各项代表的内容有兴趣的可以仔细研究。这里的buffer就是之前在创建设备时申请的具有256Kb大小的数组。

真正写log的方法是do_write_log,

do_write_log(log,&header, sizeof(struct logger_entry));

nr =do_write_log_from_user(log, iov->iov_base, len);

多次调用这个方法将header和iov中的信息写入到设备的buffer中。

static ssize_tdo_write_log_from_user(struct logger_log *log, const void __user *buf, size_tcount)//log就代表了设备,我们需要将信息写入到设备的buffer中:log->buffer//buf则存储了log信息//count为log信息的大小{size_t len; len = min(count, log->size - log->w_off);//log->size为buffer的大小//log->w_off为当前缓冲区偏移量//此处比较count(log大小)与缓冲区剩余字节(log->size- log->w_off),取其小者首先复制if (len &©_from_user(log->buffer + log->w_off, buf, len))        return -EFAULT; //如果这里不相等,则意味着缓冲区剩余空间比log的字节数小,因此剩余的log信息则需要写到缓冲区的最前面if (count != len)        if (copy_from_user(log->buffer, buf +len, count - len))               return -EFAULT;//重新定向buffer的偏移量log->w_off = logger_offset(log,log->w_off + count); return count;}
这部分分析我们可以得出如下结论:每个设备有256Kb的缓冲区域,新的log信息会一条条地依次写入到缓冲区,当缓冲区满后,则又从缓冲区的头开始继续写,因此,最前面的log就会被覆盖。但是每条log的大小也有限制:

#defineLOGGER_ENTRY_MAX_PAYLOAD 4076

#define LOGGER_ENTRY_MAX_LEN(5*1024)

四、读log

log的读取则由logcat命令来完成。相关文件为system/core/logcat/logcat.cpp。logcat命令可以过滤和查看log信息,关于它的使用这里不提。logcat命令的入口函数当然是该文件的main方法。实际读取log的方法是readLogLines方法,当然最终还是要调用我们前面提到的read方法:

ret =read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);

一般地当我们输出log时,输出的是/dev/log/main设备中的log信息,如果想输出其它三个设备的log,可以使用-bradio参数来输出radio设备中的信息。

对于adblogcat命令输出log和Eclipse中的logcat输出log,它实际也是使用logcat命令,只是那是通过一些其它方式来间接地调用而已。

这部分只能分享到这里,更多的细节请大家自己学习。

五、小结

根据个人片面的理解,Android的log机制主要包括三部分:读、写和驱动,而驱动是核心。但是我们真正需要熟练掌握的应该是读和写。由于log机制贯穿了应用层、framework层和驱动层,学习它对于我们深入学习android还是有帮助的。

相关文档可以参考如下链接:

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

http://blog.csdn.net/thl789/article/details/6629905

更多相关文章

  1. Dalvik虚拟机JNI方法的注册过程分析
  2. [置顶] Android(安卓)fragment 只让一个fragment支持横屏
  3. Android(安卓)Service学习之AIDL, Parcelable和远程服务
  4. Android绘图机制(三) ViewGroup类的延伸
  5. Android(安卓)数据库SQLite更新升级问题
  6. Android自己主动化測试——CTS測试
  7. Android上使用OpenGL画3D菱形
  8. android中非阻塞socket通信
  9. android 下使用GPS 无法获取经纬度的解决方法

随机推荐

  1. Android之Touch事件分发机制
  2. Android音乐播放器简单示例
  3. Android(安卓)计时器,定时功能
  4. android 图片LOMO效果
  5. Android(安卓)kotlin使用注解实现防按钮
  6. android项目源码异步加载远程图片的小例
  7. Android(安卓)Studio 使用NDK编译时常见
  8. Android中的设计模式--建造者模式
  9. Android(安卓)使用finalBitmap实现缓存读
  10. android开机自启动的后台Service的实现 .