Android的log机制小结

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下。

yutao@yutao:~$adb shell

root@android:/ #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这样的命令来查看各个设备的大小信息

root@android:/ #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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值