分析安卓的Logger机制

一、概述



Logger机制是在Android系统中提供的一个轻量级的日志系统,这个日志系统是以驱动程序的形式在内核空间实现的,在用户空间分别提供了Java接口和C/C++接口来使用这个日志系统,使用的接口取决于编写的是Android应用程序还是系统组件。

以下我准备从应用开发和源代码分析两部分来分析安卓的Logger机制。

二、从Android应用程序开发角度看Logger机制



这一部分将简要地介绍一下在Android应用程序开发中Log的用法

2.1概述



在程序开发过程中,LOG是广泛使用的用来记录程序运行过程的机制。它既能够用于程序调试,也能够用于产品运营中的事件记录。

在Android系统中,提供了简单、便利的LOG机制。开发者能够方便地使用。以下我将介绍在Android内核空间和用户空间中LOG的使用和查看方法。

2.2内核开发时LOG的使用



Android内核是基于Linux Kernel 2.36的。因此,Linux
Kernel的LOG机制相同适合于Android内核,这就是与C语言的printf齐名的printk。与printf类似。printk提供格式化输入功能,同一时候,它也具有全部LOG机制的特点——提供日志级别过虑功能。

printk提供了8种日志级别(<linux/kernel.h>):

#define KERN_EMERG "<0>"  
/* system is unusable          */

#define KERN_ALERT "<1>"  
/* action must be taken immediately */

#define KERN_CRIT  "<2>"  
/* critical conditions         */

#deinfe KERN_ERR   "<3>"  
/* error conditions         */

#deinfe KERN_WARNING   "<4>"  
/* warning conditions          */

#deinfe KERN_NOTICE "<5>"  /* normal but significant condition */

#deinfe KERN_INFO  "<6>"  
/* informational            */

#deinfe KERN_DEBUG "<7>"  
/* debug-level messages        */

printk的用法:

//KERN_ALERT表示日志级别,后面紧跟着格式化字符串。

printk(KERN_ALERT"This
is the log printed by printk in linux kernel space.");

在Android系统中,printk输出的日志信息保存在/proc/kmsg中,要查看/proc/kmsg的内容,须要在后台中执行模拟器:

USER-NAME@MACHINE-NAME:~/Android$ emulator &

启动adb shell工具:

USER-NAME@MACHINE-NAME:~/Android$ adb shell

查看/proc/kmsg文件:

root@android:/# cat  /proc/kmsg

2.3用户空间程序开发时LOG的使用



Android系统在用户空间中提供了轻量级的logger日志系统。它是在内核中实现的一种设备驱动,与用户空间的logcat工具配合使用可以方便地跟踪调试程序。在Android系统中,分别为C/C++和Java语言提供两种不同的logger訪问接口。

C/C++日志接口通常是在编写硬件抽象层模块或者编写JNI方法时使用,而Java接口通常是在应用层编写APP时使用。

2.31 C/C++日志接口



Android系统中的C/C++日志接口是通过宏来使用的。

在system/core/include/android/log.h定义了日志的级别:

/*

* Android log priority values, in ascending priority order.

*/

typedefenum
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;

在system/core/include/cutils/log.h中。定义了相应的宏。

如相应于ANDROID_LOG_VERBOSE的宏LOGV:

/*

* This is the local tag used for the following simplified

* logging macros. You can change this preprocessor definition

* before using the other macros to change the tag.

*/

#ifndef LOG_TAG

#define LOG_TAG NULL

#endif

/*

* Simplified macro to send a verbose log message using the current LOG_TAG.

*/

#ifndef LOGV

#if LOG_NDEBUG

#define LOGV(...)  ((void)0)

#else

#define LOGV(...)  ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))

#endif

#endif

/*

* Basic log message macro.

*

* Example:

* LOG(LOG_WARN, NULL, "Failed with error %d", errno);

*

* The second argument may be NULL or "" to indicate the "global" tag.

*/

#ifndef LOG

#define LOG(priority, tag, ...) \

LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)

#endif

/*

* Log macro that allows you to specify a number for priority.

*/

#ifndef LOG_PRI

#define LOG_PRI(priority, tag, ...) \

android_printLog(priority, tag, __VA_ARGS__)

#endif

/*

* ================================================================

*

* The stuff in the rest of this file should not be used directly.

*/

#define android_printLog(prio, tag, fmt...) \

__android_log_print(prio, tag, fmt)

因此。假设要使用C/C++日志接口,仅仅要定义自己的LOG_TAG宏和包括头文件system/core/include/cutils/log.h就能够了:

