[摘]Android启动时间优化

Android  小知识  2024年9月11日 pm6:37发布1个月前更新 91es.com站长
57 0 0

前言

简单摘抄一下优化Android启动时间一些建议。

启动时间是系统性能的重要组成部分,因为用户必须等待启动完成后才能使用设备。对于较常进行冷启动的汽车等设备而言,较短的启动时间至关重要(没有人喜欢在等待几十秒后才能输入导航目的地)。

本文摘抄的,记录一下方便自己查看。

正文

优化引导加载程序

请遵循以下做法优化引导加载程序以缩短启动时间:

  • 对于日志记录:

    • 停止向 UART 写入日志,因为如果日志记录很多,可能需要很长时间来处理。(在 Google Pixel 设备上,我们发现这会使引导加载程序的速度减慢 1.5 秒)。

    • 仅记录错误情况,并考虑将其他信息存储到具有单独检索机制的内存中。

  • 对于内核解压缩,请考虑为当代硬件使用 LZ4 而非 GZIP(例如补丁)。请注意,不同的内核压缩选项具有不同的加载和解压缩时间,对于特定硬件,某些选项可能比其他选项更适合。

  • 检查进入去抖动/特殊模式过程中是否有不必要的等待时间,并最大限度地减少此类时间。

  • 将在引导加载程序中花费的启动时间以命令行的形式传递到内核。

  • 检查 CPU 时钟并考虑内核加载和初始化 I/O 并行进行(需要多核支持)。

优化 I/O 效率

提高 I/O 效率对缩短启动时间来说至关重要,对任何不必要内容的读取都应推迟到启动之后再进行(在 Google Pixel 上,启动时大约要读取 1.2GB 的数据)。

调整文件系统

当从头开始读取某个文件或依序读取块时,预读的 Linux 内核便会启动,这就需要调整专门用于启动的 I/O 调度程序参数(与普通应用的工作负载特性不同)。

支持无缝 (A/B) 更新的设备在首次启动时会极大地受益于文件系统调整(例如,Google Pixel 的启动时间缩短了 20 秒)。例如,我们为 Google Pixel 调整了以下参数:

on late-fs
  # boot time fs tune
    # boot time fs tune
    write /sys/block/sda/queue/iostats 0
    write /sys/block/sda/queue/scheduler cfq
    write /sys/block/sda/queue/iosched/slice_idle 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
    # end boot time fs tune
    write /sys/block/sda/queue/read_ahead_kb 512
    ...
其他
  • 使用内核配置 DM_VERITY_HASH_PREFETCH_MIN_SIZE(默认大小为 128)来启用 dm-verity 哈希预提取大小。

  • 为了提升文件系统稳定性及取消每次启动时的强制检查,请在 BoardConfig.mk 中设置 TARGET_USES_MKE2FS,以使用新的 ext4 生成工具。

分析 I/O

如需了解启动过程中的 I/O 活动,请使用内核 ftrace 数据(systrace 也使用这些数据):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

如需针对每个文件细分文件访问权限,请对内核进行以下更改(仅限开发版内核;请勿在正式版内核中应用这些更改):

diff --git a/fs/open.c b/fs/open.c
index 1651f35..a808093 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -981,6 +981,25 @@
 }
 EXPORT_SYMBOL(file_open_root);
 
