Android系统在运行时库层提供了一个用来和Logger日志驱动程序进行交互的日志库liblog。通过日志库liblog提供的接口,应用程序就可以方便地往Logger日志驱动程序中写入日志记录。位于运行时库层的C/C++日志写入接口和位于应用程序框架层的Java日志写入接口都是通过liblog库提供的日志写入接口来往Logger日志驱动程序中写入日志记录的,因此,在分析这些C/C++或者Java日志写入接口之前,我们首先介绍liblog库的日志记录写入接口。
日志库liblog提供的日志记录写入接口实现在logd_write.c文件中,它的位置如下:
~~~
~/Android/system/core
----liblog
----logd_write.c
~~~
它里面实现了一系列的日志记录写入函数,如图4-7所示。
![liblog日志库的函数调用关系](https://box.kancloud.cn/1a90a02056d4bf73315921341dac963a_679x346.jpg =679x346)
根据写入的日志记录的类型不同,这些函数可以划分为三个类别。其中,函数__android_log_assert、__android_log_vprint和__android_log_print用来写入类型为main的日志记录;函数__android_log_btwrite和__android_log_bwrite用来写入类型为events的日志记录;函数__android_log_buf_print可以写入任意一种类型的日志记录。特别地,在函数__android_log_write和__android_log_buf_write中,如果要写入的日志记录的标签以“RIL”开头或者等于“HTC_RIL”、“AT”、“GSM”、“STK”、“CDMA”、“PHONE”或“SMS”,那么它们就会被认为是radio类型的日志记录。
无论写入的是什么类型的日志记录,它们最终都是通过调用函数write_to_log写入到Logger日志驱动程序中的。write_to_log是一个函数指针,它开始时指向函数__write_to_log_init。因此,当函数write_to_log第一次被调用时,实际上执行的是函数__write_to_log_init。函数__write_to_log_init执行的是一些日志库的初始化操作,接着将函数指针write_to_log重定向到函数__write_to_log_kernel或者__write_to_log_null中,这取决于是否成功地将日志设备文件打开。
在本节接下来的内容中,我们就分别描述日志库liblog提供的日志记录写入函数的实现。
**write_to_log**
**system/core/liblog/logd_write.c**
~~~
static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);
static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;
~~~
函数指针write_to_log在开始的时候被设置为函数__write_to_log_init。当它第一次被调用时,便会执行函数__write_to_log_init来初始化日志库liblog,如下所示。
**system/core/liblog/logd_write.c**
~~~
static int log_fds[(int)LOG_ID_MAX] = { -1, -1, -1, -1 };
static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
{
......
if (write_to_log == __write_to_log_init) {
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);
write_to_log = __write_to_log_kernel;
if (log_fds[LOG_ID_MAIN] < 0 || log_fds[LOG_ID_RADIO] < 0 ||
log_fds[LOG_ID_EVENTS] < 0) {
log_close(log_fds[LOG_ID_MAIN]);
log_close(log_fds[LOG_ID_RADIO]);
log_close(log_fds[LOG_ID_EVENTS]);
log_fds[LOG_ID_MAIN] = -1;
log_fds[LOG_ID_RADIO] = -1;
log_fds[LOG_ID_EVENTS] = -1;
write_to_log = __write_to_log_null;
}
if (log_fds[LOG_ID_SYSTEM] < 0) {
log_fds[LOG_ID_SYSTEM] = log_fds[LOG_ID_MAIN];
}
}
......
return write_to_log(log_id, vec, nr);
}
~~~
在函数__write_to_log_init中,第7行如果发现函数指针write_to_log指向的是自己,那么就会调用函数open打开系统中的日志设备文件,并且把得到的文件描述符保存在全局数组log_fds中。
LOG_ID_MAIN、LOG_ID_RADIO、LOG_ID_EVENTS、LOG_ID_SYSTEM和LOG_ID_MAX是五个枚举值,它们的定义如下所示。
**system/core/include/cutils/log.h**
~~~
typedef enum {
LOG_ID_MAIN = 0,
LOG_ID_RADIO = 1,
LOG_ID_EVENTS = 2,
LOG_ID_SYSTEM = 3,
LOG_ID_MAX
} log_id_t;
~~~
LOGGER_LOG_MAIN、LOGGER_LOG_RADIO、LOGGER_LOG_EVENTS和LOGGER_LOG_SYSTEM是四个宏,它们的定义如下所示。
**system/core/include/cutils/logger.h**
~~~
#define LOGGER_LOG_MAIN "log/main"
#define LOGGER_LOG_RADIO "log/radio"
#define LOGGER_LOG_EVENTS "log/events"
#define LOGGER_LOG_SYSTEM "log/system"
~~~
因此,函数__write_to_log_init的第8行到第11行实际上是调用宏log_open来打开/dev/log/main、/dev/log/radio、/dev/log/events和/dev/log/system四个日志设备文件。宏log_open的定义如下所示。
**system/core/liblog/logd_write.c**
~~~
#if FAKE_LOG_DEVICE
// This will be defined when building for the host.
#define log_open(pathname, flags) fakeLogOpen(pathname, flags)
#define log_writev(filedes, vector, count) fakeLogWritev(filedes, vector, count)
#define log_close(filedes) fakeLogClose(filedes)
#else
#define log_open(pathname, flags) open(pathname, flags)
#define log_writev(filedes, vector, count) writev(filedes, vector, count)
#define log_close(filedes) close(filedes)
#endif
~~~
在正式环境中编译日志库liblog时,宏FAKE_LOG_DEVICE的值定义为0,因此,宏log_open实际上指向的是打开文件操作函数open。从这里同时也可以看到,在正式环境中,宏log_writev和log_close分别指向写文件操作函数writev和关闭文件操作函数close。
回到函数__write_to_log_init中,第15行的if语句判断/dev/log/main、/dev/log/radio和/dev/log/events三个日志设备文件是否都打开成功。如果是,就将函数指针write_to_log指向函数__write_to_log_kernel;否则,将函数指针write_to_log指向函数__write_to_log_null。第26行的if语句判断日志设备文件/dev/log/system是否打开成功。如果不成功,就将log_fds[LOG_ID_SYSTEM]的值设置为log_fds[LOG_ID_MAIN],即将类型为system和main的日志记录都写入到日志设备文件/dev/log/main中。
**__write_to_log_kernel**
**system/core/liblog/logd_write.c**
~~~
static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr)
{
ssize_t ret;
int log_fd;
if (/*(int)log_id >= 0 &&*/ (int)log_id < (int)LOG_ID_MAX) {
log_fd = log_fds[(int)log_id];
} else {
return EBADF;
}
do {
ret = log_writev(log_fd, vec, nr);
} while (ret < 0 && errno == EINTR);
return ret;
}
~~~
函数__write_to_log_kernel根据参数log_id在全局数组log_fds中找到对应的日志设备文件描述符,然后调用宏log_writev,即函数writev,把日志记录写入到Logger日志驱动程序中。
如果调用宏log_writev写入日志记录时,Logger日志驱动程序的返回值小于0,并且错误码等于EINTR,那么就需要重新执行写入日志记录的操作。这种情况一般出现在当前进程等待写入日志记录的过程中,刚好碰到有新的信号需要处理,这时候内核就会返回一个EINTR错误码给调用者,表示需要调用者再次执行相同的操作。
**__write_to_log_null**
**system/core/liblog/logd_write.c**
~~~
static int __write_to_log_null(log_id_t log_fd, struct iovec *vec, size_t nr)
{
return -1;
}
~~~
函数__write_to_log_null是一个空实现,什么也不做。在日志设备文件打开失败的情况下,函数指针write_to_log才会指向该函数。
**__android_log_write**
**system/core/liblog/logd_write.c**
~~~
int __android_log_write(int prio, const char *tag, const char *msg)
{
struct iovec vec[3];
log_id_t log_id = LOG_ID_MAIN;
if (!tag)
tag = "";
/* XXX: This needs to go! */
if (!strcmp(tag, "HTC_RIL") ||
!strncmp(tag, "RIL", 3) || /* Any log tag with "RIL" as the prefix */
!strcmp(tag, "AT") ||
!strcmp(tag, "GSM") ||
!strcmp(tag, "STK") ||
!strcmp(tag, "CDMA") ||
!strcmp(tag, "PHONE") ||
!strcmp(tag, "SMS"))
log_id = LOG_ID_RADIO;
vec[0].iov_base = (unsigned char *) &prio;
vec[0].iov_len = 1;
vec[1].iov_base = (void *) tag;
vec[1].iov_len = strlen(tag) + 1;
vec[2].iov_base = (void *) msg;
vec[2].iov_len = strlen(msg) + 1;
return write_to_log(log_id, vec, 3);
}
~~~
在默认情况下,函数__android_log_write写入的日志记录的类型为main。然而,如果传进来的日志记录的标签以“RIL”开头或者等于“HTC_RIL”、“AT”、“GSM”、“STK”、“CDMA”、“PHONE”或“SMS”,那么它就会被认为是类型为radio的日志记录。
第20行到第25行首先将日志记录的优先级、标签和内容保存在数组元素vec[0]、vec[1]和vec[2]中,然后再将它们写入到Logger日志驱动程序中。日志记录的标签和内容的类型均为字符串,它们后面紧跟着的字符串结束字符‘\0’也被写入到Logger日志驱动程序中。这样做的好处是,可以通过字符串结束字符‘\0’来解析日志记录的标签字段和内容字段。
**__android_log_buf_write**
**system/core/liblog/logd_write.c**
~~~
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
{
struct iovec vec[3];
if (!tag)
tag = "";
/* XXX: This needs to go! */
if (!strcmp(tag, "HTC_RIL") ||
!strncmp(tag, "RIL", 3) || /* Any log tag with "RIL" as the prefix */
!strcmp(tag, "AT") ||
!strcmp(tag, "GSM") ||
!strcmp(tag, "STK") ||
!strcmp(tag, "CDMA") ||
!strcmp(tag, "PHONE") ||
!strcmp(tag, "SMS"))
bufID = LOG_ID_RADIO;
vec[0].iov_base = (unsigned char *) &prio;
vec[0].iov_len = 1;
vec[1].iov_base = (void *) tag;
vec[1].iov_len = strlen(tag) + 1;
vec[2].iov_base = (void *) msg;
vec[2].iov_len = strlen(msg) + 1;
return write_to_log(bufID, vec, 3);
}
~~~
函数__android_log_buf_write的实现与函数__android_log_write的实现类似,不过它可以指定写入的日志记录的类型。特别地,如果要写入的日志记录的标签以“RIL”开头或者等于“HTC_RIL”、“AT”、“GSM”、“STK”、“CDMA”、“PHONE”或“SMS”,那么它们就会被认为是类型为radio的日志记录。
函数__android_log_buf_write与前面分析的函数__android_log_write一样,把紧跟在日志记录标签和内容后面的字符串结束符号‘\0’也写入到Logger日志驱动程序中,目的也是为了以后从Logger日志驱动程序读取日志时,可以方便地将日志记录的标签字段和内容字段解析出来。
**__android_log_vprint、__android_log_print、__android_log_assert**
**system/core/liblog/logd_write.c**
~~~
int __android_log_vprint(int prio, const char *tag, const char *fmt, va_list ap)
{
char buf[LOG_BUF_SIZE];
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
return __android_log_write(prio, tag, buf);
}
int __android_log_print(int prio, const char *tag, const char *fmt, ...)
{
va_list ap;
char buf[LOG_BUF_SIZE];
va_start(ap, fmt);
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
va_end(ap);
return __android_log_write(prio, tag, buf);
}
void __android_log_assert(const char *cond, const char *tag,
const char *fmt, ...)
{
char buf[LOG_BUF_SIZE];
if (fmt) {
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
va_end(ap);
} else {
/* Msg not provided, log condition. N.B. Do not use cond directly as
* format string as it could contain spurious '%' syntax (e.g.
* "%d" in "blocks%devs == 0").
*/
if (cond)
snprintf(buf, LOG_BUF_SIZE, "Assertion failed: %s", cond);
else
strcpy(buf, "Unspecified assertion failed");
}
__android_log_write(ANDROID_LOG_FATAL, tag, buf);
__builtin_trap(); /* trap so we have a chance to debug the situation */
}
~~~
函数__android_log_vprint、__android_log_print和__android_log_assert都是调用函数__android_log_write向Logger日志驱动程序中写入日志记录的,它们都可以使用格式化字符串来描述要写入的日志记录内容。
**__android_log_buf_print**
**system/core/liblog/logd_write.c**
~~~
int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fmt, ...)
{
va_list ap;
char buf[LOG_BUF_SIZE];
va_start(ap, fmt);
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
va_end(ap);
return __android_log_buf_write(bufID, prio, tag, buf);
}
~~~
函数__android_log_buf_print是调用函数__android_log_buf_write向Logger日志驱动程序中写入日志记录的,它可以指定要写入的日志记录的类型,以及使用格式化字符串来描述要写入的日志记录内容。
**__android_log_bwrite、__android_log_btwrite**
**system/core/liblog/logd_write.c**
~~~
int __android_log_bwrite(int32_t tag, const void *payload, size_t len)
{
struct iovec vec[2];
vec[0].iov_base = &tag;
vec[0].iov_len = sizeof(tag);
vec[1].iov_base = (void*)payload;
vec[1].iov_len = len;
return write_to_log(LOG_ID_EVENTS, vec, 2);
}
/*
* Like __android_log_bwrite, but takes the type as well. Doesn't work
* for the general case where we're generating lists of stuff, but very
* handy if we just want to dump an integer into the log.
*/
int __android_log_btwrite(int32_t tag, char type, const void *payload,
size_t len)
{
struct iovec vec[3];
vec[0].iov_base = &tag;
vec[0].iov_len = sizeof(tag);
vec[1].iov_base = &type;
vec[1].iov_len = sizeof(type);
vec[2].iov_base = (void*)payload;
vec[2].iov_len = len;
return write_to_log(LOG_ID_EVENTS, vec, 3);
}
~~~
函数__android_log_bwrite和__android_log_btwrite写入的日志记录的类型为events。其中,函数__android_log_bwrite写入的日志记录的内容可以由多个值组成,而函数__android_log_btwrite写入的日志记录的内容只有一个值。在前面的4.1小节中提到,类型为events的日志记录的内容一般是由一系列值组成的,每一个值都有自己的名称、类型和单位。函数__android_log_btwrite就是通过第二个参数type来指定要写入的日志记录内容的值类型的,由于它写入的日志记录的内容只有一个值,因此,为了方便读取,就把这个值的类型抽取出来,作为一个独立的字段写入到Logger日志驱动程序中。
- 文章概述
- 下载Android源码以及查看源码
- win10 平台通过VMware Workstation安装Ubuntu
- Linux系统安装Ubuntu编译Android源码
- Eclipse快捷键大全
- 前言
- 第一篇 初识Android系统
- 第一章 准备知识
- 1.1 Linux内核参考书籍
- 1.2 Android应用程序参考书籍
- 1.3 下载、编译和运行Android源代码
- 1.3.1 下载Android源代码
- 1.3.2 编译Android源代码
- 1.3.3 运行Android模拟器
- 1.4 下载、编译和运行Android内核源代码
- 1.4.1 下载Android内核源代码
- 1.4.2 编译Android内核源代码
- 1.4.3 运行Android模拟器
- 1.5 开发第一个Android应用程序
- 1.6 单独编译和打包Android应用程序模块
- 1.6.1 导入单独编译模块的mmm命令
- 1.6.2 单独编译Android应用程序模块
- 1.6.3 重新打包Android系统镜像文件
- 第二章 硬件抽象层
- 2.1 开发Android硬件驱动程序
- 2.1.1 实现内核驱动程序模块
- 2.1.2 修改内核Kconfig文件
- 2.1.3 修改内核Makefile文件
- 2.1.4 编译内核驱动程序模块
- 2.1.5 验证内核驱动程序模块
- 2.2 开发C可执行程序验证Android硬件驱动程序
- 2.3 开发Android硬件抽象层模块
- 2.3.1 硬件抽象层模块编写规范
- 2.3.1.1 硬件抽象层模块文件命名规范
- 2.3.1.2 硬件抽象层模块结构体定义规范
- 2.3.2 编写硬件抽象层模块接口
- 2.3.3 硬件抽象层模块的加载过程
- 2.3.4 处理硬件设备访问权限问题
- 2.4 开发Android硬件访问服务
- 2.4.1 定义硬件访问服务接口
- 2.4.2 实现硬件访问服务
- 2.4.3 实现硬件访问服务的JNI方法
- 2.4.4 启动硬件访问服务
- 2.5 开发Android应用程序来使用硬件访问服务
- 第三章 智能指针
- 3.1 轻量级指针
- 3.1.1 实现原理分析
- 3.1.2 使用实例分析
- 3.2 强指针和弱指针
- 3.2.1 强指针的实现原理分析
- 3.2.2 弱指针的实现原理分析
- 3.2.3 应用实例分析
- 第二篇 Android专用驱动系统
- 第四章 Logger日志系统
- 4.1 Logger日志格式
- 4.2 Logger日志驱动程序
- 4.2.1 基础数据结构
- 4.2.2 日志设备的初始化过程
- 4.2.3 日志设备文件的打开过程
- 4.2.4 日志记录的读取过程
- 4.2.5 日志记录的写入过程
- 4.3 运行时库层日志库
- 4.4 C/C++日志写入接口
- 4.5 Java日志写入接口
- 4.6 Logcat工具分析
- 4.6.1 基础数据结构
- 4.6.2 初始化过程
- 4.6.3 日志记录的读取过程
- 4.6.4 日志记录的输出过程
- 第五章 Binder进程间通信系统
- 5.1 Binder驱动程序
- 5.1.1 基础数据结构
- 5.1.2 Binder设备的初始化过程
- 5.1.3 Binder设备文件的打开过程
- 5.1.4 设备文件内存映射过程
- 5.1.5 内核缓冲区管理
- 5.1.5.1 分配内核缓冲区
- 5.1.5.2 释放内核缓冲区
- 5.1.5.3 查询内核缓冲区
- 5.2 Binder进程间通信库
- 5.3 Binder进程间通信应用实例
- 5.4 Binder对象引用计数技术
- 5.4.1 Binder本地对象的生命周期
- 5.4.2 Binder实体对象的生命周期
- 5.4.3 Binder引用对象的生命周期
- 5.4.4 Binder代理对象的生命周期
- 5.5 Binder对象死亡通知机制
- 5.5.1 注册死亡接收通知
- 5.5.2 发送死亡接收通知
- 5.5.3 注销死亡接收通知
- 5.6 Service Manager的启动过程
- 5.6.1 打开和映射Binder设备文件
- 5.6.2 注册成为Binder上下文管理者
- 5.6.3 循环等待Client进程请求
- 5.7 Service Manager代理对象接口的获取过程
- 5.8 Service的启动过程
- 5.8.1 注册Service组件
- 5.8.1.1 封装通信数据为Parcel对象
- 5.8.1.2 发送和处理BC_TRANSACTION命令协议
- 5.8.1.3 发送和处理BR_TRANSACTION返回协议
- 5.8.1.4 发送和处理BC_REPLY命令协议
- 5.8.1.5 发送和处理BR_REPLY返回协议
- 5.8.2 循环等待Client进程请求
- 5.9 Service代理对象接口的获取过程
- 5.10 Binder进程间通信机制的Java实现接口
- 5.10.1 获取Service Manager的Java代理对象接口
- 5.10.2 AIDL服务接口解析
- 5.10.3 Java服务的启动过程
- 5.10.4 获取Java服务的代理对象接口
- 5.10.5 Java服务的调用过程
- 第六章 Ashmem匿名共享内存系统
- 6.1 Ashmem驱动程序
- 6.1.1 相关数据结构
- 6.1.2 设备初始化过程
- 6.1.3 设备文件打开过程
- 6.1.4 设备文件内存映射过程
- 6.1.5 内存块的锁定和解锁过程
- 6.1.6 解锁状态内存块的回收过程
- 6.2 运行时库cutils的匿名共享内存接口
- 6.3 匿名共享内存的C++访问接口
- 6.3.1 MemoryHeapBase
- 6.3.1.1 Server端的实现
- 6.3.1.2 Client端的实现
- 6.3.2 MemoryBase
- 6.3.2.1 Server端的实现
- 6.3.2.2 Client端的实现
- 6.3.3 应用实例
- 6.4 匿名共享内存的Java访问接口
- 6.4.1 MemoryFile
- 6.4.2 应用实例
- 6.5 匿名共享内存的共享原理分析
- 第三篇 Android应用程序框架篇
- 第七章 Activity组件的启动过程
- 7.1 Activity组件应用实例
- 7.2 根Activity的启动过程
- 7.3 Activity在进程内的启动过程
- 7.4 Activity在新进程中的启动过程
- 第八章 Service组件的启动过程
- 8.1 Service组件应用实例
- 8.2 Service在新进程中的启动过程
- 8.3 Service在进程内的绑定过程
- 第九章 Android系统广播机制
- 9.1 广播应用实例
- 9.2 广播接收者的注册过程
- 9.3 广播的发送过程
- 第十章 Content Provider组件的实现原理
- 10.1 Content Provider组件应用实例
- 10.1.1 ArticlesProvider
- 10.1.2 Article
- 10.2 Content Provider组件的启动过程
- 10.3 Content Provider组件的数据共享原理
- 10.4 Content Provider组件的数据更新通知机制
- 10.4.1 内容观察者的注册过程
- 10.4.2 数据更新的通知过程
- 第十一章 Zygote和System进程的启动过程
- 11.1 Zygote进程的启动脚本
- 11.2 Zygote进程的启动过程
- 11.3 System进程的启动过程
- 第十二章 Android应用程序进程的启动过程
- 12.1 应用程序进程的创建过程
- 12.2 Binder线程池的启动过程
- 12.3 消息循环的创建过程
- 第十三章 Android应用程序的消息处理机制
- 13.1 创建线程消息队列
- 13.2 线程消息循环过程
- 13.3 线程消息发送过程
- 13.4 线程消息处理过程
- 第十四章 Android应用程序的键盘消息处理机制
- 14.1 InputManager的启动过程
- 14.1.1 创建InputManager
- 14.1.2 启动InputManager
- 14.1.3 启动InputDispatcher
- 14.1.4 启动InputReader
- 14.2 InputChannel的注册过程
- 14.2.1 创建InputChannel
- 14.2.2 注册Server端InputChannel
- 14.2.3 注册当前激活窗口
- 14.2.4 注册Client端InputChannel
- 14.3 键盘消息的分发过程
- 14.3.1 InputReader处理键盘事件
- 14.3.2 InputDispatcher分发键盘事件
- 14.3.3 当前激活的窗口获得键盘消息
- 14.3.4 InputDispatcher获得键盘事件处理完成通知
- 14.4 InputChannel的注销过程
- 14.4.1 销毁应用程序窗口
- 14.4.2 注销Client端InputChannel
- 14.4.3 注销Server端InputChannel
- 第十五章 Android应用程序线程的消息循环模型
- 15.1 应用程序主线程消息循环模型
- 15.2 界面无关的应用程序子线程消息循环模型
- 15.3 界面相关的应用程序子线程消息循环模型
- 第十六章 Android应用程序的安装和显示过程
- 16.1 应用程序的安装过程
- 16.2 应用程序的显示过程