Android的logger机制分析
分析安卓的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源代码工程中使用,则在后台中运行模拟器:
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源代码工程中,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机制分析相关推荐
- Android 事件分发机制分析及源码详解
Android 事件分发机制分析及源码详解 文章目录 Android 事件分发机制分析及源码详解 事件的定义 事件分发序列模型 分发序列 分发模型 事件分发对象及相关方法 源码分析 事件分发总结 一般 ...
- android的窗口机制分析------事件处理
由于Android是linux内核的,所以它的事件处理也在linux的基础上完成的,因此本文我们从linux 内核往应用这个方向慢慢理清它的处理过程. linux内核提供了一个Input子系统来实现的 ...
- Android Broadcast广播机制分析
基于Android 6.0的源码剖析, 分析android广播的发送与接收流程. 一.概述 广播(Broadcast)机制用于进程/线程间通信,广播分为广播发送和广播接收两个过程,其中广播接收者Bro ...
- android 休眠唤醒机制分析(一)
Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一有效的wake_lock,系统就不能进入深度休眠,但可以进行设备的浅度休眠操作.wake_lock一般在关闭lcd.tp但系统 ...
- android的窗口机制分析------UI管理系统
Activity可以看做是整个Android系统的人机接口,它提供了一个窗口来绘制UI,每个Activity在启动时,我们都需要给它设置一个Content view,作为Activity所呈现的UI内 ...
- Android pid uid 机制分析Permission Denial的错误
PID 指进程ID. PID是进程的身份标识,程序一旦运行,就会给应用分配一个独一无二的PID(ps:一个应用可能包含多个进程,每个进程有唯一的一个PID) 进程终止后PID会被系统收回,再次打开应用 ...
- Android之内存机制分析-Android堆和栈
1.dalvik的Heap和Stack 这里说的只是dalvik java部分的内存,实际上除了dalvik部分,还有native.这个以后再说. 下面针对上面列出的数据类型进行说明,只有了解了我们 ...
- android的窗口机制分析------ViewRoot类
ViewRoot是GUI管理系统与GUI呈现系统之间的桥梁,根据ViewRoot的定义,我们发现它并不是一个View类型,而是一个Handler. 它的主要作用如下: A. 向DecorView分发收 ...
- 《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制
第10章 Android的消息机制 10.1 Android消息机制概述 (1)Android的消息机制主要是指Handler的运行机制,其底层需要MessageQueue和Looper的支撑.Mes ...
最新文章
- 条款22: 尽量用“传引用”而不用“传值”
- 第五轮学科评估再提“破五唯”
- tstringgrid 单元格区域获取_对合并单元格的序号填充还在一个个的手动录入?那就真的Out了!...
- delphi 脚本引擎比较
- SQL Server 高可用性(一)AlwaysOn 技术
- 数据结构之第一章一些概念
- hbase数据结构小结
- python正确方法_下列定义函数的方法,在Python中正确的是()。
- dapperpoco mysql_DapperPoco -- 基于Dapper的、轻量级的、高性能的、简单的、灵活的ORM框架...
- 夏普扫描显示没有所选服务器,夏普扫描绑定服务器地址
- AI 人工智能之概率论基础(概念总结和复习)
- 关于严蔚敏教授的数据结构一书中return ok ,OK为1不为0的问题
- C语言数组——一维数组实例详解
- tongweb7启动参数配置配置个人理解
- Kafka eagel 网页能打开,但是登录不上
- VS2013 OpenCV 2.4.9 “HEAP:Invalid Address specified to RtlValidateHeap( 000D0000, 019FEF18 )” 错误
- SpringBoot碰到的常见问题
- 验收测试,软件测试的最后阶段了
- 交叉熵损失函数的计算公式
- APP下载量成空洞,留住用户最关键
热门文章
- 【SAP业务模式】之ICS(三):前台操作
- Uvalive - 3026 Period (kmp求字符串的最小循环节+最大重复次数)
- UNIX网络编程——shutdown 与 close 函数 的区别
- OSGi.NET 学习笔记 [模块化和插件化][概念]
- Learning Video Object Segmentation from Static Images
- (四)孪生神经网络介绍及pytorch实现
- 数据结构笔记(十四)-- 串的模式匹配算法
- stl之queue队列容器
- python local global_Python 变量作用域 LEGB (上)—— Local,Global,Builtin
- php 判断是否是16进制,如何求解16进制字符串的验证