Pengoptimalan waktu booting

Halaman ini memberikan tips untuk meningkatkan waktu booting.

Menghapus simbol debug dari modul

Serupa dengan cara simbol debug dihapus dari kernel di perangkat produksi, pastikan Anda juga menghapus simbol debug dari modul. Menghapus debug simbol dari modul membantu waktu booting dengan mengurangi hal berikut:

  • Waktu yang diperlukan untuk membaca biner dari flash.
  • Waktu yang diperlukan untuk mendekompresi ramdisk.
  • Waktu yang diperlukan untuk memuat modul.

Menghapus simbol debug dari modul dapat menghemat waktu beberapa detik selama booting.

Penghapusan simbol diaktifkan secara default dalam build platform Android, tetapi untuk mengaktifkannya secara eksplisit, tetapkan BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES dalam konfigurasi khusus perangkat di bagian perangkat/vendor/device.

Menggunakan kompresi LZ4 untuk kernel dan ramdisk

Gzip menghasilkan output terkompresi yang lebih kecil dibandingkan dengan LZ4, tetapi LZ4 melakukan dekompresi lebih cepat daripada Gzip. Untuk kernel dan modul, pengurangan ukuran penyimpanan absolut dari penggunaan Gzip tidak terlalu signifikan dibandingkan dengan manfaat waktu dekompresi LZ4.

Dukungan untuk kompresi ramdisk LZ4 telah ditambahkan ke build platform Android melalui BOARD_RAMDISK_USE_LZ4. Anda dapat menetapkan opsi ini di konfigurasi khusus perangkat. Kompresi kernel dapat diatur melalui {i>kernel defconfig<i}.

Beralih ke LZ4 akan memberikan waktu booting yang lebih cepat 500 md hingga 1.000 md.

Hindari logging berlebihan di driver Anda

Di ARM64 dan ARM32, panggilan fungsi yang lebih dari jarak tertentu dari situs panggilan memerlukan tabel lompatan (disebut tabel penautan prosedur, atau PLT) agar dapat mengenkode alamat lompatan penuh. Karena modul dimuat secara dinamis, tabel lompat ini perlu diperbaiki selama pemuatan modul. Panggilan yang memerlukan relokasi disebut entri relokasi dengan entri tambahan eksplisit (atau RELA, untuk singkatnya) dalam format ELF.

Kernel Linux melakukan beberapa pengoptimalan ukuran memori (seperti cache ditemukan pengoptimalan) saat mengalokasikan PLT. Dengan commit upstream ini, skema pengoptimalan memiliki kompleksitas O(N^2), dengan N adalah jumlah RELA dari jenis R_AARCH64_JUMP26 atau R_AARCH64_CALL26. Jadi memiliki lebih sedikit RELA jenis ini sangat membantu dalam mengurangi waktu muat modul.

Salah satu pola coding umum yang meningkatkan jumlah RELA R_AARCH64_CALL26 atau R_AARCH64_JUMP26 adalah logging yang berlebihan di driver. Setiap panggilan ke printk() atau skema logging lainnya biasanya menambahkan CALL26/JUMP26 Entri RELA. Dalam teks commit dalam commit upstream, perhatikan bahwa meskipun dengan pengoptimalan, enam modul memerlukan waktu sekitar 250 md untuk dimuat—hal ini karena enam modul tersebut adalah enam modul teratas dengan jumlah logging terbanyak.

Mengurangi logging dapat menghemat waktu sekitar 100 - 300 md pada waktu booting, bergantung pada seberapa berlebihan logging yang ada.

Mengaktifkan probing asinkron, secara selektif

Saat modul dimuat, jika perangkat yang didukungnya telah diisi dari DT (devicetree) dan ditambahkan ke core driver, maka probe perangkat dilakukan dalam konteks panggilan module_init(). Ketika pemeriksaan perangkat dilakukan dalam konteks module_init(), modul tidak dapat menyelesaikan pemuatan sampai probe selesai. Karena pemuatan modul sebagian besar diserialisasi, perangkat yang memerlukan waktu relatif lama untuk melakukan pemeriksaan akan memperlambat waktu booting.

