Tối ưu hoá thời gian khởi động

Trang này cung cấp các mẹo để cải thiện thời gian khởi động.

Loại bỏ biểu tượng gỡ lỗi khỏi các mô-đun

Tương tự như cách xoá các ký hiệu gỡ lỗi khỏi hạt nhân trên thiết bị phát hành chính thức, hãy nhớ xoá các ký hiệu gỡ lỗi khỏi các mô-đun. Việc loại bỏ các ký hiệu gỡ lỗi khỏi mô-đun giúp giảm thời gian khởi động bằng cách giảm các yếu tố sau:

  • Thời gian cần thiết để đọc tệp nhị phân từ bộ nhớ flash.
  • Thời gian cần thiết để giải nén ổ đĩa RAM.
  • Thời gian tải các mô-đun.

Việc loại bỏ biểu tượng gỡ lỗi khỏi các mô-đun có thể tiết kiệm vài giây trong quá trình khởi động.

Tính năng loại bỏ biểu tượng được bật theo mặc định trong bản dựng nền tảng Android, nhưng để bật các tính năng này một cách rõ ràng, hãy đặt BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES trong cấu hình dành riêng cho thiết bị của bạn trong device/vendor/device.

Sử dụng tính năng nén LZ4 cho nhân và ramdisk

Gzip tạo ra đầu ra nén nhỏ hơn so với LZ4, nhưng LZ4 giải nén nhanh hơn so với Gzip. Đối với nhân và mô-đun, việc giảm kích thước bộ nhớ tuyệt đối khi sử dụng Gzip không đáng kể so với lợi ích về thời gian giải nén của LZ4.

Tính năng hỗ trợ nén ramdisk LZ4 đã được thêm vào bản dựng nền tảng Android thông qua BOARD_RAMDISK_USE_LZ4. Bạn có thể đặt tuỳ chọn này trong cấu hình dành riêng cho thiết bị. Bạn có thể thiết lập tính năng nén nhân qua kernel defconfig.

Nếu bạn chuyển sang LZ4, thời gian khởi động nhanh hơn từ 500 mili giây đến 1.000 mili giây.

Tránh ghi nhật ký quá mức trong trình điều khiển

Trong ARM64 và ARM32, các lệnh gọi hàm cách xa vị trí gọi một khoảng cách cụ thể cần có bảng nhảy (còn gọi là bảng liên kết quy trình hoặc PLT) để có thể mã hoá địa chỉ nhảy đầy đủ. Vì các mô-đun được tải động, nên bạn cần sửa các bảng nhảy này trong quá trình tải mô-đun. Các lệnh gọi cần di chuyển được gọi là mục nhập di chuyển có các mục bổ sung rõ ràng (viết tắt là RELA) ở định dạng ELF.

Hạt nhân Linux thực hiện một số hoạt động tối ưu hoá kích thước bộ nhớ (chẳng hạn như tối ưu hoá số lần truy cập bộ nhớ đệm) khi phân bổ PLT. Với cam kết ngược dòng này, lược đồ tối ưu hoá có độ phức tạp O(N^2), trong đó N là số lượng RELA thuộc loại R_AARCH64_JUMP26 hoặc R_AARCH64_CALL26. Vì vậy, việc có ít RELA hơn thuộc các loại này sẽ giúp giảm thời gian tải mô-đun.

Một mẫu lập trình phổ biến làm tăng số lượng RELA R_AARCH64_CALL26 hoặc R_AARCH64_JUMP26 là việc đăng nhập quá nhiều vào trình điều khiển. Mỗi lệnh gọi đến printk() hoặc bất kỳ giao thức ghi nhật ký nào khác thường thêm mục nhập RELA CALL26/JUMP26. Trong văn bản thay đổi trong thay đổi ngược dòng, hãy lưu ý rằng ngay cả khi được tối ưu hoá, 6 mô-đun này vẫn mất khoảng 250 mili giây để tải. Đó là vì 6 mô-đun đó là 6 mô-đun hàng đầu có nhiều hoạt động ghi nhật ký nhất.

