O Android 10 inclui o Android Live-Lock Daemon ( llkd
), projetado para detectar e mitigar deadlocks do kernel. O componente llkd
fornece uma implementação autônoma padrão, mas você pode alternativamente integrar o código llkd
em outro serviço, como parte do loop principal ou como um thread separado.
Cenários de detecção
O llkd
possui dois cenários de detecção: estado D ou Z persistente e assinatura de pilha persistente.
Estado D ou Z persistente
Se um thread estiver no estado D (suspensão ininterrupta) ou Z (zumbi) sem progresso de avanço por mais de ro.llk.timeout_ms or ro.llk.[D|Z].timeout_ms
, o llkd
mata o processo (ou processo pai ). Se uma varredura subsequente mostrar que o mesmo processo continua existindo, o llkd
confirma uma condição de live-lock e coloca o kernel em pânico de uma maneira que fornece o relatório de bug mais detalhado para a condição.
O llkd
inclui um self watchdog que emite um alarme se o llkd
travar; watchdog é o dobro do tempo esperado para fluir através do mainloop e a amostragem é a cada ro.llk_sample_ms
.
Assinatura de pilha persistente
Para versões userdebug, o llkd
pode detectar live-locks do kernel usando verificação persistente de assinatura de pilha. Se um thread em qualquer estado, exceto Z, tiver um símbolo de kernel ro.llk.stack
listado persistentemente que é relatado por mais tempo que ro.llk.timeout_ms
ou ro.llk.stack.timeout_ms
, o llkd
mata o processo (mesmo se houver encaminhamento andamento do agendamento). Se uma varredura subsequente mostrar que o mesmo processo continua existindo, o llkd
confirma uma condição de live-lock e coloca o kernel em pânico de uma maneira que fornece o relatório de bug mais detalhado para a condição.
A verificação lldk
persiste continuamente quando a condição de bloqueio ativo existe e procura as strings compostas " symbol+0x"
ou " symbol.cfi+0x"
no arquivo /proc/pid/stack
no Linux. A lista de símbolos está em ro.llk.stack
e o padrão é a lista separada por vírgula de " cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable
".
Os símbolos devem ser raros e de curta duração o suficiente para que em um sistema típico a função seja vista apenas uma vez em uma amostra durante o período de tempo limite de ro.llk.stack.timeout_ms
(as amostras ocorrem a cada ro.llk.check_ms
). Devido à falta de proteção ABA, esta é a única maneira de evitar um falso disparo. O símbolo da função deve aparecer abaixo da função que chama o bloqueio que pode competir. Se o bloqueio estiver abaixo ou na função de símbolo, o símbolo aparecerá em todos os processos afetados, não apenas naquele que causou o bloqueio.
Cobertura
A implementação padrão de llkd
não monitora init
, [kthreadd]
ou [kthreadd]
spawns. Para que o llkd
cubra os threads gerados por [kthreadd]
:
- Os condutores não devem permanecer num estado D persistente,
OU
- Os drivers devem ter mecanismos para recuperar o thread caso ele seja eliminado externamente. Por exemplo, use
wait_event_interruptible()
em vez dewait_event()
.
Se uma das condições acima for atendida, a lista negra llkd
poderá ser ajustada para cobrir os componentes do kernel. A verificação de símbolos de pilha envolve uma lista negra de processos adicional para evitar violações de sepolicy em serviços que bloqueiam operações ptrace
.
Propriedades do Android
O llkd
responde a diversas propriedades do Android (listadas abaixo).
- As propriedades denominadas
prop_ms
estão em milissegundos. - As propriedades que usam separador de vírgula (,) para listas usam um separador à esquerda para preservar a entrada padrão e, em seguida, adicionam ou subtraem entradas com prefixos opcionais de mais (+) e menos (-), respectivamente. Para essas listas, a string "false" é sinônimo de uma lista vazia e as entradas em branco ou ausentes recorrem ao valor padrão especificado.
ro.config.low_ram
O dispositivo está configurado com memória limitada.
ro.depurável
O dispositivo está configurado para userdebug ou eng build.
ro.llk.sysrq_t
Se a propriedade for "eng", o padrão não será ro.config.low_ram
ou ro.debuggable
. Se for verdade, despeje todos os threads ( sysrq t
).
ro.llk.enable
Permitir que o daemon de bloqueio ao vivo seja ativado. O padrão é falso.
llk.enable
Avaliado para compilações de engenharia. O padrão é ro.llk.enable
.
ro.khungtask.enable
Permitir que o daemon [khungtask]
seja ativado. O padrão é falso.
khungtask.enable
Avaliado para compilações de engenharia. O padrão é ro.khungtask.enable
.
ro.llk.mlockall
Habilite a chamada para mlockall()
. O padrão é falso.
ro.khungtask.timeout
[khungtask]
limite de tempo máximo. O padrão é 12 minutos.
ro.llk.timeout_ms
Limite de tempo máximo D ou Z. O padrão é 10 minutos. Duplique esse valor para definir o watchdog de alarme para llkd
.
ro.llk.D.timeout_ms
D prazo máximo. O padrão é ro.llk.timeout_ms
.
ro.llk.Z.timeout_ms
Prazo máximo Z. O padrão é ro.llk.timeout_ms
.
ro.llk.stack.timeout_ms
Verifica o limite de tempo máximo dos símbolos da pilha persistente. O padrão é ro.llk.timeout_ms
. Ativo apenas em compilações userdebug ou eng .
ro.llk.check_ms
Amostras de threads para D ou Z. O padrão é dois minutos.
ro.llk.stack
Verifica se há símbolos de pilha do kernel que, se presentes persistentemente, podem indicar que um subsistema está bloqueado. O padrão é cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable
lista separada por vírgula de símbolos do kernel. A verificação não faz agendamento antecipado de ABA, exceto pesquisando cada ro.llk_check_ms
durante o período ro.llk.stack.timeout_ms
, portanto, os símbolos da pilha devem ser excepcionalmente raros e fugazes (é altamente improvável que um símbolo apareça persistentemente em todos amostras da pilha). Verifica se há uma correspondência para " symbol+0x"
ou " symbol.cfi+0x"
na expansão da pilha. Disponível apenas em compilações userdebug ou eng ; preocupações de segurança em compilações de usuários resultam em privilégios limitados que impedem essa verificação.
ro.llk.lista negra.process
O llkd
não monitora os processos especificados. O padrão é 0,1,2
( kernel
, init
e [kthreadd]
) mais nomes de processos init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd, [watchdogd],[watchdogd/0],...,[watchdogd/get_nprocs-1]
. Um processo pode ser uma referência comm
, cmdline
ou pid
. Um padrão automatizado pode ser maior que o tamanho máximo atual da propriedade de 92.
ro.llk.lista negra.parent
O llkd
não monitora processos que possuem o(s) pai(s) especificado(s). O padrão é 0,2,adbd&[setsid]
( kernel
, [kthreadd]
e adbd
apenas para zumbi setsid
). Um separador de E comercial (&) especifica que o pai é ignorado apenas em combinação com o processo filho de destino. O E comercial foi selecionado porque nunca faz parte de um nome de processo; no entanto, um setprop
no shell requer que o E comercial seja escapado ou entre aspas, embora o arquivo init rc
onde isso é normalmente especificado não tenha esse problema. Um processo pai ou de destino pode ser uma referência comm
, cmdline
ou pid
.
ro.llk.blacklist.uid
O llkd
não monitora processos que correspondam aos uid(s) especificados. Lista separada por vírgulas de números ou nomes uid. O padrão é vazio ou falso.
ro.llk.blacklist.process.stack
O llkd
não monitora o subconjunto especificado de processos para assinaturas de pilha de bloqueios ativos. O padrão são nomes de processos init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd
. Impede a violação da sepolicy associada a processos que bloqueiam ptrace
(já que estes não podem ser verificados). Ativo apenas em compilações userdebug e eng . Para obter detalhes sobre os tipos de build, consulte Construindo o Android .
Preocupações arquitetônicas
- As propriedades são limitadas a 92 caracteres (no entanto, isso é ignorado para padrões definidos no arquivo
include/llkd.h
nas fontes). - O daemon integrado
[khungtask]
é muito genérico e tropeça muito no código do driver que fica no estado D. Mudar para S tornaria as tarefas elimináveis (e ressuscitáveis pelos drivers, se necessário).
Interface da biblioteca (opcional)
Opcionalmente, você pode incorporar o llkd
em outro daemon privilegiado usando a seguinte interface C do componente libllkd
:
#include "llkd.h"
bool llkInit(const char* threadname) /* return true if enabled */
unsigned llkCheckMillseconds(void) /* ms to sleep for next check */
Se um nome de thread for fornecido, um thread será gerado automaticamente, caso contrário, o chamador deverá chamar llkCheckMilliseconds
em seu loop principal. A função retorna o período de tempo antes da próxima chamada esperada para esse manipulador.