Untuk menghindari waktu booting yang lebih lambat, aktifkan pemindaian asinkron untuk modul yang memerlukan waktu untuk memindai perangkatnya. Mengaktifkan pemindaian asinkron untuk semua modul mungkin tidak bermanfaat karena waktu yang diperlukan untuk membuat fork thread dan memulai pemindaian mungkin sama tingginya dengan waktu yang diperlukan untuk memindai perangkat.

Perangkat yang terhubung melalui bus lambat seperti I2C, perangkat yang melakukan pemuatan firmware dalam fungsi probe, dan perangkat yang melakukan banyak inisialisasi hardware dapat menyebabkan masalah pengaturan waktu. Cara terbaik untuk mengidentifikasi kapan yang terjadi adalah mengumpulkan waktu penyelidikan untuk setiap pengemudi dan mengurutkannya.

Untuk mengaktifkan pemeriksaan asinkron untuk suatu modul, pemeriksaan tidak cukup untuk hanya setel PROBE_PREFER_ASYNCHRONOUS di kode {i>driver<i}. Untuk modul, Anda juga perlu menambahkan module_name.async_probe=1 di command line kernel atau teruskan async_probe=1 sebagai parameter modul saat memuat modul menggunakan modprobe atau insmod.

Mengaktifkan probing asinkron dapat menghemat sekitar 100 - 500 md pada waktu booting, bergantung pada hardware/driver Anda.

Periksa driver CPUfreq sedini mungkin

Semakin awal pemeriksaan driver CPUfreq, semakin cepat Anda dapat menskalakan CPU ke maksimum (atau beberapa maksimum yang dibatasi secara termal) selama booting. Tujuan semakin cepat CPU, semakin cepat waktu {i>booting<i}. Panduan ini juga berlaku untuk driver devfreq yang mengontrol DRAM, memori, dan frekuensi interkoneksi.

Dengan modul, pengurutan pemuatan dapat bergantung pada level initcall dan kompilasi atau tautan dari {i>driver<i}. Gunakan alias MODULE_SOFTDEP() untuk membuat pastikan driver cpufreq ada di antara beberapa modul pertama yang dimuat.

Selain memuat modul lebih awal, Anda juga perlu memastikan semua dependensi untuk menyelidiki driver CPUfreq juga telah diselidiki. Misalnya, jika Anda memerlukan handle regulator atau clock untuk mengontrol frekuensi CPU, pastikan handle tersebut diuji terlebih dahulu. Atau Anda mungkin memerlukan {i>driver<i} termal untuk dimuat sebelum {i>driver<i} CPUfreq jika mungkin CPU Anda terlalu panas selama {i>booting<i}. Jadi, lakukan apa yang Anda bisa untuk memastikan CPUfreq dan driver devfreq yang relevan menyelidiki sedini mungkin.

Penghematan dari menyelidiki driver CPUfreq lebih awal dapat sangat kecil hingga sangat besar, bergantung pada seberapa awal Anda dapat menyelidikinya dan pada frekuensi apa bootloader meninggalkan CPU.

Memindahkan modul ke partisi init tahap kedua, vendor, atau vendor_dlkm

Karena proses init tahap pertama diserialisasi, tidak ada banyak kesempatan untuk melakukan paralelisasi proses booting. Jika modul tidak diperlukan untuk langkah pertama hingga selesai, pindahkan modul ke init tahap kedua dengan menempatkannya di vendor atau partisi vendor_dlkm.

Inisialisasi tahap pertama tidak memerlukan pemeriksaan beberapa perangkat untuk sampai ke inisialisasi tahap kedua. Hanya kemampuan konsol dan penyimpanan flash yang diperlukan untuk alur booting normal.

Muat driver penting berikut:

  • watchdog
  • reset
  • cpufreq

Untuk mode fastbootd pemulihan dan ruang pengguna, inisialisasi tahap pertama memerlukan lebih banyak perangkat untuk diselidiki (seperti USB), dan ditampilkan. Simpan salinan modul ini di ramdisk tahap pertama dan di vendor atau partisi vendor_dlkm. Hal ini memungkinkan mereka dimuat dalam init tahap pertama untuk pemulihan atau alur booting fastbootd. Namun, jangan memuat modul mode pemulihan dalam inisialisasi tahap pertama selama alur booting normal. Modul mode pemulihan dapat ditangguhkan ke init tahap kedua untuk mengurangi waktu booting. Semua modul lain yang tidak diperlukan pada init tahap pertama harus dipindahkan ke partisi vendor atau vendor_dlkm.

