起動時間の最適化

このページでは、起動時間を改善するために選択できる一連のヒントを提供します。

モジュールからデバッグシンボルを削除します

本番デバイスのカーネルからデバッグシンボルを削除する方法と同様に、モジュールからデバッグシンボルも削除するようにしてください。モジュールからデバッグシンボルを取り除くと、以下が削減され、起動時間が短縮されます。

  • フラッシュからバイナリを読み取るのにかかる時間。
  • RAMディスクの解凍にかかる時間。
  • モジュールのロードにかかる時間。

モジュールからデバッグシンボルを削除すると、起動中に数秒節約できる場合があります。

シンボルストリッピングはAndroidプラットフォームビルドでデフォルトで有効になっていますが、明示的に有効にするには、デバイス/ vendor / deviceの下のデバイス固有の構成でBOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULESを設定します。

カーネルとRAMディスクにLZ4圧縮を使用する

GzipはLZ4と比較して小さい圧縮出力を生成しますが、LZ4はGzipよりも速く解凍します。カーネルとモジュールの場合、Gzipを使用することによる絶対的なストレージサイズの削減は、LZ4の解凍時間の利点と比較してそれほど重要ではありません。

LZ4 RAMディスク圧縮のサポートが、 BOARD_RAMDISK_USE_LZ4を介してAndroidプラットフォームビルドに追加されました。このオプションは、デバイス固有の構成で設定できます。カーネル圧縮は、kerneldefconfigを介して設定できます。

LZ4に切り替えると、起動時間が500ミリ秒から1000ミリ秒速くなります。

ドライバーへの過度のログインは避けてください

ARM64およびARM32では、呼び出しサイトから特定の距離を超える関数呼び出しでは、完全なジャンプアドレスをエンコードできるように、ジャンプテーブル(プロシージャリンクテーブルまたはPLTと呼ばれる)が必要です。モジュールは動的にロードされるため、これらのジャンプテーブルはモジュールのロード中に修正する必要があります。再配置が必要な呼び出しは、ELF形式の明示的な加数(または略してRELA)エントリを持つ再配置エントリと呼ばれます。

Linuxカーネルは、PLTを割り当てるときに、メモリサイズの最適化(キャッシュヒットの最適化など)を実行します。このアップストリームコミットでは、最適化スキームの複雑さはO(N ^ 2)になります。ここで、NはタイプR_AARCH64_JUMP26またはR_AARCH64_CALL26のRELAの数です。したがって、これらのタイプのRELAを少なくすると、モジュールのロード時間を短縮するのに役立ちます。

R_AARCH64_CALL26またはR_AARCH64_JUMP26の数を増やす一般的なコーディングパターンの1つは、ドライバーへの過剰なロギングです。 printk()またはその他のロギングスキームを呼び出すたびに、通常、 CALL26 / JUMP26エントリが追加されます。アップストリームコミットのコミットテキストでは、最適化を行っても、6つのモジュールのロードに約250ミリ秒かかることに注意してください。これは、これらの6つのモジュールがログの量が最も多い上位6つのモジュールであったためです。

ロギングを減らすと、既存のロギングがどれだけ過剰であるかに応じて、起動時間を約100〜300ミリ秒節約できます。

非同期プロービングを選択的に有効にする

モジュールがロードされたときに、サポートするデバイスがすでにDT(devicetree)から入力され、ドライバーコアに追加されている場合、デバイスプローブはmodule_init()呼び出しのコンテキストで実行されます。 module_init()のコンテキストでデバイスプローブが実行されると、プローブが完了するまでモジュールはロードを完了できません。モジュールのロードはほとんどシリアル化されているため、プローブに比較的長い時間がかかるデバイスは起動時間を遅くします。

起動時間が遅くなるのを防ぐには、デバイスのプローブに時間がかかるモジュールの非同期プローブを有効にします。すべてのモジュールで非同期プローブを有効にすると、スレッドをフォークしてプローブを開始するのにかかる時間がデバイスのプローブにかかる時間と同じくらい長くなる可能性があるため、有益ではない場合があります。

I2Cなどの低速バスを介して接続されているデバイス、プローブ機能でファームウェアのロードを実行するデバイス、および多くのハードウェア初期化を実行するデバイスは、タイミングの問題を引き起こす可能性があります。これがいつ発生するかを特定する最良の方法は、すべてのドライバーのプローブ時間を収集してソートすることです。

モジュールの非同期プローブを有効にするには、ドライバーコードでPROBE_PREFER_ASYNCHRONOUSフラグを設定するだけで不十分です。モジュールの場合は、カーネルコマンドラインにmodule_name .async_probe=1を追加するか、 modprobeまたはinsmodを使用してモジュールをロードするときにモジュールパラメーターとしてasync_probe=1を渡す必要もあります。

非同期プロービングを有効にすると、ハードウェア/ドライバーに応じて、起動時間を約100〜500ミリ秒節約できます。

CPUfreqドライバーをできるだけ早くプローブします

