Logger 机制是在 Android 系统中提供的一个轻量级的日志系统,这个日志系统是以驱动程序的形式在内核空间实现的,在用户空间分别提供了 Java 接口和 C/C++ 接口来使用这个日志系统,使用的接口取决于编写的是 Android 应用程序还是系统组件。 下面我准备从应用开发和源码分析两部分来分析安卓的 Logger 机制。 二、从 Android 应用程序开发角度看 Logger 机制这一部分将简要地介绍一下在 Android 应用程序开发中 Log 的使用方法 在程序开发过程中, 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. */ 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 ; 在 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 ): ................................................public final class Log { ................................................/** * Priority constant for the println method; use Log.v. */ public static final int VERBOSE = 2 ; /** * Priority constant for the println method; use Log.d. */ public static final int DEBUG = 3 ; /** * Priority constant for the println method; use Log.i. */ public static final int INFO = 4 ; /** * Priority constant for the println method; use Log.w. */ public static final int WARN = 5 ; /** * Priority constant for the println method; use Log.e. */ public static final int ERROR = 6 ; /** * Priority constant for the println method. */ public static final int ASSERT = 7 ; .....................................................public static int v ( String tag , String msg ) { return println_native ( LOG_ID_MAIN , VERBOSE , tag , msg ); } public static int v ( String tag , String msg , Throwable tr ) { return println_native ( LOG_ID_MAIN , VERBOSE , tag , msg + '\n' +getStackTraceString ( tr )); } public static int d ( String tag , String msg ) { return println_native ( LOG_ID_MAIN , DEBUG , tag , msg ); } public static int d ( String tag , String msg , Throwable tr ) { return println_native ( LOG_ID_MAIN , DEBUG , tag , msg + '\n' +getStackTraceString ( tr )); } public static int i ( String tag , String msg ) { return println_native ( LOG_ID_MAIN , INFO , tag , msg ); } public static int i ( String tag , String msg , Throwable tr ) { return println_native ( LOG_ID_MAIN , INFO , tag , msg + '\n' +getStackTraceString ( tr )); } public static int w ( String tag , String msg ) { return println_native ( LOG_ID_MAIN , WARN , tag , msg ); } public static int w ( String tag , String msg , Throwable tr ) { return println_native ( LOG_ID_MAIN , WARN , tag , msg + '\n' +getStackTraceString ( tr )); } public static int w ( String tag , Throwable tr ) { return println_native ( LOG_ID_MAIN , WARN , tag , getStackTraceString ( tr)); } public static int e ( String tag , String msg ) { return println_native ( LOG_ID_MAIN , ERROR , tag , msg ); } public static int e ( String tag , String msg , Throwable tr ) { return println_native ( LOG_ID_MAIN , ERROR , tag , msg + '\n' +getStackTraceString ( tr )); } ................................................................../**@hide */ public static native int 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 { unsigned char * 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 */ 注释说明了这三个日志设备的用途。 注册的日志设备文件操作方法为 logger_fops : static 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 , };日志驱动程序模块的初始化函数为 logger_init : static int __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 函数来初始化了上述提到的三个日志设备: static int __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" , ( unsigned long ) log -> size >> 10 , log -> misc . name ); return 0 ; }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! */ static int 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 ; return 0 ; }新打开日志设备文件时,是从 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 ) { case 1 : memcpy (& val , log -> buffer + off , 1 ); memcpy ((( char *) & val ) + 1 , log -> buffer , 1 ); break ; default : memcpy (& val , log -> buffer + off , 2 ); } return sizeof ( 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 , const struct iovec * iov , unsigned long 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 )) return 0 ; 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; 然后调用 do_write_log 首先把 logger_entry 结构体写入到日志缓冲区中: /* * do_write_log - writes 'len' bytes from 'buf' to 'log' * * 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 ); 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 , const void __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. */ static void 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 ) return 1 ; } else { if ( a < c && b >= c ) return 1 ; } return 0 ; }最后,日志写入完毕,还需要唤醒正在等待新日志的 reader 进程 :
/* wake up any blocked readers */ 至此, Logger 驱动程序的主要逻辑就分析完成了,还有其它的一些接口,如logger_poll 、 logger_ioctl 和 logger_release 函数,比较简单,就不再分析了。 还有一点需要注意的是,由于 Logger 驱动程序模块在退出系统时,是不会卸载的,所以这个模块没有 module_exit 函数,而对于模块里面定义的对象,也没有用对引用计数技术。 (责任编辑:好模板) |