Android 10 には、Android Live-LocK Daemon(llkd
)が搭載されています。これは、カーネルのデッドロックを検出して軽減するためのものです。llkd
コンポーネントは、デフォルトではスタンドアロンで実装されていますが、llkd
のコードを(メインループの一部または別のスレッドとして)他のサービスに統合することもできます。
検出シナリオ
llkd
の検出シナリオには、「永続的 D または Z 状態」、「永続的スタック署名」の 2 つがあります。
永続的 D または Z 状態
スレッドが D(割り込み不可スリープ)または Z(ゾンビ)状態のまま ro.llk.timeout_ms or ro.llk.[D|Z].timeout_ms
より長い時間前進しない場合、llkd
はそのプロセス(または親プロセス)を強制終了します。次のスキャン時にまだそのプロセスが存在している場合、llkd
はライブロック状態を確認し、その状態が最も詳細にバグレポートに出力される方法でカーネル パニックを発生させます。
llkd
には、llkd
がロック状態になった場合にアラームを出すセルフ ウォッチドッグが含まれています。ウォッチドッグ タイマーはメインループ全体の予想処理時間の 2 倍であり、サンプリングは ro.llk_sample_ms
ごとです。
永続的スタック署名
userdebug リリースの場合、llkd
は永続的スタック署名チェックを使用してカーネルのライブロックを検出できます。Z 以外のいずれかの状態のスレッドに、ro.llk.timeout_ms
または ro.llk.stack.timeout_ms
よりも長い間 ro.llk.stack
カーネル シンボルが永続的にリストされている場合、llkd
はそのプロセスを(フォワード スケジューリングが進行している場合でも)強制終了します。次のスキャン時にまだそのプロセスが存在している場合、llkd
はライブロック状態を確認し、その状態が最も詳細にバグレポートに出力される方法でカーネル パニックを発生させます。
lldk
チェックは、ライブロック状態が存在する間は継続して行われ、Linux の /proc/pid/stack
ファイル内の組み合わせ文字列 " symbol+0x"
または " symbol.cfi+0x"
を探します。シンボルのリストは ro.llk.stack
内にあり、デフォルトはカンマ区切りのリスト "cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable
" です。
シンボルが存在することはめったになく、存在期間も短いため、通常のシステムでこの関数が現れるのは、タイムアウト期間 ro.llk.stack.timeout_ms
全体にわたって 1 回のサンプリングで 1 度のみです(サンプリングは ro.llk.check_ms
ごとに行われます)。ABA を回避する機能がないため、これが誤ったトリガーを防ぐ唯一の方法です。シンボル関数は、競合する可能性のあるロックを呼び出す関数の下にある必要があります。ロックがシンボル関数の下または中で起きた場合、シンボルはロックを引き起こしたプロセスだけでなく、影響を受けるすべてのプロセスに現れます。
カバレッジ
llkd
は、デフォルトの実装では init
、[kthreadd]
、[kthreadd]
から生成されたスレッドを監視しません。llkd
が [kthreadd]
から生成されたスレッドをカバーするには、次の条件を満たす必要があります。
- ドライバが永続的 D 状態にとどまらないこと。
または
- ドライバが、外部から強制終了されたスレッドを回復するメカニズムを備えていること(たとえば、
wait_event()
ではなくwait_event_interruptible()
を使用しているなど)。
上記いずれかの条件を満たしていれば、llkd
拒否リストを調整して、カーネル コンポーネントをカバーできます。スタック シンボルのチェックでは、ptrace
オペレーションをブロックするサービスでの sepolicy 違反を防ぐため、別のプロセス拒否リストも考慮されます。
Android プロパティ
llkd
は、以下に挙げる Android プロパティの影響を受けます。
prop_ms
という名前のプロパティの単位はミリ秒です。- リストにカンマ(,)区切り文字を使用するプロパティでは、先頭に区切り文字を置くことでデフォルトのエントリを保持し、オプションで接頭辞のプラス(+)、マイナス(-)を使ってエントリを追加、削除できます。このリストにおいて、文字列 "false" は空のリストと同義です。また、空白または欠落しているエントリには、指定したデフォルト値が使用されます。
ro.config.low_ram
デバイスのメモリが限られています。
ro.debuggable
デバイスが userdebug ビルドまたは eng ビルド用に設定されています。
ro.llk.sysrq_t
プロパティが "eng" の場合、デフォルトは ro.config.low_ram
または ro.debuggable
にはなりません。
true の場合、すべてのスレッドをダンプ(sysrq t
)します。
ro.llk.enable
ライブロック デーモンを有効化できるようにします。デフォルトは false です。
llk.enable
eng ビルドかどうかの検査に使います。デフォルトは ro.llk.enable
です。
ro.khungtask.enable
[khungtask]
デーモンを有効化できるようにします。デフォルトは false です。
khungtask.enable
eng ビルドかどうかの検査に使います。デフォルトは ro.khungtask.enable
です。
ro.llk.mlockall
mlockall()
の呼び出しを有効にします。デフォルトは false です。
ro.khungtask.timeout
[khungtask]
の制限時間の上限。デフォルトは 12 分です。
ro.llk.timeout_ms
D または Z の制限時間の上限。デフォルトは 10 分です。この値を 2 倍にして、llkd
のアラーム ウォッチドッグを設定します。
ro.llk.D.timeout_ms
D の制限時間の上限。デフォルトは ro.llk.timeout_ms
です。
ro.llk.Z.timeout_ms
Z の制限時間の上限。デフォルトは ro.llk.timeout_ms
です。
ro.llk.stack.timeout_ms
永続的スタック シンボルの制限時間の上限をチェックします。デフォルトは ro.llk.timeout_ms
です。userdebug ビルドまたは eng ビルドでのみ有効です。
ro.llk.check_ms
D または Z のスレッドのサンプリング周期。デフォルトは 2 分です。
ro.llk.stack
このカーネル スタック シンボル(永続的に存在する場合、サブシステムがロックされていることを示す)についてチェックします。デフォルトは、カーネル シンボルのカンマ区切りのリスト(cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable
)です。このチェックでは、ro.llk.stack.timeout_ms
の期間にわたる ro.llk_check_ms
ごとのポーリングを除いて、フォワード スケジューリング ABA を行いません。そのため、スタック シンボルが現れるのは非常にまれであり、存在するとしても短時間です(スタックのすべてのサンプルにおいて、シンボルが永続的に存在することはほとんどありません)。スタックの拡張範囲で " symbol+0x"
または " symbol.cfi+0x"
との一致があるかどうかをチェックします。userdebug ビルドまたは eng ビルドでのみ使用できます。user ビルドでは、セキュリティ上の問題により権限が制限されるため、このチェックを行えません。
ro.llk.blacklist.process
llkd
は、ここに指定したプロセスを監視しません。デフォルトは、0,1,2
(kernel
、init
、[kthreadd]
)に加えて、プロセス名 init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd, [watchdogd],[watchdogd/0],...,[watchdogd/get_nprocs-1]
です。
プロセスは、comm
、cmdline
、pid
参照にできます。自動設定されたデフォルトは、現在の最大プロパティ サイズである 92 より大きくなる可能性があります。
ro.llk.blacklist.parent
llkd
は、ここに指定した親を持つプロセスを監視しません。デフォルトは、0,2,adbd&[setsid]
(ゾンビ setsid
の場合のみ kernel
、[kthreadd]
、adbd
)です。アンパサンド(&)区切り文字は、ターゲットの子プロセスとの組み合わせでのみ親を無視する場合に使用します。アンパサンドを使用するのは、プロセス名の一部に使用されることがないためです。シェルの setprop
では、アンパサンドはエスケープするか引用符で囲む必要がありますが、通常これが指定されている init rc
ファイルでは問題は生じません。親プロセスまたはターゲット プロセスは、comm
、cmdline
、pid
参照にできます。
ro.llk.blacklist.uid
llkd
は、ここに指定した uid に一致するプロセスを監視しません。
uid の番号または名前のカンマ区切りリストです。デフォルトは空または false です。
ro.llk.blacklist.process.stack
llkd
は、ここに指定したプロセスのサブセットに対して、ライブロック スタック署名を監視しません。デフォルトは、プロセス名 init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd
です。ptrace
をブロックするプロセスに関連する sepolicy 違反を防止します(これらはチェックできないため)。userdebug ビルドと eng ビルドでのみ有効です。ビルドタイプの詳細については、Android のビルドをご覧ください。
アーキテクチャ上の留意事項
- プロパティの文字数の上限は 92 です(ただし、ソースの
include/llkd.h
ファイルで定義されるデフォルトでは無視されます)。 - 組み込みの
[khungtask]
デーモンは使用頻度が高く、D 状態に置かれたドライバコードで処理される頻度も高くなります。S に切り替えることで、タスクが強制終了可能になります(必要に応じて、ドライバにより復元可能です)。
ライブラリ インターフェース(任意)
必要に応じて、以下の libllkd
コンポーネントの C インターフェースを使用して、llkd
を別の特権デーモンに組み込むことができます。
#include "llkd.h"
bool llkInit(const char* threadname) /* return true if enabled */
unsigned llkCheckMillseconds(void) /* ms to sleep for next check */
スレッド名を指定すると、スレッドが自動的に生成されます。それ以外の場合、呼び出し元はメインループで llkCheckMilliseconds
を呼び出す必要があります。この関数は、このハンドラが次に呼び出されるまでの予想時間を返します。