1.. SPDX-License-Identifier: GPL-2.0 2 3.. include:: ../disclaimer-ita.rst 4 5.. _it_kernel_hacking_locktypes: 6 7======================================== 8Tipologie di blocco e le loro istruzioni 9======================================== 10 11Introduzione 12============ 13 14Il kernel fornisce un certo numero di primitive di blocco che possiamo dividere 15in tre categorie: 16 17 - blocchi ad attesa con sospensione 18 - blocchi locali per CPU 19 - blocchi ad attesa attiva 20 21Questo documento descrive questi tre tipi e fornisce istruzioni su come 22annidarli, ed usarli su kernel PREEMPT_RT. 23 24Categorie di blocchi 25==================== 26 27Blocchi ad attesa con sospensione 28--------------------------------- 29 30I blocchi ad attesa con sospensione possono essere acquisiti solo in un contesti 31dov'è possibile la prelazione. 32 33Diverse implementazioni permettono di usare try_lock() anche in altri contesti, 34nonostante ciò è bene considerare anche la sicurezza dei corrispondenti 35unlock(). Inoltre, vanno prese in considerazione anche le varianti di *debug* 36di queste primitive. Insomma, non usate i blocchi ad attesa con sospensioni in 37altri contesti a meno che proprio non vi siano alternative. 38 39In questa categoria troviamo: 40 41 - mutex 42 - rt_mutex 43 - semaphore 44 - rw_semaphore 45 - ww_mutex 46 - percpu_rw_semaphore 47 48Nei kernel con PREEMPT_RT, i seguenti blocchi sono convertiti in blocchi ad 49attesa con sospensione: 50 51 - local_lock 52 - spinlock_t 53 - rwlock_t 54 55Blocchi locali per CPU 56---------------------- 57 58 - local_lock 59 60Su kernel non-PREEMPT_RT, le funzioni local_lock gestiscono le primitive di 61disabilitazione di prelazione ed interruzioni. Al contrario di altri meccanismi, 62la disabilitazione della prelazione o delle interruzioni sono puri meccanismi 63per il controllo della concorrenza su una CPU e quindi non sono adatti per la 64gestione della concorrenza inter-CPU. 65 66Blocchi ad attesa attiva 67------------------------ 68 69 - raw_spinlcok_t 70 - bit spinlocks 71 72 Nei kernel non-PREEMPT_RT, i seguenti blocchi sono ad attesa attiva: 73 74 - spinlock_t 75 - rwlock_t 76 77Implicitamente, i blocchi ad attesa attiva disabilitano la prelazione e le 78funzioni lock/unlock hanno anche dei suffissi per gestire il livello di 79protezione: 80 81 =================== ========================================================================= 82 _bh() disabilita / abilita *bottom halves* (interruzioni software) 83 _irq() disabilita / abilita le interruzioni 84 _irqsave/restore() salva e disabilita le interruzioni / ripristina ed attiva le interruzioni 85 =================== ========================================================================= 86 87Semantica del proprietario 88========================== 89 90Eccetto i semafori, i sopracitati tipi di blocchi hanno tutti una semantica 91molto stringente riguardo al proprietario di un blocco: 92 93 Il contesto (attività) che ha acquisito il blocco deve rilasciarlo 94 95I semafori rw_semaphores hanno un'interfaccia speciale che permette anche ai non 96proprietari del blocco di rilasciarlo per i lettori. 97 98rtmutex 99======= 100 101I blocchi a mutua esclusione RT (*rtmutex*) sono un sistema a mutua esclusione 102con supporto all'ereditarietà della priorità (PI). 103 104Questo meccanismo ha delle limitazioni sui kernel non-PREEMPT_RT dovuti alla 105prelazione e alle sezioni con interruzioni disabilitate. 106 107Chiaramente, questo meccanismo non può avvalersi della prelazione su una sezione 108dove la prelazione o le interruzioni sono disabilitate; anche sui kernel 109PREEMPT_RT. Tuttavia, i kernel PREEMPT_RT eseguono la maggior parte delle 110sezioni in contesti dov'è possibile la prelazione, specialmente in contesti 111d'interruzione (anche software). Questa conversione permette a spinlock_t e 112rwlock_t di essere implementati usando rtmutex. 113 114semaphore 115========= 116 117La primitiva semaphore implementa un semaforo con contatore. 118 119I semafori vengono spesso utilizzati per la serializzazione e l'attesa, ma per 120nuovi casi d'uso si dovrebbero usare meccanismi diversi, come mutex e 121completion. 122 123semaphore e PREEMPT_RT 124---------------------- 125 126I kernel PREEMPT_RT non cambiano l'implementazione di semaphore perché non hanno 127un concetto di proprietario, dunque impediscono a PREEMPT_RT d'avere 128l'ereditarietà della priorità sui semafori. Un proprietario sconosciuto non può 129ottenere una priorità superiore. Di consequenza, bloccarsi sui semafori porta 130all'inversione di priorità. 131 132 133rw_semaphore 134============ 135 136Il blocco rw_semaphore è un meccanismo che permette più lettori ma un solo scrittore. 137 138Sui kernel non-PREEMPT_RT l'implementazione è imparziale, quindi previene 139l'inedia dei processi scrittori. 140 141Questi blocchi hanno una semantica molto stringente riguardo il proprietario, ma 142offre anche interfacce speciali che permettono ai processi non proprietari di 143rilasciare un processo lettore. Queste interfacce funzionano indipendentemente 144dalla configurazione del kernel. 145 146rw_semaphore e PREEMPT_RT 147------------------------- 148 149I kernel PREEMPT_RT sostituiscono i rw_semaphore con un'implementazione basata 150su rt_mutex, e questo ne modifica l'imparzialità: 151 152 Dato che uno scrittore rw_semaphore non può assicurare la propria priorità ai 153 suoi lettori, un lettore con priorità più bassa che ha subito la prelazione 154 continuerà a trattenere il blocco, quindi porta all'inedia anche gli scrittori 155 con priorità più alta. Per contro, dato che i lettori possono garantire la 156 propria priorità agli scrittori, uno scrittore a bassa priorità che subisce la 157 prelazione vedrà la propria priorità alzata finché non rilascerà il blocco, e 158 questo preverrà l'inedia dei processi lettori a causa di uno scrittore. 159 160 161local_lock 162========== 163 164I local_lock forniscono nomi agli ambiti di visibilità delle sezioni critiche 165protette tramite la disattivazione della prelazione o delle interruzioni. 166 167Sui kernel non-PREEMPT_RT le operazioni local_lock si traducono 168nell'abilitazione o disabilitazione della prelazione o le interruzioni. 169 170 =============================== ====================== 171 local_lock(&llock) preempt_disable() 172 local_unlock(&llock) preempt_enable() 173 local_lock_irq(&llock) local_irq_disable() 174 local_unlock_irq(&llock) local_irq_enable() 175 local_lock_irqsave(&llock) local_irq_save() 176 local_unlock_irqrestore(&llock) local_irq_restore() 177 =============================== ====================== 178 179Gli ambiti di visibilità con nome hanno due vantaggi rispetto alle primitive di 180base: 181 182 - Il nome del blocco permette di fare un'analisi statica, ed è anche chiaro su 183 cosa si applichi la protezione cosa che invece non si può fare con le 184 classiche primitive in quanto sono opache e senza alcun ambito di 185 visibilità. 186 187 - Se viene abilitato lockdep, allora local_lock ottiene un lockmap che 188 permette di verificare la bontà della protezione. Per esempio, questo può 189 identificare i casi dove una funzione usa preempt_disable() come meccanismo 190 di protezione in un contesto d'interruzione (anche software). A parte 191 questo, lockdep_assert_held(&llock) funziona come tutte le altre primitive 192 di sincronizzazione. 193 194local_lock e PREEMPT_RT 195------------------------- 196 197I kernel PREEMPT_RT sostituiscono local_lock con uno spinlock_t per CPU, quindi 198ne cambia la semantica: 199 200 - Tutte le modifiche a spinlock_t si applicano anche a local_lock 201 202L'uso di local_lock 203------------------- 204 205I local_lock dovrebbero essere usati su kernel non-PREEMPT_RT quando la 206disabilitazione della prelazione o delle interruzioni è il modo più adeguato per 207gestire l'accesso concorrente a strutture dati per CPU. 208 209Questo meccanismo non è adatto alla protezione da prelazione o interruzione su 210kernel PREEMPT_RT dato che verrà convertito in spinlock_t. 211 212 213raw_spinlock_t e spinlock_t 214=========================== 215 216raw_spinlock_t 217-------------- 218 219I blocco raw_spinlock_t è un blocco ad attesa attiva su tutti i tipi di kernel, 220incluso quello PREEMPT_RT. Usate raw_spinlock_t solo in sezioni critiche nel 221cuore del codice, nella gestione delle interruzioni di basso livello, e in posti 222dove è necessario disabilitare la prelazione o le interruzioni. Per esempio, per 223accedere in modo sicuro lo stato dell'hardware. A volte, i raw_spinlock_t 224possono essere usati quando la sezione critica è minuscola, per evitare gli 225eccessi di un rtmutex. 226 227spinlock_t 228---------- 229 230Il significato di spinlock_t cambia in base allo stato di PREEMPT_RT. 231 232Sui kernel non-PREEMPT_RT, spinlock_t si traduce in un raw_spinlock_t ed ha 233esattamente lo stesso significato. 234 235spinlock_t e PREEMPT_RT 236----------------------- 237 238Sui kernel PREEMPT_RT, spinlock_t ha un'implementazione dedicata che si basa 239sull'uso di rt_mutex. Questo ne modifica il significato: 240 241 - La prelazione non viene disabilitata. 242 243 - I suffissi relativi alla interruzioni (_irq, _irqsave / _irqrestore) per le 244 operazioni spin_lock / spin_unlock non hanno alcun effetto sullo stato delle 245 interruzioni della CPU. 246 247 - I suffissi relativi alle interruzioni software (_bh()) disabilitano i 248 relativi gestori d'interruzione. 249 250 I kernel non-PREEMPT_RT disabilitano la prelazione per ottenere lo stesso effetto. 251 252 I kernel PREEMPT_RT usano un blocco per CPU per la serializzazione, il che 253 permette di tenere attiva la prelazione. Il blocco disabilita i gestori 254 d'interruzione software e previene la rientranza vista la prelazione attiva. 255 256A parte quanto appena discusso, i kernel PREEMPT_RT preservano il significato 257di tutti gli altri aspetti di spinlock_t: 258 259 - Le attività che trattengono un blocco spinlock_t non migrano su altri 260 processori. Disabilitando la prelazione, i kernel non-PREEMPT_RT evitano la 261 migrazione. Invece, i kernel PREEMPT_RT disabilitano la migrazione per 262 assicurarsi che i puntatori a variabili per CPU rimangano validi anche 263 quando un'attività subisce la prelazione. 264 265 - Lo stato di un'attività si mantiene durante le acquisizioni del blocco al 266 fine di garantire che le regole basate sullo stato delle attività si possano 267 applicare a tutte le configurazioni del kernel. I kernel non-PREEMPT_RT 268 lasciano lo stato immutato. Tuttavia, la funzionalità PREEMPT_RT deve 269 cambiare lo stato se l'attività si blocca durante l'acquisizione. Dunque, 270 salva lo stato attuale prima di bloccarsi ed il rispettivo risveglio lo 271 ripristinerà come nell'esempio seguente:: 272 273 task->state = TASK_INTERRUPTIBLE 274 lock() 275 block() 276 task->saved_state = task->state 277 task->state = TASK_UNINTERRUPTIBLE 278 schedule() 279 lock wakeup 280 task->state = task->saved_state 281 282 Altri tipi di risvegli avrebbero impostato direttamente lo stato a RUNNING, 283 ma in questo caso non avrebbe funzionato perché l'attività deve rimanere 284 bloccata fintanto che il blocco viene trattenuto. Quindi, lo stato salvato 285 viene messo a RUNNING quando il risveglio di un non-blocco cerca di 286 risvegliare un'attività bloccata in attesa del rilascio di uno spinlock. Poi, 287 quando viene completata l'acquisizione del blocco, il suo risveglio 288 ripristinerà lo stato salvato, in questo caso a RUNNING:: 289 290 task->state = TASK_INTERRUPTIBLE 291 lock() 292 block() 293 task->saved_state = task->state 294 task->state = TASK_UNINTERRUPTIBLE 295 schedule() 296 non lock wakeup 297 task->saved_state = TASK_RUNNING 298 299 lock wakeup 300 task->state = task->saved_state 301 302 Questo garantisce che il vero risveglio non venga perso. 303 304rwlock_t 305======== 306 307Il blocco rwlock_t è un meccanismo che permette più lettori ma un solo scrittore. 308 309Sui kernel non-PREEMPT_RT questo è un blocco ad attesa e per i suoi suffissi si 310applicano le stesse regole per spinlock_t. La sua implementazione è imparziale, 311quindi previene l'inedia dei processi scrittori. 312 313rwlock_t e PREEMPT_RT 314--------------------- 315 316Sui kernel PREEMPT_RT rwlock_t ha un'implementazione dedicata che si basa 317sull'uso di rt_mutex. Questo ne modifica il significato: 318 319 - Tutte le modifiche fatte a spinlock_t si applicano anche a rwlock_t. 320 321 - Dato che uno scrittore rw_semaphore non può assicurare la propria priorità ai 322 suoi lettori, un lettore con priorità più bassa che ha subito la prelazione 323 continuerà a trattenere il blocco, quindi porta all'inedia anche gli 324 scrittori con priorità più alta. Per contro, dato che i lettori possono 325 garantire la propria priorità agli scrittori, uno scrittore a bassa priorità 326 che subisce la prelazione vedrà la propria priorità alzata finché non 327 rilascerà il blocco, e questo preverrà l'inedia dei processi lettori a causa 328 di uno scrittore. 329 330 331Precisazioni su PREEMPT_RT 332========================== 333 334local_lock su RT 335---------------- 336 337Sui kernel PREEMPT_RT Ci sono alcune implicazioni dovute alla conversione di 338local_lock in un spinlock_t. Per esempio, su un kernel non-PREEMPT_RT il 339seguente codice funzionerà come ci si aspetta:: 340 341 local_lock_irq(&local_lock); 342 raw_spin_lock(&lock); 343 344ed è equivalente a:: 345 346 raw_spin_lock_irq(&lock); 347 348Ma su un kernel PREEMPT_RT questo codice non funzionerà perché local_lock_irq() 349si traduce in uno spinlock_t per CPU che non disabilita né le interruzioni né la 350prelazione. Il seguente codice funzionerà su entrambe i kernel con o senza 351PREEMPT_RT:: 352 353 local_lock_irq(&local_lock); 354 spin_lock(&lock); 355 356Un altro dettaglio da tenere a mente con local_lock è che ognuno di loro ha un 357ambito di protezione ben preciso. Dunque, la seguente sostituzione è errate:: 358 359 360 func1() 361 { 362 local_irq_save(flags); -> local_lock_irqsave(&local_lock_1, flags); 363 func3(); 364 local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock_1, flags); 365 } 366 367 func2() 368 { 369 local_irq_save(flags); -> local_lock_irqsave(&local_lock_2, flags); 370 func3(); 371 local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock_2, flags); 372 } 373 374 func3() 375 { 376 lockdep_assert_irqs_disabled(); 377 access_protected_data(); 378 } 379 380Questo funziona correttamente su un kernel non-PREEMPT_RT, ma su un kernel 381PREEMPT_RT local_lock_1 e local_lock_2 sono distinti e non possono serializzare 382i chiamanti di func3(). L'*assert* di lockdep verrà attivato su un kernel 383PREEMPT_RT perché local_lock_irqsave() non disabilita le interruzione a casa 384della specifica semantica di spinlock_t in PREEMPT_RT. La corretta sostituzione 385è:: 386 387 func1() 388 { 389 local_irq_save(flags); -> local_lock_irqsave(&local_lock, flags); 390 func3(); 391 local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock, flags); 392 } 393 394 func2() 395 { 396 local_irq_save(flags); -> local_lock_irqsave(&local_lock, flags); 397 func3(); 398 local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock, flags); 399 } 400 401 func3() 402 { 403 lockdep_assert_held(&local_lock); 404 access_protected_data(); 405 } 406 407spinlock_t e rwlock_t 408--------------------- 409 410Ci sono alcune conseguenze di cui tener conto dal cambiamento di semantica di 411spinlock_t e rwlock_t sui kernel PREEMPT_RT. Per esempio, sui kernel non 412PREEMPT_RT il seguente codice funziona come ci si aspetta:: 413 414 local_irq_disable(); 415 spin_lock(&lock); 416 417ed è equivalente a:: 418 419 spin_lock_irq(&lock); 420 421Lo stesso vale per rwlock_t e le varianti con _irqsave(). 422 423Sui kernel PREEMPT_RT questo codice non funzionerà perché gli rtmutex richiedono 424un contesto con la possibilità di prelazione. Al suo posto, usate 425spin_lock_irq() o spin_lock_irqsave() e le loro controparti per il rilascio. I 426kernel PREEMPT_RT offrono un meccanismo local_lock per i casi in cui la 427disabilitazione delle interruzioni ed acquisizione di un blocco devono rimanere 428separati. Acquisire un local_lock àncora un processo ad una CPU permettendo cose 429come un'acquisizione di un blocco con interruzioni disabilitate per singola CPU. 430 431Il tipico scenario è quando si vuole proteggere una variabile di processore nel 432contesto di un thread:: 433 434 435 struct foo *p = get_cpu_ptr(&var1); 436 437 spin_lock(&p->lock); 438 p->count += this_cpu_read(var2); 439 440Questo codice è corretto su un kernel non-PREEMPT_RT, ma non lo è su un 441PREEMPT_RT. La modifica della semantica di spinlock_t su PREEMPT_RT non permette 442di acquisire p->lock perché, implicitamente, get_cpu_ptr() disabilita la 443prelazione. La seguente sostituzione funzionerà su entrambe i kernel:: 444 445 struct foo *p; 446 447 migrate_disable(); 448 p = this_cpu_ptr(&var1); 449 spin_lock(&p->lock); 450 p->count += this_cpu_read(var2); 451 452La funzione migrate_disable() assicura che il processo venga tenuto sulla CPU 453corrente, e di conseguenza garantisce che gli accessi per-CPU alle variabili var1 e 454var2 rimangano sulla stessa CPU fintanto che il processo rimane prelabile. 455 456La sostituzione con migrate_disable() non funzionerà nel seguente scenario:: 457 458 func() 459 { 460 struct foo *p; 461 462 migrate_disable(); 463 p = this_cpu_ptr(&var1); 464 p->val = func2(); 465 466Questo non funziona perché migrate_disable() non protegge dal ritorno da un 467processo che aveva avuto il diritto di prelazione. Una sostituzione più adatta 468per questo caso è:: 469 470 func() 471 { 472 struct foo *p; 473 474 local_lock(&foo_lock); 475 p = this_cpu_ptr(&var1); 476 p->val = func2(); 477 478Su un kernel non-PREEMPT_RT, questo codice protegge dal rientro disabilitando la 479prelazione. Su un kernel PREEMPT_RT si ottiene lo stesso risultato acquisendo lo 480spinlock di CPU. 481 482raw_spinlock_t su RT 483-------------------- 484 485Acquisire un raw_spinlock_t disabilita la prelazione e possibilmente anche le 486interruzioni, quindi la sezione critica deve evitare di acquisire uno spinlock_t 487o rwlock_t. Per esempio, la sezione critica non deve fare allocazioni di 488memoria. Su un kernel non-PREEMPT_RT il seguente codice funziona perfettamente:: 489 490 raw_spin_lock(&lock); 491 p = kmalloc(sizeof(*p), GFP_ATOMIC); 492 493Ma lo stesso codice non funziona su un kernel PREEMPT_RT perché l'allocatore di 494memoria può essere oggetto di prelazione e quindi non può essere chiamato in un 495contesto atomico. Tuttavia, si può chiamare l'allocatore di memoria quando si 496trattiene un blocco *non-raw* perché non disabilitano la prelazione sui kernel 497PREEMPT_RT:: 498 499 spin_lock(&lock); 500 p = kmalloc(sizeof(*p), GFP_ATOMIC); 501 502 503bit spinlocks 504------------- 505 506I kernel PREEMPT_RT non possono sostituire i bit spinlock perché un singolo bit 507è troppo piccolo per farci stare un rtmutex. Dunque, la semantica dei bit 508spinlock è mantenuta anche sui kernel PREEMPT_RT. Quindi, le precisazioni fatte 509per raw_spinlock_t valgono anche qui. 510 511In PREEMPT_RT, alcuni bit spinlock sono sostituiti con normali spinlock_t usando 512condizioni di preprocessore in base a dove vengono usati. Per contro, questo non 513serve quando si sostituiscono gli spinlock_t. Invece, le condizioni poste sui 514file d'intestazione e sul cuore dell'implementazione della sincronizzazione 515permettono al compilatore di effettuare la sostituzione in modo trasparente. 516 517 518Regole d'annidamento dei tipi di blocchi 519======================================== 520 521Le regole principali sono: 522 523 - I tipi di blocco appartenenti alla stessa categoria possono essere annidati 524 liberamente a patto che si rispetti l'ordine di blocco al fine di evitare 525 stalli. 526 527 - I blocchi con sospensione non possono essere annidati in blocchi del tipo 528 CPU locale o ad attesa attiva 529 530 - I blocchi ad attesa attiva e su CPU locale possono essere annidati nei 531 blocchi ad attesa con sospensione. 532 533 - I blocchi ad attesa attiva possono essere annidati in qualsiasi altro tipo. 534 535Queste limitazioni si applicano ad entrambe i kernel con o senza PREEMPT_RT. 536 537Il fatto che un kernel PREEMPT_RT cambi i blocchi spinlock_t e rwlock_t dal tipo 538ad attesa attiva a quello con sospensione, e che sostituisca local_lock con uno 539spinlock_t per CPU, significa che non possono essere acquisiti quando si è in un 540blocco raw_spinlock. Ne consegue il seguente ordine d'annidamento: 541 542 1) blocchi ad attesa con sospensione 543 2) spinlock_t, rwlock_t, local_lock 544 3) raw_spinlock_t e bit spinlocks 545 546Se queste regole verranno violate, allora lockdep se ne accorgerà e questo sia 547con o senza PREEMPT_RT. 548