Android启动时间优化大全

1 修改Android mksh默认的列长度

不修改这个参数,adb shell后,输入超过80个字符,就不能看到完整的命令行。

external/mksh/src/sh.h

EXTERN mksh_ari_t x_cols E_INIT(80);

EXTERN mksh_ari_t x_lins E_INIT(24);

2 Kernel优化

2.1 内核驱动模块化

将内核中尽可能多的驱动模块化,写一个负责insmod的shell脚本,开机时作为服务运行,可以大大减少内核启动的时间。同时由于init后是多任务运行,脚本服务对init启动其它服务的延时可以忽略不计。

注意insmod是阻塞调用,所以直接在init.rc脚本中调用,还是会加长启动时间,所以需要insmod的模块统一放到一个脚本服务中。

BoardConfig.mk

BOARD_VENDOR_KERNEL_MODULES

BOARD_RECOVERY_KERNEL_MODULES

2.2 提升console串口的波特率

2.3 printk

2.3.1 实现原理

printk的实现原理很简单,在有了日志消息后,首先申请控制台的信号量,如果申请到,则调用控制台写方法,写控制台。

在内核源码树的kernel/printk.c中,使用宏DECLARE_MUTEX声明了一个互斥锁console_sem,他用于保护console驱动列表console_drivers及同步对整个console驱动系统的访问。其中定义了函数acquire_console_sem来获得互斥锁console_sem,定义了release_console_sem来释放互斥锁console_sem,定义了函数try_acquire_console_sem来尽力得到互斥锁console_sem。这三个函数实际上是分别对函数down,up和down_trylock的简单包装。需要访问console_drivers驱动列表时就需要使用acquire_console_sem来保护console_drivers列表,当访问完该列表后,就调用release_console_sem释放信号量console_sem。函数console_unblank,console_device,console_stop,console_start,register_console 和unregister_console都需要访问console_drivers,因此他们都使用函数对acquire_console_sem和release_console_sem来对console_drivers进行保护。

调试console_sem时,需要打开宏CONFIG_DEBUG_SPINLOCK以跟踪owner字段。

关闭Kernel Log,通过bootchart.png可以看到启动init进程的时间明显提前,可以加快启动速度。

kernel/printk.c

int console_printk[4] = {

DEFAULT_CONSOLE_LOGLEVEL,

DEFAULT_MESSAGE_LOGLEVEL,

MINIMUM_CONSOLE_LOGLEVEL,

DEFAULT_CONSOLE_LOGLEVEL,

};

改为

int console_printk[4] = {

0, //DEFAULT_CONSOLE_LOGLEVEL,

0, //DEFAULT_MESSAGE_LOGLEVEL,

0, //MINIMUM_CONSOLE_LOGLEVEL,

0, //DEFAULT_CONSOLE_LOGLEVEL,

};

这四个值对应到路径proc/sys/kernel/printk,当printk()没有指定消息级别时,就采用DEFAULT_MESSAGE_LOGLEVEL(对应到KERN_WARNING = 4)。

echo "8 8 8 8" > /proc/sys/kernel/printk

  • 第一个"8"表示内核打印函数printk的打印级别

2.3.2 pr_debug动态log

CONFIG_DEBUG_FS=y

CONFIG_DYNAMIC_DEBUG=y

echo "file my_drv.c +p" > \

/sys/kernel/debug/dynamic_debug/control

2.3.3 kernel调试时打开所有log

BOARD_KERNEL_CMDLINE += ignore_loglevel

动态修改:

echo 0 > \

/sys/module/printk/parameters/ignore_loglevel

echo 1 > \

/sys/module/printk/parameters/ignore_loglevel

2.4 调试驱动probe耗时

BoardConfig.mk

BOARD_KERNEL_CMDLINE += \

initcall_debug ignore_loglevel

3 Android init进程

3.1 init

stage1: device tree

stage2: fstab

3.2 Sections Loading Sequence

on early-init

wait_for_coldboot_done()

on init

on early-fs

on fs

on post-fs

on post-fs-data

on early-boot

on boot

3.3 init Log机制

无论init代码架构如何变化,init进程的log始终是通过/dev/kmsg输出。

android::base::InitLogging(argv,

&android::base::KernelLogger);

LOG:普通的流式

PLOG:普通的流式,但是可以打印错误,类似于Linux的perror()

3.4 自定义kmsg函数

#include <sys/stat.h>

#include <sys/types.h>