#define LOG_TAG "MY LOG TAG"

#include <cutils/log.h>

比如使用LOGV:

LOGV("This is the log printed by LOGV in androiduser space.");

2.32 JAVA日志接口



Android系统在Frameworks层中定义了Log接口(frameworks/base/core/java/android/util/Log.java):

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

publicfinalclass
Log{

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

/**

* Priority constant for the println method; use Log.v.

*/

publicstaticfinalint
VERBOSE=2;

/**

* Priority constant for the println method; use Log.d.

*/

publicstaticfinalint
DEBUG=3;

/**

* Priority constant for the println method; use Log.i.

*/

publicstaticfinalint
INFO=4;

/**

* Priority constant for the println method; use Log.w.

*/

publicstaticfinalint
WARN=5;

/**

* Priority constant for the println method; use Log.e.

*/

publicstaticfinalint
ERROR=6;

/**

* Priority constant for the println method.

*/

publicstaticfinalint
ASSERT=7;

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

publicstaticint
v(String tag,
String msg){

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

}

publicstaticint
v(String tag,
String msg, Throwable tr){

return
println_native(LOG_ID_MAIN,
VERBOSE, tag,
msg +'\n'+
getStackTraceString(tr));

}

publicstaticint
d(String tag,
String msg){

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

}

publicstaticint
d(String tag,
String msg, Throwable tr){

return
println_native(LOG_ID_MAIN,
DEBUG, tag,
msg +'\n'+
getStackTraceString(tr));

}

publicstaticint
i(String tag,
String msg){

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

}

publicstaticint
i(String tag,
String msg, Throwable tr){

return
println_native(LOG_ID_MAIN,
INFO, tag,
msg +'\n'+
getStackTraceString(tr));

}

publicstaticint
w(String tag,
String msg){

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

}

publicstaticint
w(String tag,
String msg, Throwable tr){

return
println_native(LOG_ID_MAIN,
WARN, tag,
msg +'\n'+
getStackTraceString(tr));

}

publicstaticint
w(String tag,
Throwable tr){

return
println_native(LOG_ID_MAIN,
WARN, tag,
getStackTraceString(tr));

}

publicstaticint
e(String tag,
String msg){

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

}

publicstaticint
e(String tag,
String msg, Throwable tr){

return
println_native(LOG_ID_MAIN,
ERROR, tag,
msg +'\n'+
getStackTraceString(tr));

}

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

/**@hide
*/publicstaticnativeint
println_native(int
bufID,

int
priority, String tag,
String msg);

}

因此。假设要使用Java日志接口,仅仅要在类中定义的LOG_TAG常量和引用android.util.Log就能够了:

private static final String LOG_TAG ="MY_LOG_TAG";

Log.i(LOG_TAG, "This is the log printed by Log.i inandroid user space.");

Log.e(LOG_TAG, "This is the logprinted by Log.e in android user space.");

要查看这些LOG的输出。能够配合logcat工具。假设是在Eclipse环境(ADT)下执行。直接在Eclipse就能够查看了:

假设是在自己编译的Android源码project中使用,则在后台中执行模拟器:

USER-NAME@MACHINE-NAME:~/Android$ emulator &

       启动adb shell工具:

USER-NAME@MACHINE-NAME:~/Android$ adb shell

       使用logcat命令查看日志:

root@android:/ # logcat

       这样就能够看到输出的日志了。

以上是Logger在应用开发中的使用的简单分析,除了Logger在应用开发中的使用,我将更进一步地分析Logger驱动程序的源码,更加深刻的认识Android日志系统。

三、从Logger驱动程序源码看Logger机制



3.1概述



由于Android日志系统是以驱动程序的形式实如今内核空间的,所以须要获取Android内核源码来分析。在下载好Android源码project中,Logger驱动程序主要由两个文件构成,各自是:

kernel/common/drivers/staging/android/logger.h

kernel/common/drivers/staging/android/logger.c

接下来。我将首先分析Logger驱动程序的相关数据结构,然后对Logger驱动程序源码的使用情景进行分析,比方日志系统初始化情景、日志读取情景和日志写入情景。

3.2Logger驱动程序的相关数据结构



3.21 logger.h源码



#ifndef _LINUX_LOGGER_H

#define _LINUX_LOGGER_H

#include <linux/types.h>

#include <linux/ioctl.h>

