Tối ưu hóa thời gian khởi động

Tài liệu này cung cấp hướng dẫn dành cho đối tác để cải thiện thời gian khởi động cho các thiết bị Android cụ thể. Thời gian khởi động là một thành phần quan trọng của hiệu suất hệ thống vì người dùng phải đợi quá trình khởi động hoàn tất trước khi có thể sử dụng thiết bị. Đối với các thiết bị như ô tô, nơi xảy ra hiện tượng khởi động nguội thường xuyên hơn, việc có thời gian khởi động nhanh là rất quan trọng (không ai thích chờ đợi hàng chục giây chỉ để nhập điểm đến điều hướng).

Android 8.0 cho phép giảm thời gian khởi động bằng cách hỗ trợ một số cải tiến trên nhiều thành phần. Bảng sau đây tóm tắt những cải tiến về hiệu suất này (được đo trên thiết bị Google Pixel và Pixel XL).

Thành phần Sự cải tiến
Bộ nạp khởi động
  • Đã lưu 1,6 giây bằng cách xóa nhật ký UART
  • Tiết kiệm 0,4 giây bằng cách đổi sang LZ4 từ GZIP
Nhân thiết bị
  • Tiết kiệm 0,3 giây bằng cách loại bỏ các cấu hình kernel không sử dụng và giảm kích thước trình điều khiển
  • Đã lưu 0,3 giây với tối ưu hóa tìm nạp trước dm-verity
  • Đã lưu 0,15 giây để loại bỏ việc chờ/kiểm tra không cần thiết trong trình điều khiển
  • Đã lưu 0,12 giây để xóa CONFIG_CC_OPTIMIZE_FOR_SIZE
Điều chỉnh I/O
  • Đã lưu 2 giây khi khởi động bình thường
  • Tiết kiệm 25 giây trong lần khởi động đầu tiên
init.*.rc
  • Tiết kiệm 1,5 giây bằng cách thực hiện song song các lệnh init
  • Tiết kiệm 0,25 giây bằng cách bắt đầu hợp tử sớm
  • Đã lưu 0,22 giây bởi cpuset tune
Khởi động hoạt hình
  • Bắt đầu sớm hơn 2 giây khi khởi động mà không kích hoạt fsck, lớn hơn nhiều khi khởi động với khởi động được kích hoạt fsck
  • Tiết kiệm 5 giây trên Pixel XL khi tắt hoạt ảnh khởi động ngay lập tức
Chính sách SELinux Đã lưu 0,2 giây bởi genfscon

Tối ưu hóa Bootloader

Để tối ưu hóa bộ nạp khởi động để cải thiện thời gian khởi động:

  • Để ghi nhật ký:
    • Vô hiệu hóa tính năng ghi nhật ký vào UART vì việc này có thể mất nhiều thời gian khi ghi nhật ký nhiều. (Trên các thiết bị Google Pixel, chúng tôi nhận thấy nó làm chậm bộ tải khởi động 1,5 giây).
    • Chỉ ghi lại các tình huống lỗi và cân nhắc việc lưu trữ các thông tin khác vào bộ nhớ bằng cơ chế truy xuất riêng.
  • Để giải nén kernel, hãy cân nhắc sử dụng LZ4 cho phần cứng hiện đại thay vì GZIP (ví dụ bản vá ). Hãy nhớ rằng các tùy chọn nén hạt nhân khác nhau có thể có thời gian tải và giải nén khác nhau và một số tùy chọn có thể hoạt động tốt hơn các tùy chọn khác đối với phần cứng cụ thể của bạn.
  • Kiểm tra thời gian chờ đợi không cần thiết để gỡ lỗi/vào chế độ đặc biệt và giảm thiểu chúng.
  • Chuyển thời gian khởi động trong bộ nạp khởi động sang kernel dưới dạng cmdline.
  • Kiểm tra xung nhịp CPU và xem xét việc song song hóa (yêu cầu hỗ trợ đa lõi) để tải hạt nhân và khởi tạo I/O.

Tối ưu hóa hiệu quả I/O

Việc cải thiện hiệu suất I/O là rất quan trọng để giúp thời gian khởi động nhanh hơn và việc đọc mọi thứ không cần thiết nên được hoãn lại cho đến sau khi khởi động (trên Google Pixel, khoảng 1,2 GB dữ liệu được đọc khi khởi động).

Tinh chỉnh hệ thống tập tin

Nhân Linux bắt đầu đọc trước khi một tệp được đọc từ đầu hoặc khi các khối được đọc tuần tự, do đó cần phải điều chỉnh các tham số bộ lập lịch I/O cụ thể để khởi động (có đặc tính khối lượng công việc khác với các ứng dụng thông thường).

Các thiết bị hỗ trợ cập nhật liền mạch (A/B) được hưởng lợi rất nhiều từ việc điều chỉnh hệ thống tệp khi khởi động lần đầu (ví dụ: 20 giây trên Google Pixel). Một ví dụ: chúng tôi đã điều chỉnh các thông số sau cho 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
    ...