#include <stdarg.h>

#include <unistd.h>

#include <fcntl.h>

static void kmsg(const char *fmt, ...)

{

char buf[512] = {0};

va_list ap;

int n, flags;

static int fd = 0;

va_start(ap,fmt);

n = vsnprintf(buf, 511, fmt, ap);

va_end(ap);

if (fd <= 0) {

fd = open("/dev/kmsg", O_RDWR);

if (fd > 0) {

// 必须加,否则init fork zygote后,

// 该描述符会被zygote继承,

// 导致zygote异常,不断重启

flags = fcntl(fd, F_GETFD);

flags |= FD_CLOEXEC;

fcntl(fd, F_SETFD, flags);

}

}

if ((fd > 0) && (n > 0)) {

write(fd, buf, n);

}

}

如果往/dev/kmsg中写log,通过dmesg几乎看不到log,加上如下的配置可以解决该问题。

BoardConfig.mk

BOARD_KERNEL_CMDLINE += \

printk.devkmsg=on

该配置用在如下的代码中:

kernel/printk/printk.c

devkmsg_write()

4 IO

4.1 CPU手动调频

/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

Foreground:Tasks run all cores

Background:Tasks run little cores

System-background:Tasks run all little cores for system processes that shouldn't run on big cores

TOP-APP:Tasks run all cores big cores

4.2 eMMC5.1速度调试

Read my blog "Flash闪存技术"。

4.3 IO调度Tunning

IO调度算法种类:cfq、deadline、noop(No Operation,电梯调度算法)

4.3.1 方法1

@ init.rc

on late-fs

boot time fs tune for UFS, change sda for eMMC

write /sys/block/sda/queue/iostats 0

write /sys/block/sda/queue/read_ahead_kb 2048

write /sys/block/sda/queue/nr_requests 256

write /sys/block/dm-0/queue/read_ahead_kb 2048

write /sys/block/dm-1/queue/read_ahead_kb 2048

on property:sys.boot_completed=1

write /sys/block/dm-0/queue/read_ahead_kb 512

write /sys/block/dm-1/queue/read_ahead_kb 512

write /sys/block/sda/queue/read_ahead_kb 128

write /sys/block/sda/queue/nr_requests 128

更好的方法是用(与上面的方法互斥):ioprio rt <value>

添加方法如下:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

...

nice的取值范围从-20到+19,-20优先级最高,+19最低

priority -20

范围从0到7,数字越小ioprio real-time优先级越高

ioprio rt 2

...

Read my blog "Linux Containers知识点"。

4.3.2 方法2

ionice可以用来调整特定进程的ioprio。

0 - none, 1 - Realtime, 2 - Best-effort, 3 - idle

SYS_ioprio_get

SYS_ioprio_set

5 Framework优化

5.1 设置log等级

/data/local.prop

setprop log.tag.<tagname> VERBOSE

setprop persist.log.tag.<tagname> VERBOSE

5.2 Android虚拟按键编码头文件

frameworks/native/include/android/keycodes.h

5.3 Zygote

5.3.1 Preface

frameworks/base/cmds/app_process

frameworks/base/core/jni/AndroidRuntime.cpp - LOG_BOOT_PROGRESS_START

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

当64位zygote启动时,它会从/data/dalvik-cache/arm64/system@framework@boot*.{art,oat,vdex}加载代码,这些被加载的jar包由环境变量BOOTCLASSPATH指定。

测试zygote c++时间:

#include<utils/SystemClock.h>

uptimeMillis()

5.3.2 preloaded-classes

frameworks/base/preloaded-classes

frameworks/base/tools/preload/WritePreloadedClassFile.java

frameworks/base/tools/preload/Compile.java

以上2个文件的修改,需要重新生成preload.jar

mmm frameworks/base/tools/preload/

产生文件out/host/linux-x86/framework/preload.jar

重新生成preloaded-classes文件

rm frameworks/base/preloaded-classes

rm out/target/product/<ppp>/system/etc/preloaded-classes

java -Xss512M -cp out/host/linux-x86/framework/preload.jar WritePreloadedClassFile frameworks/base/tools/preload/20100223.compiled

make systemimage -j8

5.3.3 framework-res.apk

on post-fs-data

exec [ <seclabel> [ <user> [ <group> ]* ]] -- <command> [ <argument> ]*

exec u:r:init:s0 -- /system/bin/cat /system/framework/framework-res.apk > /dev/null