Dengan mencantumkan daftar perangkat leaf (misalnya, UFS atau serial), dev needs.sh {i>script <i}menemukan semua {i>driver<i}, perangkat, dan modul yang dibutuhkan untuk dependensi atau penyedia (misalnya, jam, regulator, atau gpio) untuk diselidiki.

Memindahkan modul ke init tahap kedua mengurangi waktu booting di cara:

  • Pengurangan ukuran ramdisk.
    • Proses ini menghasilkan pembacaan {i>flash<i} yang lebih cepat ketika {i>bootloader<i} memuat ramdisk (langkah booting serial).
    • Hal ini menghasilkan kecepatan dekompresi yang lebih cepat ketika {i>kernel<i} melakukan dekompresi {i>ramdisk<i} (langkah {i>booting<i} yang diserialisasi).
  • Init tahap kedua berfungsi secara paralel, yang menyembunyikan waktu pemuatan modul dengan pekerjaan yang dilakukan pada init tahap kedua.

Memindahkan modul ke tahap kedua dapat menghemat 500 - 1000 md pada waktu booting, bergantung pada jumlah modul yang dapat Anda pindahkan ke inisialisasi tahap kedua.

Modul memuat logistik

Versi terbaru Android memiliki fitur konfigurasi board yang mengontrol modul akan disalin ke setiap tahap, dan modul mana yang dimuat. Bagian ini berfokus pada subset berikut:

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES. Daftar modul ini akan disalin ke ramdisk.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD. Daftar modul yang akan dimuat pada init tahap pertama.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD. Daftar modul ini untuk dimuat saat pemulihan atau fastbootd dipilih dari ramdisk.
  • BOARD_VENDOR_KERNEL_MODULES. Daftar modul ini akan disalin ke partisi vendor atau vendor_dlkm di direktori /vendor/lib/modules/.
  • BOARD_VENDOR_KERNEL_MODULES_LOAD. Daftar modul yang akan dimuat {i>init<i} tahap kedua.

Modul {i>boot<i} dan pemulihan dalam {i>ramdisk<i} juga harus disalin ke vendor atau Partisi vendor_dlkm pada /vendor/lib/modules. Menyalin modul ini ke partisi vendor memastikan modul tidak terlihat selama init tahap kedua, yang berguna untuk men-debug dan mengumpulkan modinfo untuk laporan bug.

Duplikasi harus menghabiskan ruang minimal di partisi vendor atau vendor_dlkm selama set modul booting diminimalkan. Pastikan bahwa File modules.list memiliki daftar modul yang difilter di /vendor/lib/modules. Daftar yang difilter memastikan waktu booting tidak terpengaruh oleh modul yang dimuat lagi (yang merupakan proses yang mahal).

Pastikan modul mode pemulihan dimuat sebagai grup. Memuat modul mode pemulihan dapat dilakukan dalam mode pemulihan, atau di awal tahap kedua init di setiap alur booting.

Anda dapat menggunakan file Board.Config.mk perangkat untuk melakukan tindakan ini seperti yang terlihat dalam contoh berikut:

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

Contoh ini menampilkan subset BOOT_KERNEL_MODULES dan cara yang lebih mudah dikelola RECOVERY_KERNEL_MODULES akan ditentukan secara lokal dalam konfigurasi board . Skrip sebelumnya menemukan dan mengisi setiap modul subset dari modul {i>kernel<i} yang tersedia, menyisakan modul yang tersedia untuk kedua kalinya init tahap.

Untuk init tahap kedua, sebaiknya jalankan pemuatan modul sebagai layanan agar tidak memblokir alur booting. Gunakan skrip shell untuk mengelola pemuatan modul sehingga logistik lainnya, seperti penanganan dan mitigasi error, atau penyelesaian pemuatan modul, dapat dilaporkan kembali (atau diabaikan) jika diperlukan.

Anda dapat mengabaikan kegagalan pemuatan modul debug yang tidak ada di build pengguna. Untuk mengabaikan kegagalan ini, tetapkan properti vendor.device.modules.ready untuk memicu tahap selanjutnya dari alur booting pembuatan skrip init rc agar dapat melanjutkan ke layar peluncuran. Lihat contoh skrip berikut, jika Anda memiliki kode berikut di /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