struct
logger_entry {

__u16      
len;   /*
length of the payload */

__u16      
__pad; /*
no matter what, we get 2 bytes of padding */

__s32      
pid;   /*
generating process's pid */

__s32      
tid;   /*
generating process's tid */

__s32      
sec;   /*
seconds since Epoch */

__s32      
nsec;  /*
nanoseconds */

char       msg[0];/*
the entry's payload */

};

#define LOGGER_LOG_RADIO   "log_radio"
/* radio-related messages */

#define LOGGER_LOG_EVENTS  "log_events"   
/* system/hardware events */

#define LOGGER_LOG_MAIN    "log_main" 
/* everything else */

#define LOGGER_ENTRY_MAX_LEN       (4*1024)

#define LOGGER_ENTRY_MAX_PAYLOAD   \

(LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))

#define __LOGGERIO 0xAE

#define LOGGER_GET_LOG_BUF_SIZE    _IO(__LOGGERIO, 1)
/* size of log */

#define LOGGER_GET_LOG_LEN     _IO(__LOGGERIO, 2)
/* used log len */

#define LOGGER_GET_NEXT_ENTRY_LEN  _IO(__LOGGERIO, 3)
/* next entry len */

#define LOGGER_FLUSH_LOG       _IO(__LOGGERIO, 4)
/* flush log */

#endif/*
_LINUX_LOGGER_H */

struct logger_entry是一个用于描写叙述一条Log记录的结构体。

len成员变量记录了这条记录的有效负载的长度,有效负载指定的日志记录本身的长度。可是不包含用于描写叙述这个记录的struct
logger_entry结构体。我们调用android.util.Log接口来使用日志系统时,会指定日志的优先级别Priority、Tag字符串以及Msg字符串,Priority + Tag + Msg三者内容的长度加起来就是记录的有效负载长度。

__pad成员变量是用来对齐结构体的。

pid和tid成员变量分别用来记录是哪条进程写入了这条记录。

sec和nsec成员变量记录日志写的时间。

msg成员变量记录有效负载的内容,它的大小由len成员变量来确定。

接着还定义了两个宏:

#define LOGGER_ENTRY_MAX_LEN       (4*1024)

#define LOGGER_ENTRY_MAX_PAYLOAD   \

(LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))

从这两个宏能够看出,每条日志记录的有效负载长度加上结构体logger_entry的长度不能超过4K个字节。

logger.h文件里还定义了其他宏。这里就不一一分析了。

再来看logger.c文件里。其他相关数据结构的定义:

3.22 logger.c中的相关数据结构



/*

* struct logger_log - represents a specific log, such as 'main' or 'radio'

*

* This structure lives from module insertion until module removal, so it does

* not need additional reference counting. The structure is protected by the

* mutex 'mutex'.

*/

struct
logger_log {

unsignedchar*    buffer;/*
the ring buffer itself */

struct
miscdevice  misc;  /*
misc device representing the log */

wait_queue_head_t  
wq;/*
wait queue for readers */

struct
list_head   readers;/*
this log's readers */

struct
mutex       mutex; /*
mutex protecting buffer */

size_t         
w_off; /*
current write head offset */

size_t         
head;  /*
new readers start here */

size_t         size;  /*
size of the log */

};

/*

* struct logger_reader - a logging device open for reading

*

* This object lives from open to release, so we don't need additional

* reference counting. The structure is protected by log->mutex.

*/

struct
logger_reader {

struct
logger_log* log;   /*
associated log */

struct
list_head   list;  /*
entry in logger_log's list */

size_t         r_off; /*
current read head offset */

};

/* logger_offset - returns index 'n' into the log via (optimized) modulus */

#define logger_offset(n)   ((n) & (log->size - 1))

struct logger_log是真正用来保存日志的结构体。

buffer成员变量是用于保存日志信息的内存缓冲区,它的大小由size成员变量确定。

从misc成员变量能够看出,logger驱动程序使用的设备属于misc类型的设备。通过在Android模拟器上运行cat
/proc/devices命令。能够看出。misc类型设备的主设备号是10。

wq成员变量是一个等待队列,用于保存正在等待读取日志的进程。

readers成员变量用来保存当前正在读取日志的进程,正在读取日志的进程由结构体logger_reader来描写叙述。

mutex成员变量是一个相互排斥量,用来保护log的并发訪问。由于能够看出,这里的日志系统的读写问题,事实上是一个生产者-消费者的问题。因此,须要相互排斥量来保护log的并发訪问。

w_off成员变量用来记录下一条日志应该从哪里開始写。

head成员变量用来表示打开日志文件里。应该从哪一个位置開始读取日志。