CPUfreqドライバーのプローブが早いほど、起動時にCPU周波数を最大(または熱的に制限された最大値)にスケーリングするのが早くなります。 CPUが高速であるほど、起動も高速になります。このガイドラインは、DRAM、メモリ、および相互接続周波数を制御するdevfreqドライバーにも適用されます。

モジュールの場合、ロードの順序は、 initcallレベル、およびドライバーのコンパイルまたはリンクの順序に依存する可能性があります。エイリアスMODULE_SOFTDEP()を使用して、 cpufreqドライバーがロードする最初の数個のモジュールの中にあることを確認します。

モジュールを早期にロードする以外に、CPUfreqドライバーをプローブするためのすべての依存関係もプローブしていることを確認する必要があります。たとえば、CPUの周波数を制御するためにクロックまたはレギュレーターのハンドルが必要な場合は、それらが最初にプローブされていることを確認してください。または、起動時にCPUが熱くなりすぎる可能性がある場合は、CPUfreqドライバーの前にサーマルドライバーをロードする必要があります。したがって、CPUfreqおよび関連するdevfreqドライバーができるだけ早くプローブするようにできることを実行してください。

CPUfreqドライバーを早期にプローブすることによる節約は、これらをプローブする速度と、ブートローダーがCPUを残す頻度に応じて、非常に小さいものから非常に大きいものになります。

モジュールを第2段階のinit、vendor、またはvendor_dlkmパーティションに移動します

第1段階の初期化プロセスはシリアル化されているため、ブートプロセスを並列化する機会は多くありません。モジュールを第1段階の初期化に終了する必要がない場合は、モジュールをベンダーまたはvendor_dlkmパーティションに配置して、モジュールを第2段階の初期化に移動します。

第1段階の初期化では、第2段階の初期化に到達するために複数のデバイスをプローブする必要はありません。通常のブートフローには、コンソールとフラッシュストレージ機能のみが必要です。

次の重要なドライバーをロードします。

  • 番犬
  • リセット
  • cpufreq

リカバリおよびユーザースペースのfastbootdモードの場合、第1段階のinitでは、プローブして表示するデバイス(USBなど)を増やす必要があります。これらのモジュールのコピーを第1段階のramdiskとvendorまたはvendor_dlkmパーティションに保存します。これにより、リカバリまたはfastbootdブートフローのために、それらを第1段階のinitにロードできます。ただし、通常のブートフローでは、最初のステージの初期化でリカバリモードモジュールをロードしないでください。リカバリモードモジュールは、起動時間を短縮するために第2段階のinitに延期することができます。第1段階のinitで不要な他のすべてのモジュールは、vendorまたはvendor_dlkmパーティションに移動する必要があります。

リーフデバイス(たとえば、UFSまたはシリアル)のリストを指定すると、 dev needs.shスクリプトは、依存関係またはサプライヤー(たとえば、クロック、レギュレーター、またはgpio )がプローブするために必要なすべてのドライバー、デバイス、およびモジュールを検索します。

モジュールを第2ステージの初期化に移動すると、次の方法で起動時間が短縮されます。

  • RAMディスクのサイズを縮小します。
    • これにより、ブートローダーがRAMディスクをロードするときのフラッシュ読み取りが高速になります(シリアル化されたブートステップ)。
    • これにより、カーネルがRAMディスクを解凍するときの解凍速度が速くなります(シリアル化されたブートステップ)。
  • 第2段階の初期化は並行して機能します。これにより、第2段階の初期化で行われる作業でモジュールのロード時間が非表示になります。

モジュールを第2ステージに移動すると、第2ステージの初期化に移動できるモジュールの数に応じて、起動時間を500〜1000ミリ秒節約できます。

モジュールローディングロジスティクス

最新のAndroidビルドは、どのモジュールが各ステージにコピーされ、どのモジュールがロードされるかを制御するボード構成を備えています。このセクションでは、次のサブセットに焦点を当てます。

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES 。 ramdiskにコピーされるモジュールのこのリスト。
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD 。この第1段階の初期化でロードされるモジュールのリスト。
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD 。リカバリまたはfastbootdがramdiskから選択されたときにロードされるモジュールのこのリスト。
  • BOARD_VENDOR_KERNEL_MODULES/vendor/lib/modules/ディレクトリのvendorまたはvendor_dlkmパーティションにコピーされるモジュールのこのリスト。
  • BOARD_VENDOR_KERNEL_MODULES_LOAD 。この第2段階のinitでロードされるモジュールのリスト。

ramdiskのブートおよびリカバリモジュールも、 /vendor/lib/modulesのvendorまたはvendor_dlkmパーティションにコピーする必要があります。これらのモジュールをベンダーパーティションにコピーすると、第2段階の初期化中にモジュールが非表示にならないようになります。これは、バグレポートのmodinfoのデバッグと収集に役立ちます。

ブートモジュールセットが最小化されている限り、複製のコストはベンダーまたはvendor_dlkmパーティションで最小限に抑えられます。ベンダーのmodules.listファイルの/vendor/lib/modulesにモジュールのフィルターされたリストがあることを確認してください。フィルタリングされたリストにより、起動時間がモジュールの再ロードの影響を受けないことが保証されます(これはコストのかかるプロセスです)。