or

service preload_res /system/bin/cat /system/framework/framework-res.apk > /dev/null

class core

user root

加seclabel是为了避免写cat.te文件,可以快速测试,最终提交版本是需要写cat.te的

seclabel u:r:init:s0

oneshot

disabled

on post-fs-data

start preload_res

5.3.4 多线程做preload优化

new Thread(new Runnable() {

@Override

public void run() {

// TODO, call preload();

}

}).start();

5.4 system_server

5.4.1 PMS包扫描

scanDirLI() :scanDir Lock mInstallLock

/system/app

/system/framework

/data/app

/data/app-private

Android 8.0后存储管理类是StorageManagerService.java

5.4.2 Pre-optimization

Android第一次开机后会进行dex2oat操作。

5.4.3 enableScreenAfterBoot

@ ActivityManagerService.java

enableScreenAfterBoot()

  • >

@ WindowManagerService.java

enableScreenAfterBoot()

  • >

mPolicy.systemBooted() - 在PhoneWindowManager.java(PWM)中

->

performEnableScreen()

  • >

checkWaitingForWindows() - 系统将检查目前所有的window是否画完,如果所有的window(包括keyguard、wallpaper和launcher等)都已经画好,系统会设置属性service.bootanim.exit值为1,退出动画。

查看当前top窗口:

findFocusedWindow()

adb shell dumpsys window windows | grep mCurrent

adb shell pm path <package name>

5.5 查看系统安装的所有APK

pm list packages

5.6 settings数据库读写

settings list system

settings put system screen_brightness 50

6 进程调试

6.1 Android性能分析工具汇总

top

free -m

procrank

vmstat 1

pidstat -w 1

mpstat -P ALL 3

iostat

logcat -b events | grep am_crash

logcat | grep died

https://github.com/zhenggaobing/pidstat

6.2 busybox

https://busybox.net/downloads/binaries/1.28.1-defconfig-multiarch/

查看进程树

busybox pstree

show threads

busybox ps -T

busybox top -H

6.3 Linux signal

查看Linux支持的signal:kill -l

6.4 Linux swap分区的使用

/dev/block/zram0

procrank

free -m

6.5 strace zygote

修改init进程

@ system/core/init/service.cpp

static void trace_zygote64(pid_t pid)

{

char pid_str[16] = {0};

if (fork() == 0) {

snprintf(pid_str, 15, "%d", pid);

execl("/system/xbin/strace",

"strace",

"-p", pid_str,

/*"-e", "trace=open,read,write,ioctl",*/

"-o", "/dev/z_tr.log",

"-s", "128",

"-tt", "-T", "-x", NULL);

}

}

将该函数放在创建zygote的地方。

z_tr.log中有具体时间,与logcat -b events | grep boot_progress抓取的log的关键事件时间戳进行对比,找出问题。

6.6 strace监视文件读写

抓取unix domain socket数据的读写 - 类似于tcpdump抓取网络数据包

strace -e read=7 -e write=7 -p 1

PID为1的进程中,dump出所有对fd = 7的读写数据

7 时间测量方法

7.1 bootchart源码下载编译

http://www.bootchart.org/download.html

在Linux桌面机器上:

apt-get install ant

解压下载的bootchart源代码,在bootchart源代码目录下执行ant,结束后,产生bootchart.jar,可以在Linux上分析,也可以将该jar包拷贝到Windows上。

7.2 如何使用bootchart

Android的bootchart时间轴是从kernel启动的时间点开始计算的,这个可以根据生成的bootchart.png和kernel msg得出结论。

echo 1 > /data/bootchart/enabled

重启手机

logs under /data/bootchart

cd /data/bootchart

busybox tar zcvf bootchart.tgz header kernel_pacct proc_diskstats.log proc_ps.log proc_stat.log

adb pull /data/bootchart/bootchart.tgz .

java -jar bootchart.jar .\bootchart.tgz

7.3 比较修改

system/core/init/compare-bootcharts.py

将2次生成的bootchart.tgz分别放到old_dir和new_dir中:

python compare-bootcharts.py old_dir new_dir

7.4 perfboot

system/core/init/perfboot.py

将system/core/init/perfboot.py和development/python-packages/adb文件夹拷贝到同一个目录下。需要注意的是,拷贝的是adb文件夹,不然perfboot.py中的import adb会报错。生成的.tsv文件使用excel打开。

python perfboot.py \

--iterations=2 \