struct logger_reader是用来表示一个读取日志的进程的结构体,log成员变量指向要读取的日志缓冲区。list成员变量用来连接其他读者进程。

r_off成员变量表示当前要读取的日志在缓冲区中的位置。

struct logger_log结构体中用于保存日志信息的内存缓冲区buffer是一个循环使用的环形缓冲区,缓冲区中保存的内容是以struct
logger_entry为单位的,每一个单位的组成为:

structlogger_entry | priority | tag | msg

因为是内存缓冲区buffer是一个循环使用的环形缓冲区,给定一个偏移值,它在buffer中的位置由下logger_offset来确定:

#definelogger_offset(n)          ((n) & (log->size -1))

3.3Logger驱动程序的初始化过程



logger.c文件里定义了三个日志设备:

/*

* Defines a log structure with name 'NAME' and a size of 'SIZE' bytes, which

* must be a power of two, greater than LOGGER_ENTRY_MAX_LEN, and less than

* LONG_MAX minus LOGGER_ENTRY_MAX_LEN.

*/

#define DEFINE_LOGGER_DEVICE(VAR, NAME, SIZE) \

static unsigned char _buf_ ## VAR[SIZE]; \

static struct logger_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,64*1024)

DEFINE_LOGGER_DEVICE(log_events,
LOGGER_LOG_EVENTS,256*1024)

DEFINE_LOGGER_DEVICE(log_radio,
LOGGER_LOG_RADIO,64*1024)

各自是log_main、log_events和log_radio,名称分别LOGGER_LOG_MAIN、LOGGER_LOG_EVENTS和LOGGER_LOG_RADIO,它们的次设备号为MISC_DYNAMIC_MINOR,即为在注冊时动态分配。

在logger.h文件里。有这三个宏的定义:

#defineLOGGER_LOG_RADIO      
"log_radio"    /* radio-related messages */

       #define LOGGER_LOG_EVENTS      "log_events" /* system/hardware events */

       #define LOGGER_LOG_MAIN         "log_main"    /* everything else */

凝视说明了这三个日志设备的用途。

注冊的日志设备文件操作方法为logger_fops:

staticstruct
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,

};

日志驱动程序模块的初始化函数为logger_init:

staticint
__init logger_init(void)

{

int
ret;

ret
= init_log(&log_main);

if(unlikely(ret))

goto
out;

ret
= init_log(&log_events);

if(unlikely(ret))

goto
out;

ret
= init_log(&log_radio);

if(unlikely(ret))

goto
out;

out:

return
ret;

}

device_initcall(logger_init);

logger_init函数通过调用init_log函数来初始化了上述提到的三个日志设备:

staticint
__init init_log(struct
logger_log *log)