Việc giảm hoạt động ghi nhật ký có thể giúp tiết kiệm khoảng 100 – 300 mili giây thời gian khởi động, tuỳ thuộc vào mức độ ghi nhật ký hiện tại.

Bật tính năng thăm dò không đồng bộ, có chọn lọc

Khi một mô-đun được tải, nếu thiết bị mà mô-đun đó hỗ trợ đã được điền từ DT (cây thiết bị) và thêm vào lõi trình điều khiển, thì quy trình thăm dò thiết bị sẽ được thực hiện trong ngữ cảnh của lệnh gọi module_init(). Khi một quy trình thăm dò thiết bị được thực hiện trong ngữ cảnh của module_init(), mô-đun không thể tải xong cho đến khi quy trình thăm dò hoàn tất. Vì quá trình tải mô-đun chủ yếu được chuyển đổi tuần tự, nên một thiết bị mất tương đối nhiều thời gian để thăm dò sẽ làm chậm thời gian khởi động.

Để tránh thời gian khởi động chậm hơn, hãy bật tính năng thăm dò không đồng bộ cho các mô-đun mất một chút thời gian để thăm dò thiết bị của chúng. Việc bật tính năng thăm dò không đồng bộ cho tất cả các mô-đun có thể không có lợi vì thời gian phân nhánh luồng và bắt đầu thăm dò có thể cao bằng thời gian thăm dò thiết bị.

Các thiết bị được kết nối thông qua một bus chậm như I2C, các thiết bị tải firmware trong hàm thăm dò và các thiết bị thực hiện nhiều quá trình khởi chạy phần cứng có thể dẫn đến vấn đề về thời gian. Cách tốt nhất để xác định thời điểm điều này xảy ra là thu thập thời gian thăm dò cho mọi trình điều khiển và sắp xếp thời gian đó.

Để bật tính năng thăm dò không đồng bộ cho một mô-đun thì chưa chỉ đủ để đặt cờ PROBE_PREFER_ASYNCHRONOUS trong mã trình điều khiển. Đối với các mô-đun, bạn cũng cần thêm module_name.async_probe=1 vào dòng lệnh hạt nhân hoặc truyền async_probe=1 dưới dạng tham số mô-đun khi tải mô-đun bằng modprobe hoặc insmod.

Việc bật tính năng thăm dò không đồng bộ có thể tiết kiệm khoảng 100 – 500 mili giây trong thời gian khởi động, tuỳ thuộc vào phần cứng/trình điều khiển của bạn.

Thăm dò trình điều khiển CPUfreq càng sớm càng tốt

Các thăm dò trình điều khiển CPUfreq càng sớm thì bạn càng sớm có thể điều chỉnh tần suất CPU lên mức tối đa (hoặc một mức tối đa bị giới hạn nhiệt) trong quá trình khởi động. CPU càng nhanh thì tốc độ khởi động càng nhanh. Nguyên tắc này cũng áp dụng cho các trình điều khiển devfreq kiểm soát DRAM, bộ nhớ và tần suất kết nối.

Với các mô-đun, thứ tự tải có thể phụ thuộc vào cấp initcall và thứ tự biên dịch hoặc liên kết của trình điều khiển. Sử dụng bí danh MODULE_SOFTDEP() để đảm bảo trình điều khiển cpufreq nằm trong số ít mô-đun đầu tiên được tải.

