安卓的 bootchart 是如何工作的

想要分析安卓的启动过程中的详细信息,比如耗时、CPU/IO占用情况,我们可以通过这样一条命令

adb shell 'touch /data/bootchart/enabled'

然后重启安卓设备。开机之后,执行命令

$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh

这个 sh 脚本长这个样子

#!/bin/sh
#
# This script is used to retrieve a bootchart log generated by init.
# All options are passed to adb, for better or for worse.
# See the readme in this directory for more on bootcharting.

TMPDIR=/tmp/android-bootchart
rm -rf $TMPDIR
mkdir -p $TMPDIR

LOGROOT=/data/bootchart
TARBALL=bootchart.tgz

FILES="header proc_stat.log proc_ps.log proc_diskstats.log"

for f in $FILES; do
    adb "${@}" pull $LOGROOT/$f $TMPDIR/$f 2>&1 > /dev/null
done
(cd $TMPDIR && tar -czf $TARBALL $FILES)
pybootchartgui ${TMPDIR}/${TARBALL}
xdg-open ${TARBALL%.tgz}.png
echo "Clean up ${TMPDIR}/ and ./${TARBALL%.tgz}.png when done"

脚本做的事情实际上就是,把安卓上边的 /data/bootchart 文件夹拉取下来之后,通过 tar 命令把文件夹内的 header proc_stat.log proc_ps.log proc_diskstats.log 压缩成一个 tgz 文件,然后交给 pybootchartgui 处理成一个png。这个png图片里面就会显示安卓启动过程的信息了。
通常,你能得到这样一张类似这样的图片(下图是小米手机的示例)

bootchart 是如何工作的呢?

查看 init.rc

on post-fs-data
    mark_post_data

    # Start checkpoint before we touch data
    start vold
    exec - system system -- /system/bin/vdc checkpoint prepareCheckpoint

    # We chown/chmod /data again so because mount is run as root + defaults
    chown system system /data
    chmod 0771 /data
    # We restorecon /data in case the userdata partition has been reset.
    restorecon /data

    # Make sure we have the device encryption key.
    installkey /data

    # Start bootcharting as soon as possible after the data partition is
    # mounted to collect more data.
    mkdir /data/bootchart 0755 shell shell
    bootchart start
    ........

这里可以看到,bootchart 每次开机的时候都 start 一下,这里是做了什么事情呢?
从文件 system/core/init/bootchart.cpp 里面可以看到

......
static Result<Success> do_bootchart_start() {
    // We don't care about the content, but we do care that /data/bootchart/enabled actually exists.
    std::string start;
    if (!android::base::ReadFileToString("/data/bootchart/enabled", &start)) {
        LOG(VERBOSE) << "Not bootcharting";
        return Success();
    }

    g_bootcharting_thread = new std::thread(bootchart_thread_main);
    return Success();
}

static Result<Success> do_bootchart_stop() {
    if (!g_bootcharting_thread) return Success();

    // Tell the worker thread it's time to quit.
    {
        std::lock_guard<std::mutex> lock(g_bootcharting_finished_mutex);
        g_bootcharting_finished = true;
        g_bootcharting_finished_cv.notify_one();
    }

    g_bootcharting_thread->join();
    delete g_bootcharting_thread;
    g_bootcharting_thread = nullptr;
    return Success();
}

Result<Success> do_bootchart(const BuiltinArguments& args) {
    if (args[1] == "start") return do_bootchart_start();
    return do_bootchart_stop();
}

}  // namespace init
}  // namespace android

......
从这里可以看出来,如果识别到第 1 个参数是 start,执行了 do_bootchart_start ,do_bootchart_start 内又判断文件`/data/bootchart/enabled`,如果文件存在则开了一个线程执行 bootchart_thread_main ,这个函数体内容如下
```cpp
......
static void bootchart_thread_main() {
  LOG(INFO) << "Bootcharting started";

  // Open log files.
  auto stat_log = fopen_unique("/data/bootchart/proc_stat.log", "we");
  if (!stat_log) return;
  auto proc_log = fopen_unique("/data/bootchart/proc_ps.log", "we");
  if (!proc_log) return;
  auto disk_log = fopen_unique("/data/bootchart/proc_diskstats.log", "we");
  if (!disk_log) return;

  log_header();

  while (true) {
    {
      std::unique_lock<std::mutex> lock(g_bootcharting_finished_mutex);
      g_bootcharting_finished_cv.wait_for(lock, 200ms);
      if (g_bootcharting_finished) break;
    }

    log_file(&*stat_log, "/proc/stat");
    log_file(&*disk_log, "/proc/diskstats");
    log_processes(&*proc_log);
  }

  LOG(INFO) << "Bootcharting finished";
}

可以看到,这里开了一个循环,每过 200ms ,检查一下 g_bootcharting_finished 是否为 true,不为 true 则读取一边系统的节点

  1. state_log --> /proc/state
  2. disk_log --> /proc/diskstats
  3. proc_log 则是读取一遍 /proc/pid/cmdline和stat。

bootchart 什么时候停止呢?
回到 init.rc

on property:sys.boot_completed=1
    bootchart stop

从这里知道,bootchart 在属性 sys.boot_completed 变为 1 时停止记录。
停止记录时,init 将 g_bootcharting_finished 置为 false,跳出,于是记录log的线程停止。

那么什么时候 sys.boot_completed 变成 1 呢?
查看文件 frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    final void finishBooting() {
    ......
                // Tell anyone interested that we are done booting!
            SystemProperties.set("sys.boot_completed", "1");

            // And trigger dev.bootcomplete if we are not showing encryption progress
            if (!"trigger_restart_min_framework".equals(VoldProperties.decrypt().orElse(""))
                    || "".equals(VoldProperties.encrypt_progress().orElse(""))) {
                SystemProperties.set("dev.bootcomplete", "1");
            }
            mUserController.sendBootCompleted(
                    new IIntentReceiver.Stub() {
                        @Override
                        public void performReceive(Intent intent, int resultCode,
                                String data, Bundle extras, boolean ordered,
                                boolean sticky, int sendingUser) {
                            synchronized (ActivityManagerService.this) {
                                mOomAdjuster.mAppCompact.compactAllSystem();
                                requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
                            }
                        }
                    });
            mUserController.scheduleStartProfiles();

安卓启动完成时,这个属性被设置为 1 ,于是 bootchart 停止记录。安卓启动流程可以上网查阅,本文不再叙述。

posted @ 2022-04-11 22:09  SupperMary  阅读(214)  评论(0编辑  收藏  举报