{

int
ret;

ret
= misc_register(&log->misc);

if(unlikely(ret)){

printk(KERN_ERR"logger:
failed to register misc "

"device
for log '%s'!\n", log->misc.name);

return
ret;

}

printk(KERN_INFO"logger:
created %luK log '%s'\n",

(unsignedlong)
log->size>>10,
log->misc.name);

return0;

}

init_log函数主要调用了misc_register函数来注冊misc设备,misc_register函数定义在kernel/common/drivers/char/misc.c文件里:

/**

*     misc_register  
-       register a miscellaneous device

*     @misc: device structure

*

*     Register a miscellaneous device with the kernel.
If the minor

*     number is set to %MISC_DYNAMIC_MINOR a minor number
is assigned

*     and placed in the minor field of the structure.
For other cases

*     the minor number requested is used.

*

*     The structure passed is linked into the kernel and
may not be

*     destroyed until it has been unregistered.

*

*     A zero is returned on success and a negative errno
code for

*     failure.

*/

int misc_register(struct
miscdevice * misc)

{

struct
miscdevice*c;

dev_t dev;

int
err=0;

INIT_LIST_HEAD(&misc->list);

mutex_lock(&misc_mtx);

list_for_each_entry(c,&misc_list,
list){

if(c->minor==
misc->minor){

mutex_unlock(&misc_mtx);

return-EBUSY;

}

}

if(misc->minor==
MISC_DYNAMIC_MINOR){

int
i= DYNAMIC_MINORS;

while(--i>=0)

if((misc_minors[i>>3]&(1<<(i&7)))==0)

break;

if(i<0){

mutex_unlock(&misc_mtx);

return-EBUSY;

}

misc->minor=
i;

}

if(misc->minor<
DYNAMIC_MINORS)

misc_minors[misc->minor>>3]|=1<<(misc->minor&7);

dev
= MKDEV(MISC_MAJOR,
misc->minor);

misc->this_device=
device_create(misc_class,
misc->parent,
dev,NULL,

"%s",
misc->name);

if(IS_ERR(misc->this_device)){

err
= PTR_ERR(misc->this_device);

goto
out;

}

/*

* Add it to the front, so that later devices can "override"

* earlier defaults

*/

list_add(&misc->list,&misc_list);

out:

mutex_unlock(&misc_mtx);

return
err;

}

注冊完毕后。通过device_create创建设备文件节点。这里。将创建/dev/log/main、/dev/log/events和/dev/log/radio三个设备文件,这样,用户空间就能够通过读写这三个文件和驱动程序进行交互。

3.4Logger驱动程序的日志记录读取过程



在logger.c文件里,注冊的读取日志设备文件的方法为logger_read:

/*

* logger_read - our log's read() method

*

* Behavior:

*

* - O_NONBLOCK works

* - If there are no log entries to read, blocks until
log is written to

* - Atomically reads exactly one log entry

*

* Optimal read size is LOGGER_ENTRY_MAX_LEN. Will set errno to EINVAL if read

* buffer is insufficient to hold next entry.

*/

static
ssize_t logger_read(struct
file *file,char
__user*buf,

size_t count,
loff_t*pos)

{

struct
logger_reader*reader=
file->private_data;

struct
logger_log*log=
reader->log;

ssize_t ret;

DEFINE_WAIT(wait);

start:

while(1){

prepare_to_wait(&log->wq,&wait,
TASK_INTERRUPTIBLE);

mutex_lock(&log->mutex);

ret
=(log->w_off==
reader->r_off);

mutex_unlock(&log->mutex);

if(!ret)

break;

if(file->f_flags&
O_NONBLOCK){

ret
=-EAGAIN;

break;

}

if(signal_pending(current)){

ret
=-EINTR;

break;

}

schedule();

}

finish_wait(&log->wq,&wait);

if(ret)

return
ret;

mutex_lock(&log->mutex);

/*
is there still something to read or did we race? */

if(unlikely(log->w_off==
reader->r_off)){

mutex_unlock(&log->mutex);

goto
start;

}

/*
get the size of the next entry */

ret
= get_entry_len(log,
reader->r_off);

if(count<
ret){

ret
=-EINVAL;

goto
out;

}

/*
get exactly one entry from the log */

ret
= do_read_log_to_user(log,
reader, buf,
ret);

out:

mutex_unlock(&log->mutex);

return
ret;

}

须要注意的是,在函数開始的地方,表示读取日志上下文的structlogger_reader是保存在文件指针的private_data成员变量里面的,这是在打开设备文件时设置的,设备文件打开方法为logger_open:

/*

* logger_open - the log's open() file operation

*

* Note how near a no-op this is in the write-only case. Keep it that way!

*/

staticint
logger_open(struct
inode *inode,struct
file*file)

{

struct
logger_log*log;

int
ret;

ret
= nonseekable_open(inode,
file);

if(ret)

return
ret;

log
= get_log_from_minor(MINOR(inode->i_rdev));

if(!log)

return-ENODEV;

if(file->f_mode&
FMODE_READ){

struct
logger_reader*reader;

reader
= kmalloc(sizeof(struct
logger_reader), GFP_KERNEL);

if(!reader)

return-ENOMEM;

reader->log=
log;

INIT_LIST_HEAD(&reader->list);

mutex_lock(&log->mutex);

reader->r_off=
log->head;

list_add_tail(&reader->list,&log->readers);

mutex_unlock(&log->mutex);

file->private_data=
reader;

}else

file->private_data=
log;

return0;

}

新打开日志设备文件时,是从log->head位置開始读取日志的。保存在struct
logger_reader的成员变量r_off中。

start标号处的while循环是在等待日志可读,假设已经没有新的日志可读了,那么就要读进程就要进入休眠状态,等待新的日志写入后再唤醒,这是通过prepare_wait和schedule两个调用来实现的。

假设没有新的日志可读,而且设备文件不是以非堵塞O_NONBLOCK的方式打开或者这时有信号要处理(signal_pending(current)),那么就直接返回,不再等待新的日志写入。推断当前是否有新的日志可读的方法是:

ret = (log->w_off == reader->r_off);

即推断当前缓冲区的写入位置和当前读进程的读取位置是否相等,假设不相等,则说明有新的日志可读。

之后的代码表示。假设有新的日志可读。那么就首先通过get_entry_len来获取下一条可读的日志记录的长度,从这里能够看出,日志读取进程是以日志记录为单位进行读取的。一次仅仅读取一条记录。get_entry_len的函数实现例如以下:

/*

* get_entry_len - Grabs the length of the payload of the next entry starting

* from 'off'.

*

* Caller needs to hold log->mutex.

*/

static
__u32 get_entry_len(struct
logger_log *log,
size_t off)

{

__u16 val;

switch(log->size-
off){

case1:

memcpy(&val,
log->buffer+
off,1);

memcpy(((char*)&val)+1,
log->buffer,1);

break;

default:

memcpy(&val,
log->buffer+
off,2);

}

returnsizeof(struct
logger_entry)+
val;

}

上面提到,每一条日志记录是由两大部分组成的,一个用于描写叙述这条日志记录的结构体struct
logger_entry,还有一个是记录体本身,即有效负载。结构体structlogger_entry的长度是固定的。仅仅要知道有效负载的长度,就能够知道整条日志记录的长度了。而有效负载的长度是记录在结构体struct logger_entry的成员变量len中。而len成员变量的地址与struct
logger_entry的地址同样,因此,仅仅须要读取记录的開始位置的两个字节就能够了。

又因为日志记录缓冲区是循环使用的。这两个节字有可能是第一个字节存放在缓冲区最后一个字节,而第二个字节存放在缓冲区的第一个节。除此之外。这两个字节都是连在一起的。因此,分两种情况来考虑。对于前者,分别通过读取缓冲区最后一个字节和第一个字节来得到日志记录的有效负载长度到本地变量val中,对于后者,直接读取连续两个字节的值到本地变量val中。

这两种情况是通过推断日志缓冲区的大小和要读取的日志记录在缓冲区中的位置的差值来差别的。假设相差1。就说明是前一种情况了。

最后,把有效负载的长度val加上struct logger_entry的长度就得到了要读取的日志记录的总长度了。

接下来。得到了要读取的记录的长度,就调用do_read_log_to_user函数来运行真正的读取动作:

static
ssize_t do_read_log_to_user(struct
logger_log *log,

struct
logger_reader*reader,

char
__user*buf,

size_t count)

{

size_t len;

/*

* We read from the log in two disjoint operations. First, we read from

* the current read head offset up to 'count' bytes or to the end of

* the log, whichever comes first.

*/

len
= min(count,
log->size-
reader->r_off);

if(copy_to_user(buf,
log->buffer+
reader->r_off,
len))

return-EFAULT;

/*

* Second, we read any remaining bytes, starting back at the head of

* the log.

*/

if(count!=
len)

if(copy_to_user(buf+
len, log->buffer,
count - len))

return-EFAULT;

reader->r_off=
logger_offset(reader->r_off+
count);

return
count;

}

这个函数简单地调用copy_to_user函数来把位于内核空间的日志缓冲区指定的内容复制到用户空间的内存缓冲区就能够了,同一时候。把当前读取日志进程的上下文信息中的读偏移r_off前进到下一条日志记录的開始的位置上。

3.5Logger驱动程序的日志记录写入过程



logger.c文件。注冊的写入日志设备文件的方法为logger_aio_write:

/*

* logger_aio_write - our write method, implementing support for write(),

* writev(), and aio_write(). Writes are our fast path, and we try to optimize

* them above all else.

*/

ssize_t logger_aio_write(struct
kiocb *iocb,conststruct
iovec*iov,

unsignedlong
nr_segs, loff_t ppos)

{

struct
logger_log*log=
file_get_log(iocb->ki_filp);

size_t orig
= log->w_off;

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.len=
min_t(size_t,
iocb->ki_left,
LOGGER_ENTRY_MAX_PAYLOAD);

/*
null writes succeed, return zero */

if(unlikely(!header.len))

return0;

mutex_lock(&log->mutex);

/*

* 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.

*/

fix_up_readers(log,sizeof(struct
logger_entry)+
header.len);

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

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)){

log->w_off=
orig;

mutex_unlock(&log->mutex);

return
nr;

}

