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

Trang này cung cấp một tập hợp các mẹo mà bạn có thể chọn để cải thiện thời gian khởi động.

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

Tương tự như cách loại bỏ các ký hiệu gỡ lỗi khỏi kernel trên thiết bị sản xuất, hãy đảm bảo bạn cũng loại bỏ các ký hiệu gỡ lỗi khỏi các mô-đun. Việc loại bỏ các biểu tượng 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 thông tin sau:

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

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

Tính năng tước 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 chúng 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 nén LZ4 cho kernel 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 Gzip. Đối với nhân và mô-đun, việc giảm kích thước lưu trữ 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.

Hỗ trợ nén đĩa ram 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 tùy chọn này trong cấu hình dành riêng cho thiết bị của mình. Nén hạt nhân có thể được thiết lập thông qua kernel defconfig.

Chuyển sang LZ4 sẽ cho thời gian khởi động nhanh hơn từ 500ms đến 1000ms.

Tránh đăng nhập quá nhiều vào trình điều khiển của bạn

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

Nhân Linux thực hiện một số tối ưu hóa kích thước bộ nhớ (chẳng hạn như tối ưu hóa lần truy cập bộ đệm) khi phân bổ PLT. Với cam kết ngược dòng này, sơ đồ tối ưu hóa 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 thuộc loại này sẽ hữu ích hơn trong việc giảm thời gian tải mô-đun.

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

Giảm ghi nhật ký có thể tiết kiệm có thể tiết kiệm khoảng 100 - 300 mili giây khi khởi động tùy thuộc vào mức độ ghi nhật ký hiện tại.

Cho phép 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à nó hỗ trợ đã được điền từ DT (devicetree) và được thêm vào lõi trình điều khiển thì việc 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 quá trình thăm dò thiết bị được thực hiện trong ngữ cảnh module_init() , mô-đun không thể tải xong cho đến khi quá trình thăm dò hoàn tất. Vì việc tải mô-đun chủ yếu được tuần tự hóa nên thiết bị mất 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 cần thiết để phân nhánh một luồng và khởi động thăm dò có thể cao bằng thời gian cần thiết để thăm dò thiết bị.

Các thiết bị được kết nối qua bus chậm như I2C, các thiết bị tải chương trình cơ sở trong chức năng thăm dò của chúng và các thiết bị thực hiện nhiều quá trình khởi tạo 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ò của từng tài xế và sắp xếp nó.

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

Kích hoạ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 thời gian khởi động tùy 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ủa bạn càng sớm càng tốt

Trình điều khiển CPUfreq của bạn thăm dò càng sớm thì bạn càng có thể tăng tần số CPU lên mức tối đa (hoặc một số mức tối đa bị giới hạn về nhiệt) trong khi khởi động càng sớm. CPU càng nhanh thì khởi động càng nhanh. Nguyên tắc này cũng áp dụng cho trình điều khiển devfreq kiểm soát DRAM, bộ nhớ và tần số 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 mô-đun sớm, 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 đồng hồ hoặc tay cầm điều chỉnh để kiểm soát tần số của CPU, hãy đảm bảo rằng chúng được thăm dò 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 khi khởi động. Vì vậy, hãy làm những gì có thể để đảm bảo CPUfreq và trình điều khiển devfreq có liên quan thăm dò càng sớm càng tốt.

Số tiền tiết kiệm được từ việc thăm dò sớm trình điều khiển CPUfreq của bạn có thể rất nhỏ đến rất lớn tùy thuộc vào việc bạn có thể thăm dò những trình điều khiển này sớm đến mức nào và tần số mà bộ nạp khởi động đưa CPU vào.

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

Bởi vì quá trình init giai đoạn đầu tiên được tuần tự hóa nên không có nhiều cơ hội để song song hóa quá trình khởi động. Nếu không cần mô-đun để quá trình init giai đoạn đầu tiên hoàn tất, hãy di chuyển mô-đun sang init 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 .

Init giai đoạn đầu tiên không yêu cầu thăm dò một số thiết bị để chuyển sang init giai đoạn thứ hai. Chỉ cần có chức năng lưu trữ flash và bảng điều khiển cho quy trình khởi động bình thường.

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

  • cơ quan giám sát
  • cài lại
  • cpufreq

Để khôi phục và chế độ fastbootd không gian người dùng, giai đoạn đầu init yêu cầu nhiều thiết bị hơn để thăm dò (chẳng hạn như USB) và hiển thị. Giữ một bản sao của các mô-đun này trong đĩa ram giai đoạn đầu tiên và trong phân vùng nhà cung cấp hoặc vendor_dlkm . Điều này cho phép chúng được tải trong giai đoạn đầu tiên của quá trình khởi động recovery hoặc fastbootd . Tuy nhiên, không tải các mô-đun chế độ khôi phục ở giai đoạn khởi tạo đầu tiên trong quá trình khởi động bình thường. Các mô-đun chế độ khôi phục có thể được hoãn lại sang giai đoạn khởi tạo 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 giai đoạn đầu tiên nên được chuyển sang phân vùng nhà cung cấp hoặc phân vùng vendor_dlkm .

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