Ngoài việc tải sớm mô-đun, bạn cũng cần đảm bảo tất cả các phần phụ thuộc để thăm dò trình điều khiển CPUfreq cũng đã được thăm dò. Ví dụ: nếu bạn cần một đồng hồ hoặc tay điều khiển bộ điều chỉnh để kiểm soát tần suất của CPU, hãy đảm bảo rằng bạn đã thăm dò các đồng hồ hoặc tay điều khiển đó trước. Hoặc bạn có thể cần tải trình điều khiển nhiệt trước trình điều khiển CPUfreq nếu CPU của bạn có thể quá nóng trong quá trình khởi động. Vì vậy, hãy làm mọi thứ có thể để đảm bảo CPUfreq và trình điều khiển devfreq liên quan thăm dò càng sớm càng tốt.

Khoản tiết kiệm được từ việc thăm dò sớm trình điều khiển CPUfreq có thể từ nhỏ đến rất lớn, tuỳ thuộc vào việc bạn có thể tiến hành thăm dò sớm hay không và trình tải khởi động rời khỏi CPU ở tần suất nào.

Di chuyển các mô-đun sang phân vùng init giai đoạn hai, nhà cung cấp hoặc nhà cung cấp_dlkm

Vì quy trình khởi động giai đoạn đầu tiên được chuyển đổi tuần tự, nên không có nhiều cơ hội để tải song song quá trình khởi động. Nếu không cần mô-đun để hoàn tất quá trình khởi chạy giai đoạn đầu tiên, hãy di chuyển mô-đun đó sang quá trình khởi chạy giai đoạn thứ hai bằng cách đặt mô-đun đó vào phân vùng nhà cung cấp hoặc vendor_dlkm.

Quá trình khởi chạy giai đoạn đầu không yêu cầu thăm dò một số thiết bị để chuyển sang giai đoạn khởi chạy thứ hai. Chỉ cần khả năng của bảng điều khiển và bộ nhớ flash để khởi động bình thường.

Tải các trình điều khiển thiết yếu sau:

  • watchdog
  • reset
  • cpufreq

Đối với chế độ khôi phục và fastbootd không gian người dùng, quá trình khởi chạy giai đoạn đầu yêu cầu nhiều thiết bị hơn để thăm dò (chẳng hạn như USB) và hiển thị. Lưu một bản sao của các mô-đun này trong ramdisk giai đoạn đầu và trong phân vùng nhà cung cấp hoặc vendor_dlkm. Điều này cho phép các tệp này được tải trong quá trình khởi chạy giai đoạn đầu để khôi phục hoặc quy trình khởi động fastbootd. Tuy nhiên, đừng tải các mô-đun chế độ khôi phục trong quá trình khởi động giai đoạn đầu trong luồng khởi động thông thường. Bạn có thể trì hoãn các mô-đun chế độ khôi phục đến giai đoạn khởi động thứ hai để giảm thời gian khởi động. Tất cả các mô-đun khác không cần thiết trong khởi tạo giai đoạn đầu tiên phải được chuyển sang phân vùng nhà cung cấp hoặc vendor_dlkm.

Với danh sách các thiết bị lá (ví dụ: UFS hoặc nối tiếp), tập lệnh dev needs.sh sẽ tìm thấy tất cả trình điều khiển, thiết bị và mô-đun cần thiết cho các phần phụ thuộc hoặc nhà cung cấp (ví dụ: đồng hồ, bộ điều chỉnh hoặc gpio) để thăm dò.

Việc di chuyển các mô-đun sang quá trình khởi động giai đoạn thứ hai sẽ làm giảm thời gian khởi động theo các cách sau:

  • Giảm kích thước ramdisk.
    • Điều này giúp đọc flash nhanh hơn khi trình tải khởi động tải ramdisk (bước khởi động tuần tự).
    • Điều này giúp tăng tốc độ giải nén khi hạt nhân giải nén ramdisk (bước khởi động tuần tự).
  • Quá trình khởi chạy giai đoạn thứ hai hoạt động song song, ẩn thời gian tải của mô-đun bằng công việc đang được thực hiện trong quá trình khởi chạy giai đoạn thứ hai.

