Logcat源码分析(一)

来源:互联网 发布:a星算法 定位 编辑:程序博客网 时间:2024/06/10 05:25
本文编辑整理自: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文件中,
Logger详解(一)一文有提到,为了方便描述,这里列出这个宏和结构体的定义:
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_tlog_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++;  
    }  
由于我们假设使用logcat时,不带任何命令行参数,这里的devices变量为NULL,因此,就会默认创建/dev/log/main设备上下文结构体,如果存在/dev/log/system设备文件,也会一并创建。宏LOGGER_LOG_MAINLOGGER_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()进行日志的软连接
原创粉丝点击