在Android有一些应用程序的日志输出是通过printf之类的标准函数输出的,这类log是无法记录到的。主要是由于init进程会把0,1,2三个fd指向到/dev/null,而其他进程都是由init fork出来的,所以标准输出和标准错误输出都会继承自父进程,所以默认也都是不打印出来的。

android init的实现在system/core/init/init.c中:

int main(int argc, char** argv) {...... // At this point we're in the second stage of init. InitKernelLogging(argv); LOG(INFO) << "init second stage started!";......}

init会执行log初始化动作,也就是把所有的标准输入标准输出和标准错误输出都指向/dev/null:

void InitKernelLogging(char* argv[]) {    // Make stdin/stdout/stderr all point to /dev/null.    int fd = open("/sys/fs/selinux/null", O_RDWR);    if (fd == -1) {        int saved_errno = errno;        android::base::InitLogging(argv, &android::base::KernelLogger);        errno = saved_errno;        PLOG(FATAL) << "Couldn't open /sys/fs/selinux/null";    }    dup2(fd, 0);    dup2(fd, 1);    dup2(fd, 2);    if (fd > 2) close(fd);    android::base::InitLogging(argv, &android::base::KernelLogger);}

android中提供了logwrapper程序用来重定向log的输出,重定向的log可以使用logcat查看,我们来看下他的实现机制又是怎样的呢?

logwrapper的源代码实现在system/core/logwrapper中,原理如下:

通过logwrapper的封装来执行一个command,logwrapper会fork一个子进程,并在子进程中exec执行cmd,父进程负责和子进程通讯,并记录子进程输出的log,父进程会根据logwrapper的参数来选择对应的log输出方式:

/* Log directly to the specified log */static void do_log_line(struct log_info *log_info, char *line) {    if (log_info->log_target & LOG_KLOG) {        klog_write(6, log_info->klog_fmt, line);    }    if (log_info->log_target & LOG_ALOG) {        ALOG(LOG_INFO, log_info->btag, "%s", line);    }    if (log_info->log_target & LOG_FILE) {        fprintf(log_info->fp, "%s\n", line);    }}

父进程起到一个log重定向的作用,它收取子进程输出的log,并通过其他方式输出出来,从上面的一段实现上可以看出,父进程可以通过do_log_line来按照三种方式来输出:

1)LOG_KLOG的方式输出,调用的是klog_write来输出kernel log2)LOG_ALOG,这种是输出给logcat使用的log,默认是LOG INFO级别(3)LOG_FILE,输出log到指定file文件,在我看得这个版本代码中,此方法暂时还不可用

父进程和子进程采用的是ptty机制来做进程通讯的,具体实现在system/core/logwrapper/logwrap.c中:

int android_fork_execvp_ext(int argc, char* argv[], int *status, bool ignore_int_quit,        int log_target, bool abbreviated, char *file_path,        void *unused_opts, int unused_opts_len) {    pid_t pid;    int parent_ptty;    int child_ptty;    struct sigaction intact;    struct sigaction quitact;    sigset_t blockset;    sigset_t oldset;    int rc = 0;    LOG_ALWAYS_FATAL_IF(unused_opts != NULL);    LOG_ALWAYS_FATAL_IF(unused_opts_len != 0);    rc = pthread_mutex_lock(&fd_mutex);    if (rc) {        ERROR("failed to lock signal_fd mutex\n");        goto err_lock;    }    /* Use ptty instead of socketpair so that STDOUT is not buffered */          //使用ptty代替socket,因为socket是有缓冲的    parent_ptty = TEMP_FAILURE_RETRY(open("/dev/ptmx", O_RDWR));    if (parent_ptty < 0) {        ERROR("Cannot create parent ptty\n");        rc = -1;        goto err_open;    }    char child_devname[64];    if (grantpt(parent_ptty) || unlockpt(parent_ptty) ||            ptsname_r(parent_ptty, child_devname, sizeof(child_devname)) != 0) {        ERROR("Problem with /dev/ptmx\n");        rc = -1;        goto err_ptty;    }    child_ptty = TEMP_FAILURE_RETRY(open(child_devname, O_RDWR));    if (child_ptty < 0) {        ERROR("Cannot open child_ptty\n");        rc = -1;        goto err_child_ptty;    }    sigemptyset(&blockset);    sigaddset(&blockset, SIGINT);    sigaddset(&blockset, SIGQUIT);    pthread_sigmask(SIG_BLOCK, &blockset, &oldset);    pid = fork();    if (pid < 0) {        close(child_ptty);        ERROR("Failed to fork\n");        rc = -1;        goto err_fork;    } else if (pid == 0) {        pthread_mutex_unlock(&fd_mutex);        pthread_sigmask(SIG_SETMASK, &oldset, NULL);        close(parent_ptty);                                       //创建的子进程会继承父进程的fd,所以要关闭不需要的fd        dup2(child_ptty, 1);        dup2(child_ptty, 2);        close(child_ptty);        child(argc, argv);    } else {        close(child_ptty);                                            //父进程也要关闭不需要的fd,剩余的fd就是用于进程交互的fd了        if (ignore_int_quit) {            struct sigaction ignact;            memset(&ignact, 0, sizeof(ignact));            ignact.sa_handler = SIG_IGN;            sigaction(SIGINT, &ignact, &intact);            sigaction(SIGQUIT, &ignact, &quitact);        }        rc = parent(argv[0], parent_ptty, pid, status, log_target,                    abbreviated, file_path);    }    if (ignore_int_quit) {        sigaction(SIGINT, &intact, NULL);        sigaction(SIGQUIT, &quitact, NULL);    }err_fork:    pthread_sigmask(SIG_SETMASK, &oldset, NULL);err_child_ptty:err_ptty:    close(parent_ptty);err_open:    pthread_mutex_unlock(&fd_mutex);err_lock:    return rc;}

示例:

servcie akmd /system/bin/logwrapper /sbin/akmd

更多相关文章

  1. GitHub 标星 2.5K+!教你通过玩游戏的方式学习 VIM!
  2. 一款霸榜 GitHub 的开源 Linux 资源监视器!
  3. android 定位的4种方式介绍
  4. Android基于Pull方式解析xml的方法详解
  5. 安装android studio报错Failed to install Intel HAXM的解决方法
  6. Android(安卓)IPC 进程间通信实现理解
  7. Android中使用GPS和NetWork获取定位信息
  8. android 中组件 service
  9. Framework笔记 | Android(安卓)Framework用到了哪些IPC方式,分别

随机推荐

  1. 【故障处理】队列等待之enq IV - content
  2. 【DG】怎么使用Data Pump备份物理备库
  3. DNS 引起经典RAC故障
  4. ORA-01578和ORA-26040--NOLOGGING操作引
  5. 告别躺赢,我靠索引+函数
  6. 利用 OpenRestry 实现负载均衡、限流功能
  7. 程序员如何活成华为让人害怕的样子
  8. ***组织“海莲花”再活跃 打造全新macOS
  9. 虚假软件更新滥用NetSupport远程访问工具
  10. 如何在Mac上打开蓝牙并配对新设备