本文编辑整理自:http://www.linuxidc.com/Linux/2011-07/38988.htm
Logcat
工具内置在Android系统中,可以在主机上通过adb logcat命令来查看模拟机上日志信息。如果你还不知道Logcat的使用,请参看《
logcat命令详解》。
Logcat
工具的用法很丰富,因此,源代码也比较多,本文并不打算完整地介绍整个Logcat工具的源代码,主要是介绍Logcat读取日志的主线,即从打开日志设备文件到读取日志设备文件的日志记录到输出日志记录的主要过程,希望能起到一个抛砖引玉的作用.
Logcat
工具源代码位于
system/core/logcat
目录下,只有一个源代码文件
logcat.cpp
,编译后生成的可执行文件位于
out/target/product/generic/system/bin
目录下,在模拟机中,可以在
/system/bin
目录下看到logcat工具。下面我们就分段来阅读
logcat.cpp
源代码文件。
一、Logcat中的基本数据结构
这些数据结构是用来保存从日志设备文件读出来的日志记录:
struct
queued_entry_t {
union {
unsigned char buf[
LOGGER_ENTRY_MAX_LEN + 1] __attribute__((aligned(4)));
struct
logger_entry entry __attribute__((aligned(4)));
};
queued_entry_t* next;
queued_entry_t() {
next = NULL;
}
};
struct
log_device_t {
char* device;
bool binary;
int fd;
bool printed;
char label;
queued_entry_t* queue;
log_device_t* next;
log_device_t(char* d, bool b, char l) {
device = d;
binary = b;
label = l;
queue = NULL;
next = NULL;
printed = false;
}
void enqueue(queued_entry_t* entry) {
if (this->queue == NULL) {
this->queue = entry;
}
else {
queued_entry_t** e = &this->queue;
while (*e && cmp(entry, *e) >= 0) {
e = &((*e)->next);
}
entry->next = *e;
*e = entry;
}
}
};
其中,宏
LOGGER_ENTRY_MAX_LEN
和struct logger_entry定义在system/core/include/cutils/logger.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_ENTRY_MAX_LEN (4*1024)
从结构体
queued_entry_t
和
log_device_t
的定义可以看出,每一个
log_device_t
都包含有一个
queued_entry_t
队列,
queued_entry_t
就是对应从日志设备文件读取出来的一条日志记录了
,而
log_device_t
则是对应一个日志设备文件。从logcat来看共有4个日志设备文件,它们分别是
/dev/log/main
,
/dev/log/system
,
/dev/log/events
和
/dev/log/radio
。
正如《
Logger详解
》文中所述,
dev/log/main
,
/dev/log/system
,
/dev/log/events
和
/dev/log/radio
其实就是Android日志系统中四个日志设备文件
/dev/log_main
、
/dev/log_system
,
/dev/log_events
和
/dev/log_radio
的软连接,也就是说只是个映射。
每个日志设备上下文通过其next成员指针连接起来,每个设备文件上下文的日志记录也是通过next指针连接起来。
日志记录队例是按时间戳从小到大排列的,这个
log_device_t::enqueue
函数可以看出,当要插入一条日志记录的时候,先队列头开始查找,直到找到一个时间戳比当前要插入的日志记录的时间戳大的日志记录的位置,然后插入当前日志记录
。比较函数cmp的定义如下:
static int cmp(queued_entry_t* a, queued_entry_t* b) {
int n = a->entry.sec - b->entry.sec;
if (n != 0) {
return n;
}
return a->entry.nsec - b->entry.nsec;
}
为什么日志记录要按照时间戳从小到大排序呢?原来,
Logcat
在使用时,可以指定一个参数
-t <count>
,可以指定只显示最新
count
条记录,超过count的记录将被丢弃,在这里的实现中,就是要把排在队列前面的多余日记记录丢弃了,因为排在前面的日志记录是最旧的,默认是显示所有的日志记录。
二.、打开日志设备文件
Logcat
的入口函数在
logcat.cpp
文件中的
main
,打开日志设备文件和一些初始化的工作也是在这里进行。
main
函数的内容也比较多,前面的逻辑都是解析命令行参数
。这里假设我们使用logcat工具时,不带任何参数。这不会影响我们分析logcat读取日志的主线,有兴趣的读取可以自行分析解析命令行参数的逻辑。
分析完命令行参数以后,就开始要创建日志设备文件上下文结构体
log_device_t
了。
在
main()
函数中:
if (!devices) {
devices = new log_device_t(strdup(
"/dev/"LOGGER_LOG_MAIN), false, 'm');
Android::g_devCount = 1;
int accessmode =
(mode & O_RDONLY) ? R_OK : 0
| (mode & O_WRONLY) ? W_OK : 0;
// only add this if it's available
if (0 == access(
"/dev/"LOGGER_LOG_SYSTEM, accessmode)) {
devices->next = new log_device_t(strdup("/dev/"LOGGER_LOG_SYSTEM), false, 's');
Android::g_devCount++;
}
}
由于我们假设使用
logca
t时,不带任何命令行参数,这里的
devices
变量为NULL,因此,就会默认创建
/dev/log/main
设备上下文结构体,如果存在
/dev/log/system
设备文件,也会一并创建。宏
LOGGER_LOG_MAIN
和
LOGGER_LOG_SYSTEM
也是定义在system/core/include/cutils/logger.h文件中:
#define LOGGER_LOG_MAIN "log/main"
#define LOGGER_LOG_SYSTEM "log/system"
注意:这里的日志文件的名字和
logger.cpp中的名字是不一致的。
正如《
Logger详解
》文中所述,
dev/log/main
,
/dev/log/system
,
/dev/log/events
和
/dev/log/radio
其实就是Android日志系统中四个日志设备文件
/dev/log_main
、
/dev/log_system
,
/dev/log_events
和
/dev/log_radio
的软连接,也就是说只是个映射。系统是在启动时,即
system/core/init/devices.c中device_init()->coldboot()->do_coldboot()->handle_device_fd()->handle_device_event()
中
,
进行日志的软连接