iov++;

ret
+= nr;

}

mutex_unlock(&log->mutex);

/*
wake up any blocked readers */

wake_up_interruptible(&log->wq);

return
ret;

}

输入的參数iocb表示io上下文,iov表示要写入的内容。长度为nr_segs,表示有nr_segs个段的内容要写入。

我们知道,每一个要写入的日志的结构形式为:

struct logger_entry | priority | tag | msg

当中。 priority、tag和msg这三个段的内容是由iov參数从用户空间传递下来的,分别相应iov里面的三个元素。而logger_entry是由内核空间来构造的:

struct logger_entry header;

       structtimespec now;

       now =current_kernel_time();

       header.pid= current->tgid;

       header.tid= current->pid;

       header.sec= now.tv_sec;

       header.nsec= now.tv_nsec;

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

然后调用do_write_log首先把logger_entry结构体写入到日志缓冲区中:

/*

* do_write_log - writes 'len' bytes from 'buf' to 'log'

*

* The caller needs to hold log->mutex.

*/

staticvoid
do_write_log(struct
logger_log *log,constvoid*buf,
size_t count)

{

size_t len;

len
= min(count,
log->size-
log->w_off);

memcpy(log->buffer+
log->w_off,
buf, len);

if(count!=
len)

memcpy(log->buffer,
buf + len,
count - len);

log->w_off=
logger_offset(log->w_off+
count);

}

