💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
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日志驱动程序中。