安卓的 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 则读取一边系统的节点
- state_log --> /proc/state
- disk_log --> /proc/diskstats
- 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 停止记录。安卓启动流程可以上网查阅,本文不再叙述。