因为logger_entry是内核堆栈空间分配的,直接用memcpy拷贝就能够了。

接着。通过一个while循环把iov的内容写入到日志缓冲区中。也就是日志的优先级别priority、日志Tag和日志主体Msg:

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)){

log->w_off=
orig;

mutex_unlock(&log->mutex);

return
nr;

}

iov++;

ret
+= nr;

}

因为iov的内容是由用户空间传下来的。须要调用do_write_log_from_user来写入:

static
ssize_t do_write_log_from_user(struct
logger_log *log,

constvoid
__user*buf,
size_t count)

{

size_t len;

len
= min(count,
log->size-
log->w_off);

if(len&&
copy_from_user(log->buffer+
log->w_off,
buf, len))

return-EFAULT;

if(count!=
len)

if(copy_from_user(log->buffer,
buf + len,
count - len))

return-EFAULT;

log->w_off=
logger_offset(log->w_off+
count);

return
count;

}

而且在这里,另一个重要的步骤:

/*

* 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.

*/

fix_up_readers(log,sizeof(struct
logger_entry)+
header.len);

为什么要调用fix_up_reader这个函数呢?这个函数又是作什么用的呢?这是因为日志缓冲区是循环使用的。即旧的日志记录假设没有及时读取,而缓冲区的内容又已经用完时,就须要覆盖旧的记录来容纳新的记录。

而这部分将要被覆盖的内容,有可能是某些reader的下一次要读取的日志所在的位置。以及为新的reader准备的日志開始读取位置head所在的位置。因此,须要调整这些位置,使它们可以指向一个新的有效的位置。

我们来看一下fix_up_reader函数的实现:

/*

* fix_up_readers - walk the list of all readers and "fix up" any who were

* lapped by the writer; also do the same for the default "start head".

* We do this by "pulling forward" the readers and start head to the first

* entry after the new write head.

*

* The caller needs to hold log->mutex.

*/

staticvoid
fix_up_readers(struct
logger_log *log,
size_t len)

{

size_t old
= log->w_off;

size_t new
= logger_offset(old+
len);

struct
logger_reader*reader;

if(clock_interval(old,
new, log->head))

log->head=
get_next_entry(log,
log->head,
len);

list_for_each_entry(reader,&log->readers,
list)

if(clock_interval(old,
new, reader->r_off))

reader->r_off=
get_next_entry(log,
reader->r_off,
len);

}

推断log->head和全部读者reader的当前读偏移reader->r_off是否在被覆盖的区域内,假设是,就须要调用get_next_entry来取得下一个有效的记录的起始位置来调整当前位置:

/*

* get_next_entry - return the offset of the first valid entry at least 'len'

* bytes after 'off'.

*

* Caller must hold log->mutex.

*/

static
size_t get_next_entry(struct
logger_log *log,
size_t off, size_t len)

{

size_t count
=0;

do{

size_t nr
= get_entry_len(log,
off);

off
= logger_offset(off+
nr);

count
+= nr;

}while(count<
len);

return
off;

}

而推断log->head和全部读者reader的当前读偏移reader->r_off是否在被覆盖的区域内。是通过clock_interval函数来实现的:

/*

* clock_interval - is a < c < b in mod-space? Put another way, does the line

* from a to b cross c?

*/

static
inline int clock_interval(size_t
a, size_t b,
size_t c)

{

if(b<
a){

if(a<
c|| b>=
c)

return1;

}else{

if(a<
c&& b>=
c)

return1;

}

return0;

}

最后。日志写入完成,还须要唤醒正在等待新日志的reader进程:

/* wake up any blocked readers */

       wake_up_interruptible(&log->wq);