Việc di chuyển các mô-đun sang giai đoạn thứ hai có thể tiết kiệm 500 – 1000 mili giây thời gian khởi động tuỳ thuộc vào số lượng mô-đun bạn có thể di chuyển sang giai đoạn khởi động thứ hai.

Quy trình tải mô-đun

Cấu hình bảng mới nhất của bản dựng Android kiểm soát mô-đun nào được sao chép sang từng giai đoạn và mô-đun nào tải. Phần này tập trung vào tập hợp con sau:

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES. Danh sách các mô-đun cần sao chép vào ramdisk.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD. Danh sách các mô-đun sẽ được tải trong quá trình khởi chạy giai đoạn đầu tiên.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD. Đây là danh sách các mô-đun sẽ được tải khi khôi phục hoặc fastbootd được chọn từ ramdisk.
  • BOARD_VENDOR_KERNEL_MODULES. Danh sách mô-đun này sẽ được sao chép vào phân vùng nhà cung cấp hoặc vendor_dlkm tại thư mục /vendor/lib/modules/.
  • BOARD_VENDOR_KERNEL_MODULES_LOAD. Danh sách các mô-đun sẽ được tải trong init giai đoạn hai.

Các mô-đun khởi động và khôi phục trong ramdisk cũng phải được sao chép vào phân vùng của nhà cung cấp hoặc vendor_dlkm tại /vendor/lib/modules. Việc sao chép các mô-đun này vào phân vùng nhà cung cấp đảm bảo các mô-đun không bị ẩn trong quá trình khởi động giai đoạn thứ hai, điều này hữu ích cho việc gỡ lỗi và thu thập modinfo cho báo cáo lỗi.

Quá trình sao chép sẽ tốn ít dung lượng trên phân vùng nhà cung cấp hoặc vendor_dlkm miễn là bộ mô-đun khởi động được giảm thiểu. Đảm bảo rằng tệp modules.list của nhà cung cấp có danh sách các mô-đun đã lọc trong /vendor/lib/modules. Danh sách được lọc đảm bảo thời gian khởi động không bị ảnh hưởng bởi các mô-đun tải lại (đây là một quá trình tốn kém).

Đảm bảo rằng các mô-đun chế độ khôi phục tải dưới dạng một nhóm. Bạn có thể tải các mô-đun chế độ khôi phục trong chế độ khôi phục hoặc ở đầu quá trình khởi động giai đoạn thứ hai trong mỗi luồng khởi động.

Bạn có thể sử dụng các tệp Board.Config.mk của thiết bị để thực hiện các thao tác này như trong ví dụ sau:

# All kernel modules
KERNEL_MODULES := $(wildcard $(KERNEL_MODULE_DIR)/*.ko)
KERNEL_MODULES_LOAD := $(strip $(shell cat $(KERNEL_MODULE_DIR)/modules.load)

# First stage ramdisk modules
BOOT_KERNEL_MODULES_FILTER := $(foreach m,$(BOOT_KERNEL_MODULES),%/$(m))

# Recovery ramdisk modules
RECOVERY_KERNEL_MODULES_FILTER := $(foreach m,$(RECOVERY_KERNEL_MODULES),%/$(m))
BOARD_VENDOR_RAMDISK_KERNEL_MODULES += \
     $(filter $(BOOT_KERNEL_MODULES_FILTER) \
                $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES))

# ALL modules land in /vendor/lib/modules so they could be rmmod/insmod'd,
# and modules.list actually limits us to the ones we intend to load.
BOARD_VENDOR_KERNEL_MODULES := $(KERNEL_MODULES)
# To limit /vendor/lib/modules to just the ones loaded, use:
# BOARD_VENDOR_KERNEL_MODULES := $(filter-out \
#     $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES))

# Group set of /vendor/lib/modules loading order to recovery modules first,
# then remainder, subtracting both recovery and boot modules which are loaded
# already.
BOARD_VENDOR_KERNEL_MODULES_LOAD := \
        $(filter-out $(BOOT_KERNEL_MODULES_FILTER), \
        $(filter $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD)))
BOARD_VENDOR_KERNEL_MODULES_LOAD += \
        $(filter-out $(BOOT_KERNEL_MODULES_FILTER) \
            $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))

# NB: Load order governed by modules.load and not by $(BOOT_KERNEL_MODULES)
BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD := \
        $(filter $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))

# Group set of /vendor/lib/modules loading order to boot modules first,
# then the remainder of recovery modules.
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD := \
    $(filter $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD += \
    $(filter-out $(BOOT_KERNEL_MODULES_FILTER), \
    $(filter $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD)))

Ví dụ này cho thấy một tập hợp con BOOT_KERNEL_MODULESRECOVERY_KERNEL_MODULES dễ quản lý hơn để được chỉ định cục bộ trong các tệp cấu hình bảng. Tập lệnh trước tìm và điền từng mô-đun con trong các mô-đun nhân có sẵn đã chọn, để lại các mô-đun còn lại cho quá trình khởi động giai đoạn thứ hai.

Đối với quá trình khởi động giai đoạn thứ hai, bạn nên chạy quá trình tải mô-đun dưới dạng dịch vụ để quá trình này không chặn luồng khởi động. Sử dụng tập lệnh shell để quản lý quá trình tải mô-đun sao cho các công việc hậu cần khác, chẳng hạn như xử lý và giảm thiểu lỗi hoặc hoàn tất tải mô-đun, có thể được báo cáo lại (hoặc bị bỏ qua) nếu cần.

Bạn có thể bỏ qua lỗi tải mô-đun gỡ lỗi không xuất hiện trên các bản dựng dành cho người dùng. Để bỏ qua lỗi này, hãy đặt thuộc tính vendor.device.modules.ready để kích hoạt các giai đoạn sau của quy trình khởi động tập lệnh init rc để tiếp tục chuyển sang màn hình khởi chạy. Tham khảo tập lệnh mẫu sau đây, nếu bạn có mã sau trong /vendor/etc/init.insmod.sh:

#!/vendor/bin/sh
. . .
if [ $# -eq 1 ]; then
  cfg_file=$1
else
  # Set property even if there is no insmod config
  # to unblock early-boot trigger
  setprop vendor.common.modules.ready
  setprop vendor.device.modules.ready
  exit 1
fi

if [ -f $cfg_file ]; then
  while IFS="|" read -r action arg
  do
    case $action in
      "insmod") insmod $arg ;;
      "setprop") setprop $arg 1 ;;
      "enable") echo 1 > $arg ;;
      "modprobe") modprobe -a -d /vendor/lib/modules $arg ;;
     . . .
    esac
  done < $cfg_file
fi

Trong tệp rc phần cứng, bạn có thể chỉ định dịch vụ one shot bằng:

service insmod-sh /vendor/etc/init.insmod.sh /vendor/etc/init.insmod.<hw>.cfg
    class main
    user root
    group root system
    Disabled
    oneshot

Bạn có thể thực hiện thêm các biện pháp tối ưu hoá sau khi các mô-đun di chuyển từ giai đoạn đầu tiên sang giai đoạn thứ hai. Bạn có thể sử dụng tính năng danh sách chặn của modprobe để chia nhỏ quy trình khởi động ở giai đoạn thứ hai nhằm đưa vào trì hoãn việc tải mô-đun của các mô-đun không thiết yếu. Bạn có thể trì hoãn việc tải các mô-đun do một HAL cụ thể sử dụng riêng để chỉ tải các mô-đun đó khi HAL khởi động.

Để cải thiện thời gian khởi động rõ ràng, bạn có thể chọn cụ thể các mô-đun trong dịch vụ tải mô-đun phù hợp hơn với việc tải sau màn hình khởi chạy. Ví dụ: bạn có thể tải trễ một cách rõ ràng các mô-đun cho bộ giải mã video hoặc Wi-Fi sau khi luồng khởi động khởi động đã bị xoá (ví dụ: sys.boot_complete tín hiệu thuộc tính Android). Đảm bảo HAL cho các mô-đun tải trễ chặn đủ lâu khi không có trình điều khiển hạt nhân.

Ngoài ra, bạn có thể sử dụng lệnh wait<file>[<timeout>] của init trong tập lệnh rc của quy trình khởi động để chờ các mục sysfs đã chọn cho biết rằng các mô-đun trình điều khiển đã hoàn tất các thao tác thăm dò. Một ví dụ về trường hợp này là chờ trình điều khiển hiển thị hoàn tất tải trong nền của khôi phục hoặc fastbootd, trước khi hiển thị đồ hoạ trình đơn.

Khởi tạo tần suất CPU thành một giá trị hợp lý trong trình tải khởi động

Không phải mọi SoC/sản phẩm đều có thể khởi động CPU ở tần số cao nhất do các vấn đề về nhiệt hoặc nguồn điện trong quá trình kiểm thử vòng lặp khởi động. Tuy nhiên, hãy đảm bảo rằng trình tải khởi động đặt tần suất của tất cả CPU trực tuyến ở mức cao nhất có thể một cách an toàn cho một SoC hoặc sản phẩm. Điều này rất quan trọng vì với một hạt nhân hoàn toàn mô-đun, quá trình giải nén init ramdisk sẽ diễn ra trước khi trình điều khiển CPUfreq có thể được tải. Vì vậy, nếu trình tải khởi động để CPU ở tần suất thấp, thời gian giải nén ramdisk có thể lâu hơn so với hạt nhân được biên dịch tĩnh (sau khi điều chỉnh cho sự khác biệt về kích thước ramdisk) vì tần suất CPU sẽ rất thấp khi thực hiện công việc nặng về CPU (giải nén). Điều này cũng áp dụng cho bộ nhớ và tần suất kết nối.

Khởi động tần suất CPU của các CPU lớn trong trình tải khởi động

Trước khi trình điều khiển CPUfreq được tải, nhân hệ điều hành không nhận biết được tần số của CPU và không mở rộng dung lượng lưu trữ của CPU theo tần suất hiện tại. Hạt nhân có thể di chuyển luồng sang CPU lớn nếu tải đủ cao trên CPU nhỏ.

Đảm bảo CPU lớn có hiệu suất ít nhất bằng CPU nhỏ đối với tần suất mà trình tải khởi động để lại. Ví dụ: nếu CPU lớn có hiệu suất gấp 2 lần CPU nhỏ với cùng tần số, nhưng trình tải khởi động đặt tần số của CPU nhỏ thành 1, 5 GHz và tần số của CPU lớn thành 300 MHz, thì hiệu suất khởi động sẽ giảm nếu nhân hệ điều hành chuyển một luồng sang CPU lớn. Trong ví dụ này, nếu có thể khởi động CPU lớn ở tốc độ 750 MHz một cách an toàn, bạn nên làm như vậy ngay cả khi không có kế hoạch sử dụng CPU lớn một cách rõ ràng.

Trình điều khiển không được tải chương trình cơ sở trong quá trình khởi động giai đoạn đầu

Có thể có một số trường hợp không thể tránh khỏi khi cần tải phần mềm trong giai đoạn khởi chạy đầu tiên. Nhưng nói chung, trình điều khiển không được tải bất kỳ phần mềm nào trong quá trình khởi động giai đoạn đầu, đặc biệt là trong ngữ cảnh thăm dò thiết bị. Việc tải chương trình cơ sở trong quá trình khởi động giai đoạn đầu sẽ khiến toàn bộ quá trình khởi động bị đình trệ nếu chương trình cơ sở không có trong ramdisk giai đoạn đầu. Và ngay cả khi có chương trình cơ sở trong ramdisk giai đoạn đầu, nó vẫn gây ra độ trễ không cần thiết.