Điều khoản khác

  • Bật kích thước tìm nạp trước băm dm-verity bằng cấu hình kernel DM_VERITY_HASH_PREFETCH_MIN_SIZE (kích thước mặc định là 128).
  • Để có độ ổn định hệ thống tệp tốt hơn và loại bỏ kiểm tra bắt buộc xảy ra trong mỗi lần khởi động, hãy sử dụng công cụ tạo ext4 mới bằng cách đặt TARGET_USES_MKE2FS trong BoardConfig.mk.

Phân tích vào/ra

Để hiểu các hoạt động I/O trong khi khởi động, hãy sử dụng dữ liệu kernel ftrace (cũng được systrace sử dụng):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

Để phân tích quyền truy cập tệp cho từng tệp, hãy thực hiện các thay đổi sau đối với kernel (chỉ kernel phát triển; không sử dụng trong kernel sản xuất):

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);

Sử dụng các tập lệnh sau để trợ giúp phân tích hiệu suất khởi động.

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py Đo thời gian khởi động bằng cách phân tích các bước quan trọng trong quy trình khởi động.
  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace Cung cấp thông tin truy cập cho mỗi tệp.
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace Cung cấp thông tin chi tiết ở cấp hệ thống.

Tối ưu hóa init.*.rc

Init là cầu nối từ kernel cho đến khi framework được thiết lập và các thiết bị thường dành vài giây ở các giai đoạn init khác nhau.

Chạy các tác vụ song song

Mặc dù init Android hiện tại ít nhiều là một tiến trình đơn luồng, nhưng bạn vẫn có thể thực hiện một số tác vụ song song.

  • Thực thi các lệnh chậm trong dịch vụ tập lệnh shell và tham gia lệnh đó sau bằng cách chờ thuộc tính cụ thể. Android 8.0 hỗ trợ trường hợp sử dụng này bằng lệnh wait_for_property mới.
  • Xác định các hoạt động chậm trong init. Hệ thống ghi lại lệnh init exec/wait_for_prop hoặc bất kỳ hành động nào mất nhiều thời gian (trong Android 8.0, bất kỳ lệnh nào mất hơn 50 mili giây). Ví dụ:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    Việc xem lại nhật ký này có thể chỉ ra các cơ hội cải tiến.

  • Bắt đầu dịch vụ và kích hoạt sớm các thiết bị ngoại vi trong đường dẫn quan trọng. Ví dụ: một số SOC yêu cầu khởi động các dịch vụ liên quan đến bảo mật trước khi khởi động SurfaceFlinger. Xem lại nhật ký hệ thống khi ServiceManager trả về "chờ dịch vụ" — đây thường là dấu hiệu cho thấy dịch vụ phụ thuộc phải được khởi động trước.
  • Xóa mọi dịch vụ và lệnh không sử dụng trong init.*.rc. Bất cứ thứ gì không được sử dụng trong giai đoạn đầu init sẽ được hoãn lại để hoàn thành quá trình khởi động.

Lưu ý: Dịch vụ thuộc tính là một phần của quy trình init, vì vậy việc gọi setproperty trong khi khởi động có thể dẫn đến độ trễ lâu nếu init bận trong các lệnh dựng sẵn.

Sử dụng điều chỉnh lịch trình

Sử dụng điều chỉnh lịch trình để khởi động sớm. Ví dụ từ 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

Một số dịch vụ có thể cần tăng mức độ ưu tiên trong quá trình khởi động. Ví dụ:

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

Bắt đầu hợp tử sớm

Các thiết bị có mã hóa dựa trên tệp có thể khởi động hợp tử sớm hơn khi kích hoạt khởi động hợp tử (theo mặc định, hợp tử được khởi chạy ở lớp chính, muộn hơn nhiều so với khởi động hợp tử). Khi thực hiện việc này, hãy đảm bảo cho phép hợp tử chạy trong tất cả các CPU (vì cài đặt cpuset sai có thể buộc hợp tử chạy trong các CPU cụ thể).

Vô hiệu hóa tiết kiệm năng lượng

Trong quá trình khởi động thiết bị, cài đặt tiết kiệm năng lượng cho các thành phần như UFS và/hoặc bộ điều chỉnh CPU có thể bị tắt.

Thận trọng: Nên bật tính năng tiết kiệm năng lượng ở chế độ sạc để đạt hiệu quả.

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

Trì hoãn việc khởi tạo không quan trọng

Việc khởi tạo không quan trọng như ZRAM có thể được hoãn lại thành boot_complete.

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

Tối ưu hóa hoạt ảnh khởi động

Sử dụng các mẹo sau để tối ưu hóa hoạt ảnh khởi động.

Định cấu hình khởi động sớm

Android 8.0 cho phép bắt đầu hoạt ảnh khởi động sớm trước khi gắn phân vùng dữ liệu người dùng. Tuy nhiên, ngay cả khi sử dụng chuỗi công cụ ext4 mới trong Android 8.0, fsck vẫn được kích hoạt định kỳ vì lý do an toàn, gây ra tình trạng chậm trễ khi khởi động dịch vụ bootanimation.