至此,Logger驱动程序的主要逻辑就分析完毕了,还有其他的一些接口,如logger_poll、 logger_ioctl和logger_release函数。比較简单。就不再分析了。

另一点须要注意的是。因为Logger驱动程序模块在退出系统时。是不会卸载的,所以这个模块没有module_exit函数,而对于模块里面定义的对象。也没实用对引用计数技术。

Android的logger机制分析的更多相关文章

  1. 【Android】窗口机制分析与UI管理系统

    类图关系 在看Android的窗口机制之前,先看看其主要的类图关系以及层级之间的依赖与调用关系 1.window在当前的android系统的中的呈现形式是PhoneWindow (frameworks ...

  2. android 休眠唤醒机制分析(一) — wake_lock

    本文转自:http://blog.csdn.net/g_salamander/article/details/7978772 Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一 ...

  3. android 休眠唤醒机制分析(一) — wake_lock【转】

    Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一有效的wake_lock,系统就不能进入深度休眠,但可以进行设备的浅度休眠操作.wake_lock一般在关闭lcd.tp但系统 ...

  4. android 休眠唤醒机制分析(二) — early_suspend

    本文转自:http://blog.csdn.net/g_salamander/article/details/7982170 early_suspend是Android休眠流程的第一阶段即浅度休眠,不 ...

  5. android 休眠唤醒机制分析(三) — suspend

    本文转自:http://blog.csdn.net/g_salamander/article/details/7988340 前面我们分析了休眠的第一个阶段即浅度休眠,现在我们继续看休眠的第二个阶段 ...

  6. android的窗口机制分析------UI管理系统

    Activity可以看做是整个Android系统的人机接口,它提供了一个窗口来绘制UI,每个Activity在启动时,我们都需要给它设置一个Content view,作为Activity所呈现的UI内 ...

  7. Android事件分发机制源码分析

    Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...

  8. Android 基础 十一 Android的消息机制

    Handler是Android消息机制的上层接口,这使得在开发应用过程中我们只需要和Handler交互即可.Handler的使用过程很简单,通过它可以轻松地将一个任务切换到Handler所在的线程中去 ...

  9. 《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制

    第10章 Android的消息机制 10.1 Android消息机制概述 (1)Android的消息机制主要是指Handler的运行机制,其底层需要MessageQueue和Looper的支撑.Mes ...

随机推荐

  1. 用requests自动访问网页,下载网页内容

    import requests # 请求下载excel def downloading(text_path): # 访问要下载的链接文件 with open(text_path) as f: for ...

  2. springboot添加外部jar包及打包

    项目中除了从pom中添加依赖包,还可以添加本地的jar包,怎么做呢? 1.在src下新建目录lib,将jar包添加到lib中 2.在pom文件里添加配置以下属性,就可以使用jar包了 <depe ...

  3. 【Vjudge】P1989Subpalindromes(线段树)

    题目链接 水题一道,用线段树维护哈希值,脑补一下加减乱搞搞……注意细节就过了 一定注意细节…… #include<cstdio> #include<cstdlib> #incl ...

  4. Centos7系统rc.local不起作用问题

    Centos7系统rc.local不起作用问题 来源 https://www.cnblogs.com/xjz00/p/7729405.html Centos7已经写了要chmod +x /etc/rc ...

  5. [LOJ#526]「LibreOJ β Round #4」子集

    [LOJ#526]「LibreOJ β Round #4」子集 试题描述 qmqmqm有一个长为 n 的数列 a1,a2,……,an,你需要选择集合{1,2,……,n}的一个子集,使得这个子集中任意两 ...

  6. AIX 常用命令 第一步(uname,lspv)

    如何知道自己在运行单处理器还是多处理器内核? /unix 是指向已启动内核的符号链接.要了解正在运行什么内核模式,可输入 ls -l /unix 并查看 /unix 链接到什么文件.下面是 ls -l ...

  7. 【CCF】高速公路 tarjan强连通缩点

    [题意] 给定一个有向图,问图中互相可达(强连通)的点有多少对 [AC] 强连通缩点,缩点后是一个DAG,所以互相可达的点只在强连通块里. #include<iostream> #incl ...

  8. 第一个 spring Boot 应用通过Docker 来实现构建、运行、发布

    1. Docker 简介 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化.容器是完全使用沙 ...

  9. es6总结(二)--正则表达式和字符串

  10. SqlServer-1

    参考:https://blog.csdn.net/qq_29413829/article/details/80077550 安装sqlServer2012:https://blog.csdn.net/ ...