+static void _trace_do_sys_open(struct file *filp, int flags, int mode, long fd)
+{
+       char *buf;
+       char *fname;
+
+       buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       if (!buf)
+               return;
+       fname = d_path(&filp-<f_path, buf, PAGE_SIZE);
+
+       if (IS_ERR(fname))
+               goto out;
+
+       trace_printk("%s: open(\"%s\", %d, %d) fd = %ld, inode = %ld\n",
+                     current-<comm, fname, flags, mode, fd, filp-<f_inode-<i_ino);
+out:
+       kfree(buf);
+}
+
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
 {
        struct open_flags op;
@@ -1003,6 +1022,7 @@
                } else {
                        fsnotify_open(f);
                        fd_install(fd, f);
+                       _trace_do_sys_open(f, flags, mode, fd);

使用以下脚本来帮助分析启动性能。

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py:负责衡量启动时间,并详细分析启动过程中的重要步骤。

  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace:提供每个文件的访问信息。

  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace 提供系统级细分信息。

优化 init.*.rc

Init 是从内核到框架建立之前的衔接过程,设备通常会在不同的 init 阶段花费几秒钟时间。

并行运行任务

虽然当前的 Android init 差不多算是一种单线程进程,但您仍然可以并行执行一些任务。

  • 在 Shell 脚本服务中执行缓慢命令,然后通过等待特定属性,在稍后加入。Android 8.0 通过新的 wait_for_property 命令支持该用例。

  • 识别 init 中的缓慢操作。系统会记录 init 命令 exec/wait_for_prop 或任何所需时间较长的操作(在 Android 8.0 中,指所需时间超过 50 毫秒的任何命令)。例如:

init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

查看此日志可能会发现可以改进的机会。

  • 启动服务并及早启用关键路径中的外围设备。例如,有些 SOC 需要先启动安全相关服务,然后再启动 SurfaceFlinger。在 ServiceManager 返回“wait for service”(等待服务)时查看系统日志 - 这通常表明必须先启动依赖服务。

  • 移除 init.*.rc 中所有未使用的服务和命令。只要是早期阶段的 init 中没有使用的服务和命令,都应推迟到启动完成后再使用。

注意:“属性”服务是 init 进程的一部分,因此,在启动期间调用 setproperty 可能会导致较长时间的延迟(如果 init 忙于执行内置命令)。

使用调度程序调整

使用调度程序调整,以便及早启动设备。以下是取自 Google Pixel 的示例:

on init
    # boottime stune
    write /dev/stune/schedtune.prefer_idle 1
    write /dev/stune/schedtune.boost 100
    on property:sys.boot_completed=1
    # reset stune
    write /dev/stune/schedtune.prefer_idle 0
    write /dev/stune/schedtune.boost 0

    # or just disable EAS during boot
    on init
    write /sys/kernel/debug/sched_features NO_ENERGY_AWARE
    on property:sys.boot_completed=1
    write /sys/kernel/debug/sched_features ENERGY_AWARE

部分服务在启动过程中可能需要进行优先级提升。例如:

init.zygote64.rc:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
...
及早启动 zygote

采用文件级加密的设备可以在 zygote-start 触发器的早期阶段启动 zygote(默认情况下,zygote 会在 main 类中启动,比 zygote-start 晚得多)。这样做时,请确保允许 zygote 在所有 CPU 中运行(因为错误的 cpuset 设置可能会强制 zygote 在特定 CPU 中运行)。

停用节电设置

在设备启动期间,可以停用 UFS 和/或 CPU 调节器等组件的节电设置。

请注意:为了提高效率,应在充电器模式下启用节电设置。

on init
    # Disable UFS powersaving
    write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 0
    write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 0
    write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 0
    write /sys/module/lpm_levels/parameters/sleep_disabled Y
on property:sys.boot_completed=1
    # Enable UFS powersaving
    write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 1
    write /sys/module/lpm_levels/parameters/sleep_disabled N
on charger
    # Enable UFS powersaving
    write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 1
    write /sys/class/typec/port0/port_type sink
    write /sys/module/lpm_levels/parameters/sleep_disabled N
推迟非关键初始化

非关键初始化(如 ZRAM)可以推迟到 boot_complete。

on property:sys.boot_completed=1
   # Enable ZRAM on boot_complete
   swapon_all /vendor/etc/fstab.${ro.hardware}

优化启动动画

请按照以下提示来优化启动动画。

配置为及早启动

Android 8.0 支持在装载 userdata 分区之前及早启动动画。然而,即使 Android 8.0 中使用了新的 ext4 工具链,系统也会出于安全原因定期触发 fsck,导致启动 bootanimation 服务时出现延迟。

为了使 bootanimation 及早启动,请将 fstab 装载分为以下两个阶段:

  • 在早期阶段,仅装载不需要运行检查的分区(例如 system/vendor/),然后启动启动动画服务及其依赖项(例如 servicemanager 和 surfaceflinger)。

  • 在第二个阶段,装载需要运行检查的分区(例如 data/)。

启动动画将会更快速地启动(且启动时间恒定),不受 fsck 影响。

干净利落地结束

在收到退出信号后,bootanimation 会播放最后一部分,而这一部分的长度会延长启动时间。快速启动的系统不需要很长的动画,如果启动动画很长,在很大程度上就体现不出所做的任何改进。我们建议缩短循环播放和结尾的时间。

优化 SELinux

请按照以下提示优化 SELinux 以缩短启动时间。

  • 使用简洁的正则表达式 (regex)。在为 file_contexts 中的 sys/devices 匹配 SELinux 政策时,格式糟糕的正则表达式可能会导致大量开销。例如,正则表达式 /sys/devices/.*abc.*(/.*)? 错误地强制扫描包含“abc”的所有 /sys/devices 子目录,导致 /sys/devices/abc/sys/devices/xyz/abc 都成为匹配项。如果将此正则表达式修正为 /sys/devices/[^/]*abc[^/]*(/.*)?,只有 /sys/devices/abc 会成为匹配项。

  • 将标签移动到 genfscon。这一现有的 SELinux 功能会将文件匹配前缀传递到 SELinux 二进制文件的内核中,而内核会将这些前缀应用于内核生成的文件系统。这也有助于修复错误标记的内核创建的文件,从而防止用户空间进程之间可能出现的竞态条件(试图在重新标记之前访问这些文件)。

工具和方法

请使用以下工具来帮助您收集用于优化目标的数据。

bootchart

bootchart 可为整个系统提供所有进程的 CPU 和 I/O 负载细分。该工具不需要重建系统映像,可以用作进入 systrace 之前的快速健全性检查。

如需启用 bootchart,请运行以下命令:

adb shell 'touch /data/bootchart/enabled'
adb reboot

在设备启动后,提取启动图表:

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

完成后,请删除 /data/bootchart/enabled 以防止每次都收集日期数据。

如果 bootchart 不起作用,并且您看到说明 bootchart.png 不存在的错误消息,请执行以下操作:

  1. 运行以下命令:

      sudo apt install python-is-python3
      cd ~/Documents
      git clone https://github.com/xrmx/bootchart.git
      cd bootchart/pybootchartgui
      mv main.py.in main.py
  1. 更新 $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh 以指向 pybootchartgui 的本地副本(位于 ~/Documents/bootchart/pybootchartgui.py

Systrace

systrace 允许在启动期间收集内核和 Android 跟踪记录。systrace 的可视化可以帮助分析启动过程中的具体问题。(不过,如果要查看整个启动过程中的平均数量或累计数量,直接查看内核跟踪记录更为方便。)

如需在启动过程中启用 systrace,请执行以下操作:

  1. frameworks/native/cmds/atrace/atrace.rc 中,更改以下行:

  write /sys/kernel/debug/tracing/tracing_on 0
  write /sys/kernel/tracing/tracing_on 0

改后

  #    write /sys/kernel/debug/tracing/tracing_on 0
  #    write /sys/kernel/tracing/tracing_on 0

这将启用跟踪功能(默认处于停用状态)。

  1. device.mk 文件中,添加以下行:

PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  1. 在设备 BoardConfig.mk 文件中,添加以下行:

BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug

如果是详细的 I/O 分析,还要添加块以及 ext4 和 f2fs。

  1. 在设备专属 init.rc 文件中,添加以下行:

on property:sys.boot_completed=1          // This stops tracing on boot complete
write /d/tracing/tracing_on 0
write /d/tracing/events/ext4/enable 0
write /d/tracing/events/f2fs/enable 0
write /d/tracing/events/block/enable 0
  1. 在设备启动后,提取跟踪记录:

adb root && adb shell atrace --async_stop -z -c -o /data/local/tmp/boot_trace
adb pull /data/local/tmp/boot_trace
$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py --from-file=boot_trace

注意:Chrome 无法处理过大的文件。请考虑使用 tailheadgrep 分割 boot_trace 文件,以获得必要的部分。由于事件过多,I/O 分析通常需要直接分析捕获的 boot_trace

参考文章

  1. 优化启动时间

 历史上的今天

  1. 2023: OkHttp分析之同步请求(0条评论)
  2. 2022: Socket的简单使用记录(0条评论)
  3. 2020: Android监听状态栏的显示与隐藏状态栏(0条评论)
  4. 2019: 梁文道:在铁路上开餐(0条评论)
版权声明 1、 本站名称: 91易搜
2、 本站网址: 91es.com3xcn.com
3、 本站内容: 部分来源于网络,仅供学习和参考,若侵权请留言
3、 本站申明: 个人流水账日记,内容并不保证有效

暂无评论

暂无评论...

随机推荐

ATC平台去掉U盘的fsck功能

前言项目中如果插入U盘进行fsck时会影响U盘的识别(挂载)性能。正文ATC平台是有新增配置选项的/device/autochips/ac8x_demo/system.prop记录一下,当然,默认应该是关闭了的,但选项功能还是有的。#external device (for examp...

十六进制和十进制之间的转换

前言进制转换是人们利用符号来计数的方法。十六进制与十进制之间的转换包括十进制转十六进制和十六进制转十进制。正文好久不接触这个,现在一不留神就又迷迷茫茫的对不上号了。今天就温习一下“十六进制转十进制”和“十进制转十六进制”,记录于此,方便自己回顾。十进制转十六进制十进制转十六进制分为整...

毕淑敏: 流露你的真表情

学医的时候,老师出过一道题目:人和动物,在解剖上的最大区别是什么?学生们争先恐后发言,都想由自己说出那个正确的答案。这看起来并不是个很难的问题。有人说,是直立行走。先生说,不对。大猩猩也是可以直立行走的。有人说,是懂得用火。先生不悦道,我问的是生理上的区别,并不是进化工的异同。更有同学答,是...

[摘]Android GC日志简单分析

前言测试反馈的日志中有大量的dalvikvm,如下:D/dalvikvm( 723): GC_CONCURRENT freed 658K, 38% free 1168K/1876K, paused 1ms+1ms, total 11msD/dalvikvm( 526): GC_FOR_A...

顾城:树枝的疏忽

我喜古诗,不因文学史,不因人们的仰望,而在它的美丽,文字清简明润,如玉如天,在于它显示出的中国哲思,那一无言就在眼前,若张九龄句:海上生明月,天涯共此时。诗如禅,如顿悟——骤然风动云散,黑暗退隐,你看见万物万象,明媚自如。“红豆生南国,春来发几枝”,气象柔和空阔;红豆生于南国,红豆生出南国,色空互...

Launcher2源码之壁纸设置

上回我们简单介绍了Launcher中的布局,这次我们看看如何设置壁纸。在Launcher界面,长按空白处,就会弹出如上图的壁纸设置对话框。直接上代码Launcher.java1. onCreate() 加载布局和初始化控件 @Override protected v...