--interval=30 -v \

--output=D:\data.tsv

等价于如下的命令:

adb logcat -b events | grep boot_progress

7.5 kernel启动时间分析

packages/services/Car/tools/bootanalyze/bootanalyze.py

packages/services/Car/tools/bootanalyze/config.yaml

7.6 获取Android各阶段的时间消耗

getprop | grep -i boottime

logcat | grep -i Timing

7.7 Android systrace使用

  1. atrace.rc

打开默认关闭的trace开关

in frameworks/native/cmds/atrace/atrace.rc

  • write /sys/kernel/debug/tracing/tracing_on 0
  • #write /sys/kernel/debug/tracing/tracing_on 0
  1. 附加配置

in device/<OEM>/common/common.mk

PRODUCT_PROPERTY_OVERRIDES += \

debug.atrace.tags.enableflags=802922

in BoardConfig.mk

BOARD_KERNEL_CMDLINE += \

trace_buf_size=64M \

trace_event=sched_wakeup,\

sched_switch,sched_blocked_reason,\

sched_cpu_hotplug,block,ext4

  1. 开机完成后结束纪录

项目的init.<PRODUCT>.rc文件加入如下修改,目的是结束trace记录。

on property:sys.boot_completed=1

write /d/tracing/tracing_on 0

write /d/tracing/events/ext4/enable 0

write /d/tracing/events/block/enable 0

  1. 抓取log并分析

做完上述修改后编译烧录镜像文件,待开机结束后执行:

adb root

adb shell "cat /d/tracing/trace" > boot_trace

然后执行

python external/chromium-trace/systrace.py \

--from-file=boot_trace \

-o boot_trace.html

上述命令可以将trace log转成html文件,用浏览器打开即可。

8 Abbreviations

bzImage:big zImage

vmlinuz:virtual memory

ABS_MT_POSITION_X:Multi Touch

Android PMS LI、LIF、LPw、LPr:要想弄明白方法名中的LI、LIF、LPw、LPr的含义,需要先了解PackageManagerService内部使用的两个锁。因为LI、LIF、LPw、LPr中的L,指的是Lock,而后面跟的I和P指的是两个锁,I表示mInstallLock同步锁;P表示mPackages同步锁。LPw、LPr中的w表示writing,r表示reading。LIF中的F表示Freeze。

avb:Android Verified Boot,用dm-verify验证system分区的完整性,用在Android 8.0之后的fstab文件中

scanDirLI() :scanDir Lock mInstallLock

APUE:əˈpju,Advanced Programming in the UNIX Environment

AT_FDCWD:File Descriptor Current Working Directory

bail out:跳伞

BLCR:BerkeleyLab Checkpoint/Restart

FRP:Factory Reset Protection

Intercept:API拦截,通信拦截

Linux dd命令:if表示input file,of表示output file,bs表示block size

Linux EPROTO:表示USB bitstuff出现了错误,眼图有问题

lmkd:Low Memory Killer Daemon

lsof:list open files

MIDR:ARM Main ID Register

MPIDR:ARM MultiProcessor ID Register

PPID:Parent Process ID(Linux ps命令可以看到),MFi:Product Plan ID

PuTTY:ˈpʌti

RA:Linux blockdev read ahead

Slog.wtf:what a terrible failure

相关推荐
JiMoKuangXiangQu4 个月前
Linux:用 runc 构建 ARM 平台容器
linux·容器·cgroup
self-motivation1 年前
深入理解linux内核 --------- CFS调度
linux·cgroup·cfs·抢占·完全公平
ZHOU西口1 年前
微服务实战系列之玩转Docker(三)
docker·微服务·云原生·镜像·cgroup·namespace
NPE~1 年前
Linux CGroup资源限制(概念&限制进程CPU使用)
linux·运维·服务器·cpu·磁盘·cgroup
律随心动1 年前
Android bootchart 分析启动性能工具使用
bootchart
╰つ栺尖篴夢ゞ2 年前
云原生之深入解析如何限制Kubernetes集群中文件描述符与线程数量
java·云原生·kubernetes·cgroup·ulimit
邵旺运2 年前
学习记录:使用Bootchart分析安卓系统开机启动时长
android·学习·bootchart
可乐不解渴2 年前
Docker基础-cgroup
docker·容器·cgroup
Wangsh@2 年前
-bash: fork: retry: Resource temporarily unavailable 问题解决
linux kernel·cgroup·资源限制