Warning
In caso di dubbi sulla correttezza del contenuto di questa traduzione, l’unico riferimento valido è la documentazione ufficiale in inglese. Per maggiori informazioni consultate le avvertenze.
Le operazioni RCU per le verifiche torture¶
CONFIG_RCU_TORTURE_TEST¶
L’opzione CONFIG_RCU_TORTURE_TEST è disponibile per tutte le implementazione di
RCU. L’opzione creerà un modulo rcutorture che potrete caricare per avviare le
verifiche. La verifica userà printk()
per riportare lo stato, dunque potrete
visualizzarlo con dmesg (magari usate grep per filtrare “torture”). Le verifiche
inizieranno al caricamento, e si fermeranno alla sua rimozione.
I parametri di modulo hanno tutti il prefisso “rcutortute.”, vedere The kernel’s command-line parameters.
Rapporto¶
Il rapporto sulle verifiche si presenta nel seguente modo:
rcu-torture:--- Start of test: nreaders=16 nfakewriters=4 stat_interval=30 verbose=0 test_no_idle_hz=1 shuffle_interval=3 stutter=5 irqreader=1 fqs_duration=0 fqs_holdoff=0 fqs_stutter=3 test_boost=1/0 test_boost_interval=7 test_boost_duration=4
rcu-torture: rtc: (null) ver: 155441 tfle: 0 rta: 155441 rtaf: 8884 rtf: 155440 rtmbe: 0 rtbe: 0 rtbke: 0 rtbre: 0 rtbf: 0 rtb: 0 nt: 3055767
rcu-torture: Reader Pipe: 727860534 34213 0 0 0 0 0 0 0 0 0
rcu-torture: Reader Batch: 727877838 17003 0 0 0 0 0 0 0 0 0
rcu-torture: Free-Block Circulation: 155440 155440 155440 155440 155440 155440 155440 155440 155440 155440 0
rcu-torture:--- End of test: SUCCESS: nreaders=16 nfakewriters=4 stat_interval=30 verbose=0 test_no_idle_hz=1 shuffle_interval=3 stutter=5 irqreader=1 fqs_duration=0 fqs_holdoff=0 fqs_stutter=3 test_boost=1/0 test_boost_interval=7 test_boost_duration=4
Sulla maggior parte dei sistemi questo rapporto si produce col comando “dmesg |
grep torture:”. Su configurazioni più esoteriche potrebbe essere necessario
usare altri comandi per visualizzare i messaggi di printk()
. La funzione
printk()
usa KERN_ALERT, dunque i messaggi dovrebbero essere ben visibili. ;-)
La prima e l’ultima riga mostrano i parametri di module di rcutorture, e solo sull’ultima riga abbiamo il risultato finale delle verifiche effettuate che può essere “SUCCESS” (successo) or “FAILURE” (insuccesso).
Le voci sono le seguenti:
- “rtc”: L’indirizzo in esadecimale della struttura attualmente visibile dai
lettori.
“ver”: Il numero di volte dall’avvio che il processo scrittore di RCU ha cambiato la struttura visible ai lettori.
“tfle”: se non è zero, indica la lista di strutture “torture freelist” da mettere in “rtc” è vuota. Questa condizione è importante perché potrebbe illuderti che RCU stia funzionando mentre invece non è il caso. :-/
“rta”: numero di strutture allocate dalla lista “torture freelist”.
“rtaf”: il numero di allocazioni fallite dalla lista “torture freelist” a causa del fatto che fosse vuota. Non è inusuale che sia diverso da zero, ma è un brutto segno se questo numero rappresenta una frazione troppo alta di “rta”.
“rtf”: il numero di rilasci nella lista “torture freelist”
“rtmbe”: Un valore diverso da zero indica che rcutorture crede che
rcu_assign_pointer()
ercu_dereference()
non funzionino correttamente. Il valore dovrebbe essere zero.“rtbe”: un valore diverso da zero indica che le funzioni della famiglia
rcu_barrier()
non funzionano correttamente.“rtbke”: rcutorture è stato capace di creare dei kthread real-time per forzare l’inversione di priorità di RCU. Il valore dovrebbe essere zero.
“rtbre”: sebbene rcutorture sia riuscito a creare dei kthread capaci di forzare l’inversione di priorità, non è riuscito però ad impostarne la priorità real-time al livello 1. Il valore dovrebbe essere zero.
“rtbf”: Il numero di volte che è fallita la promozione della priorità per risolvere un’inversione.
“rtb”: Il numero di volte che rcutorture ha provato a forzare l’inversione di priorità. Il valore dovrebbe essere diverso da zero Se state verificando la promozione della priorità col parametro “test_bootst”.
“nt”: il numero di volte che rcutorture ha eseguito codice lato lettura all’interno di un gestore di timer. Questo valore dovrebbe essere diverso da zero se avete specificato il parametro “irqreader”.
“Reader Pipe”: un istogramma dell’età delle strutture viste dai lettori. RCU non funziona correttamente se una qualunque voce, dalla terza in poi, ha un valore diverso da zero. Se dovesse succedere, rcutorture stampa la stringa “!!!” per renderlo ben visibile. L’età di una struttura appena creata è zero, diventerà uno quando sparisce dalla visibilità di un lettore, e incrementata successivamente per ogni periodo di grazia; infine rilasciata dopo essere passata per (RCU_TORTURE_PIPE_LEN-2) periodi di grazia.
L’istantanea qui sopra è stata presa da una corretta implementazione di RCU. Se volete vedere come appare quando non funziona, sbizzarritevi nel romperla. ;-)
“Reader Batch”: un istogramma di età di strutture viste dai lettori, ma conteggiata in termini di lotti piuttosto che periodi. Anche qui dalla terza voce in poi devono essere zero. La ragione d’esistere di questo rapporto è che a volte è più facile scatenare un terzo valore diverso da zero qui piuttosto che nella lista “Reader Pipe”.
“Free-Block Circulation”: il numero di strutture torture che hanno raggiunto un certo punto nella catena. Il primo numero dovrebbe corrispondere strettamente al numero di strutture allocate; il secondo conta quelle rimosse dalla vista dei lettori. Ad eccezione dell’ultimo valore, gli altri corrispondono al numero di passaggi attraverso il periodo di grazia. L’ultimo valore dovrebbe essere zero, perché viene incrementato solo se il contatore della struttura torture viene in un qualche modo incrementato oltre il normale.
Una diversa implementazione di RCU potrebbe fornire informazioni aggiuntive. Per esempio, Tree SRCU fornisce anche la seguente riga:
srcud-torture: Tree SRCU per-CPU(idx=0): 0(35,-21) 1(-4,24) 2(1,1) 3(-26,20) 4(28,-47) 5(-9,4) 6(-10,14) 7(-14,11) T(1,6)
Questa riga mostra lo stato dei contatori per processore, in questo caso per Tree SRCU, usando un’allocazione dinamica di srcu_struct (dunque “srcud-” piuttosto che “srcu-“). I numeri fra parentesi sono i valori del “vecchio” contatore e di quello “corrente” per ogni processore. Il valore “idx” mappa questi due valori nell’array, ed è utile per il debug. La “T” finale contiene il valore totale dei contatori.
Uso su specifici kernel¶
A volte può essere utile eseguire RCU torture su un kernel già compilato, ad esempio quando lo si sta per mettere in proeduzione. In questo caso, il kernel dev’essere compilato con CONFIG_RCU_TORTURE_TEST=m, cosicché le verifiche possano essere avviate usano modprobe e terminate con rmmod.
Per esempio, potreste usare questo script:
#!/bin/sh
modprobe rcutorture
sleep 3600
rmmod rcutorture
dmesg | grep torture:
Potete controllare il rapporto verificando manualmente la presenza del marcatore di errore “!!!”. Ovviamente, siete liberi di scriverne uno più elaborato che identifichi automaticamente gli errori. Il comando “rmmod” forza la stampa di “SUCCESS” (successo), “FAILURE” (fallimento), o “RCU_HOTPLUG”. I primi due sono autoesplicativi; invece, l’ultimo indica che non son stati trovati problemi in RCU, tuttavia ci sono stati problemi con CPU-hotplug.
Uso sul kernel di riferimento¶
Quando si usa rcutorture per verificare modifiche ad RCU stesso, spesso è necessario compilare un certo numero di kernel usando configurazioni diverse e con parametri d’avvio diversi. In questi casi, usare modprobe ed rmmod potrebbe richiedere molto tempo ed il processo essere suscettibile ad errori.
Dunque, viene messo a disposizione il programma tools/testing/selftests/rcutorture/bin/kvm.sh per le architetture x86, arm64 e powerpc. Di base, eseguirà la serie di verifiche elencate in tools/testing/selftests/rcutorture/configs/rcu/CFLIST. Ognuna di queste verrà eseguita per 30 minuti in una macchina virtuale con uno spazio utente minimale fornito da un initrd generato automaticamente. Al completamento, gli artefatti prodotti e i messaggi vengono analizzati alla ricerca di errori, ed i risultati delle esecuzioni riassunti in un rapporto.
Su grandi sistemi, le verifiche di rcutorture posso essere velocizzare passano a kvm.sh l’argomento --cpus. Per esempio, su un sistema a 64 processori, “--cpus 43” userà fino a 43 processori per eseguire contemporaneamente le verifiche. Su un kernel v5.4 per eseguire tutti gli scenari in due serie, riduce il tempo d’esecuzione da otto ore a un’ora (senza contare il tempo per compilare sedici kernel). L’argomento “--dryrun sched” non eseguirà verifiche, piuttosto vi informerà su come queste verranno organizzate in serie. Questo può essere utile per capire quanti processori riservare per le verifiche in --cpus.
Non serve eseguire tutti gli scenari di verifica per ogni modifica. Per esempio, per una modifica a Tree SRCU potete eseguire gli scenari SRCU-N e SRCU-P. Per farlo usate l’argomento --configs di kvm.sh in questo modo: “--configs ‘SRCU-N SRCU-P’”. Su grandi sistemi si possono eseguire più copie degli stessi scenari, per esempio, un hardware che permette di eseguire 448 thread, può eseguire 5 istanze complete contemporaneamente. Per farlo:
kvm.sh --cpus 448 --configs '5*CFLIST'
Oppure, lo stesso sistema, può eseguire contemporaneamente 56 istanze dello scenario su otto processori:
kvm.sh --cpus 448 --configs '56*TREE04'
O ancora 28 istanze per ogni scenario su otto processori:
kvm.sh --cpus 448 --configs '28*TREE03 28*TREE04'
Ovviamente, ogni esecuzione utilizzerà della memoria. Potete limitarne l’uso con l’argomento --memory, che di base assume il valore 512M. Per poter usare valori piccoli dovrete disabilitare le verifiche callback-flooding usando il parametro --bootargs che vedremo in seguito.
A volte è utile avere informazioni aggiuntive di debug, in questo caso potete
usare il parametro --kconfig, per esempio, --kconfig
'CONFIG_RCU_EQS_DEBUG=y'
. In aggiunta, ci sono i parametri --gdb, --kasan, and
kcsan. Da notare che --gdb vi limiterà all’uso di un solo scenario per
esecuzione di kvm.sh e richiede di avere anche un’altra finestra aperta dalla
quale eseguire gdb
come viene spiegato dal programma.
Potete passare anche i parametri d’avvio del kernel, per esempio, per
controllare i parametri del modulo rcutorture. Per esempio, per verificare
modifiche del codice RCU CPU stall-warning, usate bootargs
'rcutorture.stall_cpu=30
. Il programma riporterà un fallimento, ossia il
risultato della verifica. Come visto in precedenza, ridurre la memoria richiede
la disabilitazione delle verifiche callback-flooding:
kvm.sh --cpus 448 --configs '56*TREE04' --memory 128M \
--bootargs 'rcutorture.fwd_progress=0'
A volte tutto quello che serve è una serie completa di compilazioni del kernel. Questo si ottiene col parametro --buildonly.
Il parametro --duration sovrascrive quello di base di 30 minuti. Per esempio,
con --duration 2d
l’esecuzione sarà di due giorni, --duraction 5min
di
cinque minuti, e --duration 45s
di 45 secondi. L’ultimo può essere utile per
scovare rari errori nella sequenza d’avvio.
Infine, il parametro --trust-make permette ad ogni nuova compilazione del kernel di riutilizzare tutto il possibile da quelle precedenti. Da notare che senza il parametro --trust-make, i vostri file di tag potrebbero essere distrutti.
Ci sono altri parametri più misteriosi che sono documentati nel codice sorgente dello programma kvm.sh.
Se un’esecuzione contiene degli errori, il loro numero durante la compilazione e all’esecuzione verranno elencati alla fine fra i risultati di kvm.sh (che vi consigliamo caldamente di reindirizzare verso un file). I file prodotti dalla compilazione ed i risultati stampati vengono salvati, usando un riferimento temporale, nelle cartella tools/testing/selftests/rcutorture/res. Una cartella di queste cartelle può essere fornita a kvm-find-errors.sh per estrarne gli errori. Per esempio:
tools/testing/selftests/rcutorture/bin/kvm-find-errors.sh \
tools/testing/selftests/rcutorture/res/2020.01.20-15.54.23
Tuttavia, molto spesso è più conveniente aprire i file direttamente. I file riguardanti tutti gli scenari di un’esecuzione di trovano nella cartella principale (2020.01.20-15.54.23 nell’esempio precedente), mentre quelli specifici per scenario si trovano in sotto cartelle che prendono il nome dello scenario stesso (per esempio, “TREE04”). Se un dato scenario viene eseguito più di una volta (come abbiamo visto con “--configs ‘56*TREE04’”), allora dalla seconda esecuzione in poi le sottocartelle includeranno un numero di progressione, per esempio “TREE04.2”, “TREE04.3”, e via dicendo.
Il file solitamente più usato nella cartella principale è testid.txt. Se la verifica viene eseguita in un repositorio git, allora questo file conterrà il commit sul quale si basano le verifiche, mentre tutte le modifiche non registrare verranno mostrate in formato diff.
I file solitamente più usati nelle cartelle di scenario sono:
- .config
Questo file contiene le opzioni di Kconfig
- Make.out
Questo file contiene il risultato di compilazione per uno specifico scenario
- console.log
Questo file contiene il risultato d’esecuzione per uno specifico scenario. Questo file può essere esaminato una volta che il kernel è stato avviato, ma potrebbe non esistere se l’avvia non è fallito.
- vmlinux
Questo file contiene il kernel, e potrebbe essere utile da esaminare con programmi come pbjdump e gdb
Ci sono altri file, ma vengono usati meno. Molti sono utili all’analisi di rcutorture stesso o dei suoi programmi.
Nel kernel v5.4, su un sistema a 12 processori, un’esecuzione senza errori usando gli scenari di base produce il seguente risultato:
SRCU-N ------- 804233 GPs (148.932/s) [srcu: g10008272 f0x0 ]
SRCU-P ------- 202320 GPs (37.4667/s) [srcud: g1809476 f0x0 ]
SRCU-t ------- 1122086 GPs (207.794/s) [srcu: g0 f0x0 ]
SRCU-u ------- 1111285 GPs (205.794/s) [srcud: g1 f0x0 ]
TASKS01 ------- 19666 GPs (3.64185/s) [tasks: g0 f0x0 ]
TASKS02 ------- 20541 GPs (3.80389/s) [tasks: g0 f0x0 ]
TASKS03 ------- 19416 GPs (3.59556/s) [tasks: g0 f0x0 ]
TINY01 ------- 836134 GPs (154.84/s) [rcu: g0 f0x0 ] n_max_cbs: 34198
TINY02 ------- 850371 GPs (157.476/s) [rcu: g0 f0x0 ] n_max_cbs: 2631
TREE01 ------- 162625 GPs (30.1157/s) [rcu: g1124169 f0x0 ]
TREE02 ------- 333003 GPs (61.6672/s) [rcu: g2647753 f0x0 ] n_max_cbs: 35844
TREE03 ------- 306623 GPs (56.782/s) [rcu: g2975325 f0x0 ] n_max_cbs: 1496497
CPU count limited from 16 to 12
TREE04 ------- 246149 GPs (45.5831/s) [rcu: g1695737 f0x0 ] n_max_cbs: 434961
TREE05 ------- 314603 GPs (58.2598/s) [rcu: g2257741 f0x2 ] n_max_cbs: 193997
TREE07 ------- 167347 GPs (30.9902/s) [rcu: g1079021 f0x0 ] n_max_cbs: 478732
CPU count limited from 16 to 12
TREE09 ------- 752238 GPs (139.303/s) [rcu: g13075057 f0x0 ] n_max_cbs: 99011
Ripetizioni¶
Immaginate di essere alla caccia di un raro problema che si verifica all’avvio. Potreste usare kvm.sh, tuttavia questo ricompilerebbe il kernel ad ogni esecuzione. Se avete bisogno di (diciamo) 1000 esecuzioni per essere sicuri di aver risolto il problema, allora queste inutili ricompilazioni possono diventare estremamente fastidiose.
Per questo motivo esiste kvm-again.sh.
Immaginate che un’esecuzione precedente di kvm.sh abbia lasciato i suoi artefatti nella cartella:
tools/testing/selftests/rcutorture/res/2022.11.03-11.26.28
Questa esecuzione può essere rieseguita senza ricompilazioni:
kvm-again.sh tools/testing/selftests/rcutorture/res/2022.11.03-11.26.28
Alcuni dei parametri originali di kvm.sh possono essere sovrascritti, in particolare --duration e --bootargs. Per esempio:
kvm-again.sh tools/testing/selftests/rcutorture/res/2022.11.03-11.26.28 \
--duration 45s
rieseguirebbe il test precedente, ma solo per 45 secondi, e quindi aiutando a trovare quel raro problema all’avvio sopracitato.
Esecuzioni distribuite¶
Sebbene kvm.sh sia utile, le sue verifiche sono limitate ad un singolo sistema. Non è poi così difficile usare un qualsiasi ambiente di sviluppo per eseguire (diciamo) 5 istanze di kvm.sh su altrettanti sistemi, ma questo avvierebbe inutili ricompilazioni del kernel. In aggiunta, il processo di distribuzione degli scenari di verifica per rcutorture sui sistemi disponibili richiede scrupolo perché soggetto ad errori.
Per questo esiste kvm-remote.sh.
Se il seguente comando funziona:
ssh system0 date
e funziona anche per system1, system2, system3, system4, e system5, e tutti questi sistemi hanno 64 CPU, allora potere eseguire:
kvm-remote.sh "system0 system1 system2 system3 system4 system5" \
--cpus 64 --duration 8h --configs "5*CFLIST"
Questo compilerà lo scenario di base sul sistema locale, poi lo distribuirà agli altri cinque sistemi elencati fra i parametri, ed eseguirà ogni scenario per otto ore. Alla fine delle esecuzioni, i risultati verranno raccolti, registrati, e stampati. La maggior parte dei parametri di kvm.sh possono essere usati con kvm-remote.sh, tuttavia la lista dei sistemi deve venire sempre per prima.
L’argomento di kvm.sh --dryrun scenarios
può essere utile per scoprire
quanti scenari potrebbero essere eseguiti in gruppo di sistemi.
Potete rieseguire anche una precedente esecuzione remota come abbiamo già fatto per kvm.sh:
kvm-remote.sh "system0 system1 system2 system3 system4 system5" \
tools/testing/selftests/rcutorture/res/2022.11.03-11.26.28-remote \
--duration 24h
In questo caso, la maggior parte dei parametri di kvm-again.sh possono essere usati dopo il percorso alla cartella contenente gli artefatti dell’esecuzione da ripetere.