Di chuyển các mô-đun sang init giai đoạn thứ hai sẽ giảm thời gian khởi động theo các cách sau:

  • Giảm kích thước đĩa Ram.
    • Điều này mang lại tốc độ đọc flash nhanh hơn khi bộ nạp khởi động tải ramdisk (bước khởi động nối tiếp).
    • Điều này mang lại tốc độ giải nén nhanh hơn khi kernel giải nén ramdisk (bước khởi động được tuần tự hóa).
  • init giai đoạn thứ hai hoạt động song song, ẩn thời gian tải của mô-đun với công việc đang được thực hiện trong init giai đoạn thứ hai.

Việc di chuyển 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 tùy thuộc vào số lượng mô-đun bạn có thể chuyển sang giai đoạn khởi tạo thứ hai.

Hậu cần tải mô-đun

Bản dựng Android mới nhất có các cấu hình bảng kiểm soát mô-đun nào sao chép sang từng giai đoạn và mô-đun nào sẽ 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 này sẽ được sao chép vào ramdisk.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD . Danh sách các mô-đun này sẽ được tải trong giai đoạn init đầu tiên.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD . Danh sách các mô-đun này sẽ được tải khi recovery hoặc fastbootd được chọn từ ramdisk.
  • BOARD_VENDOR_KERNEL_MODULES . Danh sách các 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 này sẽ được tải trong init giai đoạn thứ 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 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 của nhà cung cấp sẽ đảm bảo các mô-đun không bị ẩn trong quá trình khởi tạo giai đoạn thứ hai, điều này rất hữu ích cho việc gỡ lỗi và thu thập modinfo sửa đổi cho các báo cáo lỗi.

Việc sao chép sẽ tốn không gian tối thiểu 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 được 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 việc tải lại mô-đun (đâ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. Việc tải các mô-đun chế độ khôi phục có thể được thực hiện ở chế độ khôi phục hoặc ở đầu giai đoạn khởi tạo thứ hai trong mỗi luồng khởi động.

Bạn có thể sử dụng tệp Board.Config.mk của thiết bị để thực hiện các hành động 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 giới thiệu 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 vào từng mô-đun tập hợp con từ các mô-đun hạt nhân có sẵn đã chọn, để lại các mô-đun còn lại cho giai đoạn khởi tạo thứ hai.

Đối với init giai đoạn thứ hai, chúng tôi khuyên bạn nên chạy tải mô-đun dưới dạng dịch vụ để nó không chặn luồng khởi động. Sử dụng tập lệnh shell để quản lý việc tải mô-đun để các hoạt động hậu cần khác, chẳng hạn như xử lý và giảm thiểu lỗi hoặc hoàn thành tải mô-đun, có thể được báo cáo lại (hoặc 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 bản dựng của 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 luồng khởi động tập lệnh init rc để tiếp tục trên màn hình khởi chạy. Hãy tham khảo tập lệnh mẫu sau, nếu bạn có đoạn 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, dịch vụ one shot có thể được chỉ định 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

Tối ưu hóa bổ sung có thể được thực hiện sau khi các mô-đun 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 modprobe để phân chia luồng khởi động giai đoạn thứ hai nhằm bao gồm việc tải mô-đun bị trì hoãn đối với các mô-đun không cần thiết. Việc tải các mô-đun được sử dụng riêng bởi một HAL cụ thể có thể được hoãn lại để chỉ tải các mô-đun khi HAL được 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 thuận lợi hơn cho việc tải sau màn hình khởi chạy. Ví dụ: bạn có thể tải muộn một cách rõ ràng các mô-đun cho bộ giải mã video hoặc wifi sau khi luồng khởi động init đã bị xóa (ví dụ: tín hiệu thuộc tính Android sys.boot_complete ). Đảm bảo khối HAL dành cho các mô-đun tải muộ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 luồng khởi động để chờ các mục nhập sysfs chọn lọc cho thấy rằng các mô-đun trình điều khiển đã hoàn thành các hoạt động thăm dò. Một ví dụ về điều này là chờ trình điều khiển hiển thị hoàn tất tải trong nền recovery hoặc fastbootd trước khi hiển thị đồ họa menu.

Khởi tạo tần số CPU thành giá trị hợp lý trong bộ nạp khởi động

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

Khởi tạo tần số CPU của CPU lớn trong bootloader

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

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

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

Có thể có một số trường hợp không thể tránh khỏi khi phần sụn cần được tải ở giai đoạn đầu tiên. Nhưng nói chung, trình điều khiển không nên tải bất kỳ chương trình cơ sở nào trong quá trình khởi tạo giai đoạn đầu tiên, đặc biệt là trong bối cảnh thăm dò thiết bị. Việc tải chương trình cơ sở ở giai đoạn đầu tiên 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ó sẵn trong đĩa ram giai đoạn đầu tiên. Và ngay cả khi phần sụn có trong đĩa ram giai đoạn đầu tiên, nó vẫn gây ra sự chậm trễ không đáng có.