Dalam file rc hardware, layanan one shot dapat ditentukan dengan:

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

Pengoptimalan tambahan dapat dilakukan setelah modul berpindah dari tahap pertama ke tahap kedua. Anda dapat menggunakan fitur daftar blokir modprobe untuk memisahkan alur booting tahap kedua guna menyertakan pemuatan modul yang ditangguhkan dari modul yang tidak penting. Pemuatan modul yang digunakan secara eksklusif oleh HAL tertentu dapat ditangguhkan untuk memuat modul hanya ketika HAL dimulai.

Untuk meningkatkan waktu {i>booting<i}, Anda dapat memilih modul secara khusus di layanan pemuatan modul yang lebih kondusif untuk dimuat setelah peluncuran layar. Misalnya, Anda dapat memuat modul secara eksplisit untuk decoder video atau Wi-Fi setelah alur booting init dihapus (sys.boot_complete sinyal properti Android, misalnya). Pastikan HAL untuk modul pemuatan terlambat memblokir cukup lama saat driver kernel tidak ada.

Atau, Anda dapat menggunakan perintah wait<file>[<timeout>] init dalam skrip rc alur booting untuk menunggu entri sysfs tertentu untuk menunjukkan bahwa modul driver telah menyelesaikan operasi probe. Contohnya adalah menunggu driver tampilan menyelesaikan pemuatan di latar belakang pemulihan atau fastbootd, sebelum menampilkan grafik menu.

Melakukan inisialisasi frekuensi CPU ke nilai yang wajar di bootloader

Tidak semua SoC/produk dapat mem-booting CPU dengan frekuensi tertinggi karena masalah termal atau daya selama pengujian {i>boot loop<i}. Namun, pastikan bootloader menetapkan frekuensi semua CPU online setinggi mungkin untuk SoC atau produk. Hal ini sangat penting karena, dengan kernel yang sepenuhnya modular, dekompresi ramdisk init terjadi sebelum driver CPUfreq dapat dimuat. Jadi, jika CPU dibiarkan di bagian bawah frekuensinya oleh bootloader, waktu dekompresi ramdisk dapat memerlukan waktu lebih lama daripada kernel yang dikompilasi secara statis (setelah menyesuaikan perbedaan ukuran ramdisk) karena frekuensi CPU akan sangat rendah saat melakukan pekerjaan intensif CPU (dekompresi). Hal yang sama berlaku untuk frekuensi memori dan interkoneksi.

Menginisialisasi frekuensi CPU CPU berukuran besar di bootloader

Sebelum driver CPUfreq dimuat, kernel tidak mengetahui frekuensi CPU dan tidak menskalakan kapasitas penjadwalan CPU untuk frekuensi saat ini. Kernel dapat memigrasikan thread ke CPU besar jika beban cukup tinggi di CPU kecil.

Pastikan kinerja CPU besar setidaknya sama dengan kinerja CPU kecil untuk frekuensi di mana {i>bootloader<i} meninggalkannya. Misalnya, jika CPU besar 2x berperforma lebih baik dibandingkan CPU kecil untuk frekuensi yang sama, tetapi {i>bootloader<i} mengatur frekuensi CPU kecil menjadi 1,5 GHz dan CPU 300 MHz, maka kinerja {i>booting<i} akan menurun jika {i>kernel<i} memindahkan thread ke CPU besar. Dalam contoh ini, jika aman untuk mem-booting CPU besar pada 750 MHz, Anda harus melakukannya meskipun tidak berencana menggunakannya secara eksplisit.

Driver tidak boleh memuat firmware dalam inisialisasi tahap pertama

Mungkin ada beberapa kasus yang tidak dapat dihindari saat firmware perlu dimuat dalam init tahap pertama. Namun secara umum, {i>driver<i} seharusnya tidak memuat {i>firmware<i} apa pun pada tahap pertama init, terutama dalam konteks pemeriksaan perangkat. Memuat firmware di init tahap pertama menyebabkan seluruh proses booting terhenti jika firmware tidak tersedia di ramdisk tahap pertama. Dan meskipun firmware ada di tahap pertama ramdisk, firmware tersebut masih menyebabkan penundaan yang tidak perlu.