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

2) 附加配置
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

3) 开机完成后结束纪录
项目的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

4) 抓取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

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值