在分析Android7.0 init进程一文中提到,在init进程中是通过klog来输出log信息的,但是由于log的级别不同可能导致有些添加的log无法输出来。在init .cpp的main函数中初始化klog。
klog_init(); //初始化klog
klog_set_level(KLOG_NOTICE_LEVEL); //设置klog的级别为NOTICE
system/core/libcutils/klog.c
void klog_init(void) {
if (klog_fd >= 0) return; /* Already initialized */ //klog_fd默认值为-1,如果大于等于1表示klog已经初始化过,返回
klog_fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); //打开/dev/kmsg文件,获得文件描述符
if (klog_fd >= 0) { //大于等于0表示,打开成功,init进程的log都会输出到kernel log中
return;
}
static const char* name = "/dev/__kmsg__";
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) { //如果上面没有打开成功,创建/dev/_kmsg_文件
klog_fd = open(name, O_WRONLY | O_CLOEXEC); //打开文件,获取文件描述符
unlink(name);
}
}
void klog_set_level(int level) {
klog_level = level; //将设置的level赋值给klog_level
}
在init.cpp通过NOTICE函数输出log信息。但是INFO函数就输不出log,下面我们看一下具体原因。
NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage");
在system/core/init/log.h头文件中对NOTICE,INFO等函数进行定义
#define ERROR(x...) init_klog_write(KLOG_ERROR_LEVEL, x)
#define WARNING(x...) init_klog_write(KLOG_WARNING_LEVEL, x)
#define NOTICE(x...) init_klog_write(KLOG_NOTICE_LEVEL, x)
#define INFO(x...) init_klog_write(KLOG_INFO_LEVEL, x)
#define DEBUG(x...) init_klog_write(KLOG_DEBUG_LEVEL, x)
#define VERBOSE(x...) init_klog_write(KLOG_DEBUG_LEVEL, x)
调用NOTICE函数,也就调到了函数init_klog_wirte传入的参数为NOTICE log level,如果调用别的函数就传入对应的log level。还有传入需要打印的信息。
先看一下各个log level的值,代码定义位置system/core//include/cutils/klog.h
#define KLOG_ERROR_LEVEL 3
#define KLOG_WARNING_LEVEL 4
#define KLOG_NOTICE_LEVEL 5
#define KLOG_INFO_LEVEL 6
#define KLOG_DEBUG_LEVEL 7
继续分析代码system/core/init/log.cpp
void init_klog_write(int level, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
init_klog_vwrite(level, fmt, ap); //函数调用
va_end(ap);
}
static void init_klog_vwrite(int level, const char* fmt, va_list ap) {
static const char* tag = basename(getprogname());
if (level > klog_get_level()) return; //之前设置的level为NOTICE 5,现在level也为5相等为false,INFO level为6所以return
// The kernel's printk buffer is only 1024 bytes.
// TODO: should we automatically break up long lines into multiple lines?
// Or we could log but with something like "..." at the end?
char buf[1024]; //kernel 的printk buffer只有1024个字节, 将长的单行log,变为多行,或者直接省略掉
size_t prefix_size = snprintf(buf, sizeof(buf), "<%d>%s: ", level, tag);
size_t msg_size = vsnprintf(buf + prefix_size, sizeof(buf) - prefix_size, fmt, ap);
if (msg_size >= sizeof(buf) - prefix_size) {
msg_size = snprintf(buf + prefix_size, sizeof(buf) - prefix_size,
"(%zu-byte message too long for printk)\n", msg_size);
}
iovec iov[1];
iov[0].iov_base = buf;
iov[0].iov_len = prefix_size + msg_size;
klog_writev(level, iov, 1); //传送整理好的信息
}
system/core/libcutils/klog.c
void klog_writev(int level, const struct iovec* iov, int iov_count) {
if (level > klog_level) return; //由于level与klog_level都为5,所以继续执行
if (klog_fd < 0) klog_init(); //前面已经初始化过klog_fd大于等于0
if (klog_fd < 0) return;
TEMP_FAILURE_RETRY(writev(klog_fd, iov, iov_count));
}
//system/core/include/log/uio.h
extern int readv( int fd, struct iovec* vecs, int count ); //定义readv函数
extern int writev( int fd, const struct iovec* vecs, int count ); /定义writev函数
//system/core/liblog/uio.c
#include <log/uio.h>
LIBLOG_ABI_PUBLIC int writev(int fd, const struct iovec *vecs, int count)
{
int total = 0;
for ( ; count > 0; count--, vecs++ ) {
const char* buf = vecs->iov_base;
int len = vecs->iov_len;
while (len > 0) {
int ret = write( fd, buf, len ); //调用linux的write函数,fd为/dev/kmsg的文件描述符,所以将log写入到kmsg中即kernel log中。
if (ret < 0) {
if (total == 0)
total = -1;
goto Exit;
}
if (ret == 0)
goto Exit;
total += ret;
buf += ret;
len -= ret;
}
}
Exit:
return total;
}
之前做项目时遇到一个问题,在开机过程中kernel 与 init进程中的log都输在kernel log中,而启动zygote之后log是输出在android中的,这样就发生了init进程到framework这段时间无法精确计算,对分析开机流程总耗时带来不小的困扰。所以我们就仿照klog自己定义TAG来输出信息,将android 中的log输出到kernel中,这样同一行log即在kernel log中有也在android log中有就可以精确计算时间了。
首先在system/core/include/cutils/klog.h中定义log level
#define KLOG_PERFORMANCE_LEVEL 0 //根据之前分析,将level设置小一点,至少要小于5
#define KLOG_BOOTINFO(tag,x...) klog_write(KLOG_PERFORMANCE_LEVEL, "<0>" tag ": " x) //定义函数关系
定义完之后就可以在framework中使用了,
例如init进程启动完zygote服务,会进入/frameowrk/base/cmds/app_process/app_main.cpp我们可以在这里使用
#include <cutils/klog.h> //先include对应的头文件
#define BOOTINFO(x...) KLOG_BOOTINFO("bootinfo", x) //定义对应的函数关系
BOOTINFO("Entered app_main.cpp main() \n"); //就可以通过BOOTINFO函数将log输出到kernel log中
不过将编译好的文件替换到手机中仍然无法输出对应log信息,经过分析发现是有与selinux权限,输出log存在selinux安全问题。
为了调试我们本地只好将selinux关闭了,代码还是在init .cpp 中我们之前将init进程时有过提到。
static void selinux_initialize(bool in_kernel_domain) {
Timer t;
//..............
if (in_kernel_domain) {
//.........
bool kernel_enforcing = (security_getenforce() == 1);
bool is_enforcing = selinux_is_enforcing();
if (kernel_enforcing != is_enforcing) {
if (security_setenforce(is_enforcing)) { //可以直接将is_enforcing改为false,就将selinux关闭了
ERROR("security_setenforce(%s) failed: %s\n",
is_enforcing ? "true" : "false", strerror(errno));
security_failure();
}
}
//.............
} else {
selinux_init_all_handles();
}
}