リカバリモードモジュールがグループとしてロードされることを確認します。リカバリモードモジュールのロードは、リカバリモードで実行することも、各ブートフローの第2段階の初期化の開始時に実行することもできます。

次の例に示すように、デバイスのBoard.Config.mkファイルを使用してこれらのアクションを実行できます。

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

この例は、ボード構成ファイルでローカルに指定されるBOOT_KERNEL_MODULESRECOVERY_KERNEL_MODULESの管理しやすいサブセットを示しています。上記のスクリプトは、選択された使用可能なカーネルモジュールから各サブセットモジュールを検索して入力し、第2段階の初期化のためにreaminingモジュールを残します。

第2段階の初期化では、モジュールのロードをサービスとして実行して、ブートフローがブロックされないようにすることをお勧めします。シェルスクリプトを使用してモジュールの読み込みを管理し、エラー処理や軽減、モジュールの読み込みの完了などの他のロジスティクスを必要に応じて報告(または無視)できるようにします。

ユーザービルドには存在しないデバッグモジュールのロードエラーは無視できます。この失敗を無視するには、 vendor.device.modules.readyプロパティを設定して、 init rcスクリプティングブートフローの後の段階をトリガーし、起動画面に進みます。 /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

ハードウェアrcファイルでは、 one shotサービスは次のように指定できます。

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

モジュールが第1段階から第2段階に移行した後、追加の最適化を行うことができます。 modprobeブロックリスト機能を使用して、第2ステージのブートフローを分割し、不要なモジュールの遅延モジュールロードを含めることができます。特定のHALによって排他的に使用されるモジュールのロードは、HALの開始時にのみモジュールをロードするように延期できます。

見かけの起動時間を改善するために、起動画面後の読み込みに適したモジュール読み込みサービスのモジュールを具体的に選択できます。たとえば、initブートフローがクリアされた後、ビデオデコーダーまたはwifiのモジュールを明示的にレイトロードできます(たとえば、 sys.boot_complete Androidプロパティシグナル)。カーネルドライバーが存在しない場合は、レイトローディングモジュールのHALが十分に長くブロックされることを確認してください。

または、ブートフローrcスクリプトでinitのwait<file>[<timeout>]コマンドを使用して、選択したsysfsエントリを待機し、ドライバーモジュールがプローブ操作を完了したことを示すことができます。この例は、メニューグラフィックを表示する前に、ディスプレイドライバがリカバリまたはfastbootdのバックグラウンドでロードを完了するのを待っています。

ブートローダーでCPU周波数を適切な値に初期化します

ブートループテスト中の熱または電力の問題により、すべてのSoC/製品がCPUを最高周波数でブートできるとは限りません。ただし、ブートローダーがすべてのオンラインCPUの周波数をSoC/製品で安全に可能な限り高く設定していることを確認してください。完全にモジュール化されたカーネルでは、CPUfreqドライバーをロードする前に、init ramdiskの解凍が行われるため、これは非常に重要です。したがって、CPUがブートローダーによってその周波数の下限に残されている場合、CPUを集中的に実行するとCPU周波数が非常に低くなるため、ramdiskの解凍時間は静的にコンパイルされたカーネルよりも長くかかる可能性があります(ramdiskのサイズの違いを調整した後)仕事(減圧)。同じことがメモリ/相互接続周波数にも当てはまります。

ブートローダーで大きなCPUのCPU周波数を初期化します

CPUfreqドライバーがロードされる前に、カーネルはCPU周波数の大小を認識せず、CPUのスケジュールされた容量を現在の周波数に合わせてスケーリングしません。小さなCPUの負荷が十分に高い場合、カーネルはスレッドを大きなCPUに移行する可能性があります。

大きなCPUが、ブートローダーがそれらを残す頻度で小さなCPUと少なくとも同じくらいのパフォーマンスを発揮することを確認します。たとえば、大きなCPUが同じ周波数で小さなCPUの2倍のパフォーマンスを発揮するが、ブートローダーが小さなCPUの周波数を1.5GHzに、大きなCPUの周波数を300 MHzにすると、カーネルがスレッドを大きなCPUに移動すると、起動パフォーマンスが低下します。この例では、大きなCPUを750 MHzで安全に起動できる場合は、明示的に使用する予定がなくても、そうする必要があります。

ドライバーは、最初の段階の初期化でファームウェアをロードしないでください

ファームウェアを第1段階の初期化でロードする必要がある場合が避けられない場合があります。ただし、一般的に、ドライバーは、特にデバイスプローブのコンテキストでは、第1段階の初期化でファームウェアをロードしないでください。ファームウェアを第1ステージの初期化でロードすると、ファームウェアが第1ステージのRAMディスクで使用できない場合、ブートプロセス全体が停止します。また、ファームウェアが第1ステージのRAMディスクに存在する場合でも、不必要な遅延が発生します。