Để bắt đầu quá trình bootanimation sớm, hãy chia fstab mount thành hai giai đoạn:

  • Trong giai đoạn đầu, chỉ gắn kết các phân vùng (chẳng hạn như system/vendor/ ) không yêu cầu kiểm tra lần chạy, sau đó khởi động các dịch vụ hoạt ảnh khởi động và các phần phụ thuộc của nó (chẳng hạn như servicemanager và surfaceflinger).
  • Trong giai đoạn thứ hai, gắn kết các phân vùng (chẳng hạn như data/ ) yêu cầu kiểm tra lần chạy.

Hoạt ảnh khởi động sẽ được bắt đầu nhanh hơn nhiều (và trong thời gian không đổi) bất kể fsck.

Hoàn thiện sạch sẽ

Sau khi nhận được tín hiệu thoát, bootanimation sẽ phát phần cuối cùng, độ dài của phần này có thể làm chậm thời gian khởi động. Một hệ thống khởi động nhanh chóng không cần những hình ảnh động dài dòng có thể che giấu bất kỳ cải tiến nào được thực hiện một cách hiệu quả. Chúng tôi khuyên bạn nên làm ngắn cả vòng lặp và phần cuối.

Tối ưu hóa SELinux

Sử dụng các mẹo sau để tối ưu hóa SELinux nhằm cải thiện thời gian khởi động.

  • Sử dụng các biểu thức chính quy rõ ràng (regex) . Regex được định dạng kém có thể dẫn đến nhiều chi phí khi khớp chính sách SELinux cho sys/devices trong file_contexts . Ví dụ: biểu thức chính quy /sys/devices/.*abc.*(/.*)? buộc quét nhầm tất cả các thư mục con /sys/devices có chứa "abc", cho phép khớp cả /sys/devices/abc/sys/devices/xyz/abc . Cải thiện biểu thức chính quy này thành /sys/devices/[^/]*abc[^/]*(/.*)? sẽ chỉ kích hoạt kết quả khớp cho /sys/devices/abc .
  • Di chuyển nhãn sang genfscon . Tính năng SELinux hiện có này chuyển các tiền tố khớp tệp vào hạt nhân trong hệ nhị phân SELinux, nơi hạt nhân áp dụng chúng cho các hệ thống tệp do hạt nhân tạo ra. Điều này cũng giúp sửa các tệp do kernel tạo bị gắn nhãn sai, ngăn ngừa tình trạng chạy đua có thể xảy ra giữa các quá trình trong không gian người dùng đang cố gắng truy cập các tệp này trước khi việc gắn nhãn lại xảy ra.

Công cụ và phương pháp

Sử dụng các công cụ sau để giúp bạn thu thập dữ liệu cho mục tiêu tối ưu hóa.

Sơ đồ khởi động

Bootchart cung cấp thông tin phân tích tải CPU và I/O của tất cả các tiến trình cho toàn bộ hệ thống. Nó không yêu cầu xây dựng lại hình ảnh hệ thống và có thể được sử dụng để kiểm tra tình trạng nhanh chóng trước khi đi sâu vào systrace.

Để kích hoạt biểu đồ khởi động:

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

Sau khi khởi động, lấy biểu đồ khởi động:

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

Khi hoàn tất, hãy xóa /data/bootchart/enabled để tránh việc thu thập dữ liệu mỗi lần.

Nếu bootchart không hoạt động và bạn gặp lỗi thông báo rằng bootchart.png không tồn tại, hãy làm như sau:
  1. Chạy các lệnh sau:
          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
        
  2. Cập nhật $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh để trỏ đến bản sao cục bộ của pybootchartgui (nằm tại ~/Documents/bootchart/pybootchartgui.py )

Systrace

Systrace cho phép thu thập cả dấu vết kernel và Android trong quá trình khởi động. Hình dung systrace có thể giúp phân tích vấn đề cụ thể trong quá trình khởi động. (Tuy nhiên, để kiểm tra số trung bình hoặc số tích lũy trong toàn bộ quá trình khởi động, việc xem trực tiếp dấu vết kernel sẽ dễ dàng hơn).

Để bật systrace trong khi khởi động:

  • Trong frameworks/native/cmds/atrace/atrace.rc , thay đổi:
      write /sys/kernel/debug/tracing/tracing_on 0
      write /sys/kernel/tracing/tracing_on 0

    ĐẾN:

      #    write /sys/kernel/debug/tracing/tracing_on 0
      #    write /sys/kernel/tracing/tracing_on 0
  • Điều này cho phép theo dõi (bị tắt theo mặc định).

  • Trong tệp device.mk , thêm dòng sau:
    PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
    PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  • Trong tệp BoardConfig.mk của thiết bị, thêm phần sau:
    BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
  • Để phân tích I/O chi tiết, hãy thêm khối và ext4 và f2fs.

  • Trong tệp init.rc dành riêng cho thiết bị, hãy thêm thông tin sau:
    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
    
  • Sau khi khởi động, tìm nạp dấu vết:

    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