Android 10 包含 Android Live-Lock 守護程序 ( llkd
),旨在捕獲和緩解內核死鎖。 llkd
組件提供默認的獨立實現,但您也可以將llkd
代碼集成到另一個服務中,作為主循環的一部分或作為單獨的線程。
檢測場景
llkd
有兩種檢測場景:持久 D 或 Z 狀態,以及持久堆棧簽名。
持續 D 或 Z 狀態
如果線程處於 D(不間斷睡眠)或 Z(殭屍)狀態且沒有前進的時間超過ro.llk.timeout_ms or ro.llk.[D|Z].timeout_ms
,則llkd
將終止進程(或父進程)。如果隨後的掃描顯示相同的進程繼續存在,則llkd
確認活鎖條件並以提供最詳細錯誤報告的方式使內核恐慌。
llkd
包括一個自我看門狗,當llkd
鎖定時會發出警報;看門狗是流過主循環的預期時間的兩倍,並且採樣是每個ro.llk_sample_ms
。
持久堆棧簽名
對於 userdebug 版本, llkd
可以使用持久堆棧簽名檢查來檢測內核活鎖。如果處於除 Z 之外的任何狀態的線程有一個持續列出的ro.llk.stack
內核符號報告的時間超過ro.llk.timeout_ms
或ro.llk.stack.timeout_ms
,則llkd
將終止該進程(即使存在 forward調度進度)。如果隨後的掃描顯示相同的進程繼續存在,則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
的超時期限內僅在樣本中出現一次(樣本每隔ro.llk.check_ms
出現一次)。由於缺乏 ABA 保護,這是防止誤觸發的唯一方法。符號函數必須出現在調用可以競爭的鎖的函數下方。如果鎖在符號函數下方或符號函數中,則符號出現在所有受影響的進程中,而不僅僅是導致鎖定的進程。
覆蓋範圍
llkd
的默認實現不監視init
、 [kthreadd]
或[kthreadd]
生成。為了讓llkd
覆蓋[kthreadd]
-spawned 線程:
- 驅動程序不得保持在持續的 D 狀態,
要么
- 如果線程被外部終止,驅動程序必須具有恢復線程的機制。例如,使用
wait_event_interruptible()
而不是wait_event()
。
如果滿足上述條件之一,可以調整llkd
黑名單以覆蓋內核組件。堆棧符號檢查涉及一個額外的進程黑名單,以防止阻止ptrace
操作的服務違反 sepolicy。
安卓屬性
llkd
響應幾個 Android 屬性(如下所列)。
- 名為
prop_ms
的屬性以毫秒為單位。 - 對列表使用逗號 (,) 分隔符的屬性使用前導分隔符來保留默認條目,然後分別添加或減去帶有可選加 (+) 和減 (-) 前綴的條目。對於這些列表,字符串“false”是空列表的同義詞,空白或缺失的條目採用指定的默認值。
ro.config.low_ram
設備配置了有限的內存。
ro.debuggable
設備配置為 userdebug 或 eng build。
ro.llk.sysrq_t
如果屬性是“eng”,則默認不是ro.config.low_ram
或ro.debuggable
。如果為真,則轉儲所有線程( sysrq t
)。
ro.llk.enable
允許啟用活鎖守護程序。默認為假。
llk.enable
評估為 eng 構建。默認為ro.llk.enable
。
ro.khungtask.enable
允許啟用[khungtask]
守護程序。默認為假。
khungtask.enable
評估為 eng 構建。默認為ro.khungtask.enable
。
ro.llk.mlockall
啟用對mlockall()
的調用。默認為假。
ro.khungtask.timeout
[khungtask]
最大時間限制。默認值為 12 分鐘。
ro.llk.timeout_ms
D 或 Z 最大時間限制。默認值為 10 分鐘。將該值加倍以設置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 build 上有效。
ro.llk.check_ms
D 或 Z 的線程樣本。默認為兩分鐘。
ro.llk.stack
檢查內核堆棧符號,如果這些符號持續存在,則表明子系統已鎖定。默認為cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable
逗號分隔的內核符號列表。除了在 r ro.llk.stack.timeout_ms
期間輪詢每個ro.llk_check_ms
之外,該檢查不會執行前向調度 ABA,因此堆棧符號應該非常罕見且短暫(符號不太可能持續出現堆棧的樣本)。在堆棧擴展中檢查" symbol+0x"
或" symbol.cfi+0x"
匹配。僅在 userdebug 或 eng build 上可用;用戶構建的安全問題會導致權限受限,從而阻止此檢查。
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]
( kernel
、 [kthreadd]
和adbd
僅適用於殭屍setsid
)。與號 (&) 分隔符指定僅在與目標子進程組合時才忽略父進程。選擇 & 是因為它從來不是進程名稱的一部分;但是,shell 中的setprop
需要轉義或引用 & 符號,儘管通常指定它的init rc
文件沒有這個問題。父進程或目標進程可以是comm
、 cmdline
或pid
引用。
ro.llk.blacklist.uid
llkd
不監視與指定 uid 匹配的進程。以逗號分隔的 uid 編號或名稱列表。默認為空或假。
ro.llk.blacklist.process.stack
llkd
不監視指定的進程子集的活鎖堆棧簽名。默認是進程名稱init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd
。防止與阻止ptrace
的進程相關的 sepolicy 違規(因為這些無法檢查)。僅在 userdebug 和 eng build 上有效。有關構建類型的詳細信息,請參閱構建 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
。該函數返回下一次預期調用此處理程序之前的時間段。