diff options
Diffstat (limited to 'Documentation/translations/it_IT/locking/locktypes.rst')
-rw-r--r-- | Documentation/translations/it_IT/locking/locktypes.rst | 547 |
1 files changed, 547 insertions, 0 deletions
diff --git a/Documentation/translations/it_IT/locking/locktypes.rst b/Documentation/translations/it_IT/locking/locktypes.rst new file mode 100644 index 000000000000..1c7056283b9d --- /dev/null +++ b/Documentation/translations/it_IT/locking/locktypes.rst @@ -0,0 +1,547 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-ita.rst + +.. _it_kernel_hacking_locktypes: + +======================================== +Tipologie di blocco e le loro istruzioni +======================================== + +Introduzione +============ + +Il kernel fornisce un certo numero di primitive di blocco che possiamo dividere +in tre categorie: + + - blocchi ad attesa con sospensione + - blocchi locali per CPU + - blocchi ad attesa attiva + +Questo documento descrive questi tre tipi e fornisce istruzioni su come +annidarli, ed usarli su kernel PREEMPT_RT. + +Categorie di blocchi +==================== + +Blocchi ad attesa con sospensione +--------------------------------- + +I blocchi ad attesa con sospensione possono essere acquisiti solo in un contesti +dov'è possibile la prelazione. + +Diverse implementazioni permettono di usare try_lock() anche in altri contesti, +nonostante ciò è bene considerare anche la sicurezza dei corrispondenti +unlock(). Inoltre, vanno prese in considerazione anche le varianti di *debug* +di queste primitive. Insomma, non usate i blocchi ad attesa con sospensioni in +altri contesti a meno che proprio non vi siano alternative. + +In questa categoria troviamo: + + - mutex + - rt_mutex + - semaphore + - rw_semaphore + - ww_mutex + - percpu_rw_semaphore + +Nei kernel con PREEMPT_RT, i seguenti blocchi sono convertiti in blocchi ad +attesa con sospensione: + + - local_lock + - spinlock_t + - rwlock_t + +Blocchi locali per CPU +---------------------- + + - local_lock + +Su kernel non-PREEMPT_RT, le funzioni local_lock gestiscono le primitive di +disabilitazione di prelazione ed interruzioni. Al contrario di altri meccanismi, +la disabilitazione della prelazione o delle interruzioni sono puri meccanismi +per il controllo della concorrenza su una CPU e quindi non sono adatti per la +gestione della concorrenza inter-CPU. + +Blocchi ad attesa attiva +------------------------ + + - raw_spinlcok_t + - bit spinlocks + + Nei kernel non-PREEMPT_RT, i seguenti blocchi sono ad attesa attiva: + + - spinlock_t + - rwlock_t + +Implicitamente, i blocchi ad attesa attiva disabilitano la prelazione e le +funzioni lock/unlock hanno anche dei suffissi per gestire il livello di +protezione: + + =================== ========================================================================= + _bh() disabilita / abilita *bottom halves* (interruzioni software) + _irq() disabilita / abilita le interruzioni + _irqsave/restore() salva e disabilita le interruzioni / ripristina ed attiva le interruzioni + =================== ========================================================================= + +Semantica del proprietario +========================== + +Eccetto i semafori, i sopracitati tipi di blocchi hanno tutti una semantica +molto stringente riguardo al proprietario di un blocco: + + Il contesto (attività) che ha acquisito il blocco deve rilasciarlo + +I semafori rw_semaphores hanno un'interfaccia speciale che permette anche ai non +proprietari del blocco di rilasciarlo per i lettori. + +rtmutex +======= + +I blocchi a mutua esclusione RT (*rtmutex*) sono un sistema a mutua esclusione +con supporto all'ereditarietà della priorità (PI). + +Questo meccanismo ha delle limitazioni sui kernel non-PREEMPT_RT dovuti alla +prelazione e alle sezioni con interruzioni disabilitate. + +Chiaramente, questo meccanismo non può avvalersi della prelazione su una sezione +dove la prelazione o le interruzioni sono disabilitate; anche sui kernel +PREEMPT_RT. Tuttavia, i kernel PREEMPT_RT eseguono la maggior parte delle +sezioni in contesti dov'è possibile la prelazione, specialmente in contesti +d'interruzione (anche software). Questa conversione permette a spinlock_t e +rwlock_t di essere implementati usando rtmutex. + +semaphore +========= + +La primitiva semaphore implementa un semaforo con contatore. + +I semafori vengono spesso utilizzati per la serializzazione e l'attesa, ma per +nuovi casi d'uso si dovrebbero usare meccanismi diversi, come mutex e +completion. + +semaphore e PREEMPT_RT +---------------------- + +I kernel PREEMPT_RT non cambiano l'implementazione di semaphore perché non hanno +un concetto di proprietario, dunque impediscono a PREEMPT_RT d'avere +l'ereditarietà della priorità sui semafori. Un proprietario sconosciuto non può +ottenere una priorità superiore. Di consequenza, bloccarsi sui semafori porta +all'inversione di priorità. + + +rw_semaphore +============ + +Il blocco rw_semaphore è un meccanismo che permette più lettori ma un solo scrittore. + +Sui kernel non-PREEMPT_RT l'implementazione è imparziale, quindi previene +l'inedia dei processi scrittori. + +Questi blocchi hanno una semantica molto stringente riguardo il proprietario, ma +offre anche interfacce speciali che permettono ai processi non proprietari di +rilasciare un processo lettore. Queste interfacce funzionano indipendentemente +dalla configurazione del kernel. + +rw_semaphore e PREEMPT_RT +------------------------- + +I kernel PREEMPT_RT sostituiscono i rw_semaphore con un'implementazione basata +su rt_mutex, e questo ne modifica l'imparzialità: + + Dato che uno scrittore rw_semaphore non può assicurare la propria priorità ai + suoi lettori, un lettore con priorità più bassa che ha subito la prelazione + continuerà a trattenere il blocco, quindi porta all'inedia anche gli scrittori + con priorità più alta. Per contro, dato che i lettori possono garantire la + propria priorità agli scrittori, uno scrittore a bassa priorità che subisce la + prelazione vedrà la propria priorità alzata finché non rilascerà il blocco, e + questo preverrà l'inedia dei processi lettori a causa di uno scrittore. + + +local_lock +========== + +I local_lock forniscono nomi agli ambiti di visibilità delle sezioni critiche +protette tramite la disattivazione della prelazione o delle interruzioni. + +Sui kernel non-PREEMPT_RT le operazioni local_lock si traducono +nell'abilitazione o disabilitazione della prelazione o le interruzioni. + + =============================== ====================== + local_lock(&llock) preempt_disable() + local_unlock(&llock) preempt_enable() + local_lock_irq(&llock) local_irq_disable() + local_unlock_irq(&llock) local_irq_enable() + local_lock_irqsave(&llock) local_irq_save() + local_unlock_irqrestore(&llock) local_irq_restore() + =============================== ====================== + +Gli ambiti di visibilità con nome hanno due vantaggi rispetto alle primitive di +base: + + - Il nome del blocco permette di fare un'analisi statica, ed è anche chiaro su + cosa si applichi la protezione cosa che invece non si può fare con le + classiche primitive in quanto sono opache e senza alcun ambito di + visibilità. + + - Se viene abilitato lockdep, allora local_lock ottiene un lockmap che + permette di verificare la bontà della protezione. Per esempio, questo può + identificare i casi dove una funzione usa preempt_disable() come meccanismo + di protezione in un contesto d'interruzione (anche software). A parte + questo, lockdep_assert_held(&llock) funziona come tutte le altre primitive + di sincronizzazione. + +local_lock e PREEMPT_RT +------------------------- + +I kernel PREEMPT_RT sostituiscono local_lock con uno spinlock_t per CPU, quindi +ne cambia la semantica: + + - Tutte le modifiche a spinlock_t si applicano anche a local_lock + +L'uso di local_lock +------------------- + +I local_lock dovrebbero essere usati su kernel non-PREEMPT_RT quando la +disabilitazione della prelazione o delle interruzioni è il modo più adeguato per +gestire l'accesso concorrente a strutture dati per CPU. + +Questo meccanismo non è adatto alla protezione da prelazione o interruzione su +kernel PREEMPT_RT dato che verrà convertito in spinlock_t. + + +raw_spinlock_t e spinlock_t +=========================== + +raw_spinlock_t +-------------- + +I blocco raw_spinlock_t è un blocco ad attesa attiva su tutti i tipi di kernel, +incluso quello PREEMPT_RT. Usate raw_spinlock_t solo in sezioni critiche nel +cuore del codice, nella gestione delle interruzioni di basso livello, e in posti +dove è necessario disabilitare la prelazione o le interruzioni. Per esempio, per +accedere in modo sicuro lo stato dell'hardware. A volte, i raw_spinlock_t +possono essere usati quando la sezione critica è minuscola, per evitare gli +eccessi di un rtmutex. + +spinlock_t +---------- + +Il significato di spinlock_t cambia in base allo stato di PREEMPT_RT. + +Sui kernel non-PREEMPT_RT, spinlock_t si traduce in un raw_spinlock_t ed ha +esattamente lo stesso significato. + +spinlock_t e PREEMPT_RT +----------------------- + +Sui kernel PREEMPT_RT, spinlock_t ha un'implementazione dedicata che si basa +sull'uso di rt_mutex. Questo ne modifica il significato: + + - La prelazione non viene disabilitata. + + - I suffissi relativi alla interruzioni (_irq, _irqsave / _irqrestore) per le + operazioni spin_lock / spin_unlock non hanno alcun effetto sullo stato delle + interruzioni della CPU. + + - I suffissi relativi alle interruzioni software (_bh()) disabilitano i + relativi gestori d'interruzione. + + I kernel non-PREEMPT_RT disabilitano la prelazione per ottenere lo stesso effetto. + + I kernel PREEMPT_RT usano un blocco per CPU per la serializzazione, il che + permette di tenere attiva la prelazione. Il blocco disabilita i gestori + d'interruzione software e previene la rientranza vista la prelazione attiva. + +A parte quanto appena discusso, i kernel PREEMPT_RT preservano il significato +di tutti gli altri aspetti di spinlock_t: + + - Le attività che trattengono un blocco spinlock_t non migrano su altri + processori. Disabilitando la prelazione, i kernel non-PREEMPT_RT evitano la + migrazione. Invece, i kernel PREEMPT_RT disabilitano la migrazione per + assicurarsi che i puntatori a variabili per CPU rimangano validi anche + quando un'attività subisce la prelazione. + + - Lo stato di un'attività si mantiene durante le acquisizioni del blocco al + fine di garantire che le regole basate sullo stato delle attività si possano + applicare a tutte le configurazioni del kernel. I kernel non-PREEMPT_RT + lasciano lo stato immutato. Tuttavia, la funzionalità PREEMPT_RT deve + cambiare lo stato se l'attività si blocca durante l'acquisizione. Dunque, + salva lo stato attuale prima di bloccarsi ed il rispettivo risveglio lo + ripristinerà come nell'esempio seguente:: + + task->state = TASK_INTERRUPTIBLE + lock() + block() + task->saved_state = task->state + task->state = TASK_UNINTERRUPTIBLE + schedule() + lock wakeup + task->state = task->saved_state + + Altri tipi di risvegli avrebbero impostato direttamente lo stato a RUNNING, + ma in questo caso non avrebbe funzionato perché l'attività deve rimanere + bloccata fintanto che il blocco viene trattenuto. Quindi, lo stato salvato + viene messo a RUNNING quando il risveglio di un non-blocco cerca di + risvegliare un'attività bloccata in attesa del rilascio di uno spinlock. Poi, + quando viene completata l'acquisizione del blocco, il suo risveglio + ripristinerà lo stato salvato, in questo caso a RUNNING:: + + task->state = TASK_INTERRUPTIBLE + lock() + block() + task->saved_state = task->state + task->state = TASK_UNINTERRUPTIBLE + schedule() + non lock wakeup + task->saved_state = TASK_RUNNING + + lock wakeup + task->state = task->saved_state + + Questo garantisce che il vero risveglio non venga perso. + +rwlock_t +======== + +Il blocco rwlock_t è un meccanismo che permette più lettori ma un solo scrittore. + +Sui kernel non-PREEMPT_RT questo è un blocco ad attesa e per i suoi suffissi si +applicano le stesse regole per spinlock_t. La sua implementazione è imparziale, +quindi previene l'inedia dei processi scrittori. + +rwlock_t e PREEMPT_RT +--------------------- + +Sui kernel PREEMPT_RT rwlock_t ha un'implementazione dedicata che si basa +sull'uso di rt_mutex. Questo ne modifica il significato: + + - Tutte le modifiche fatte a spinlock_t si applicano anche a rwlock_t. + + - Dato che uno scrittore rw_semaphore non può assicurare la propria priorità ai + suoi lettori, un lettore con priorità più bassa che ha subito la prelazione + continuerà a trattenere il blocco, quindi porta all'inedia anche gli + scrittori con priorità più alta. Per contro, dato che i lettori possono + garantire la propria priorità agli scrittori, uno scrittore a bassa priorità + che subisce la prelazione vedrà la propria priorità alzata finché non + rilascerà il blocco, e questo preverrà l'inedia dei processi lettori a causa + di uno scrittore. + + +Precisazioni su PREEMPT_RT +========================== + +local_lock su RT +---------------- + +Sui kernel PREEMPT_RT Ci sono alcune implicazioni dovute alla conversione di +local_lock in un spinlock_t. Per esempio, su un kernel non-PREEMPT_RT il +seguente codice funzionerà come ci si aspetta:: + + local_lock_irq(&local_lock); + raw_spin_lock(&lock); + +ed è equivalente a:: + + raw_spin_lock_irq(&lock); + +Ma su un kernel PREEMPT_RT questo codice non funzionerà perché local_lock_irq() +si traduce in uno spinlock_t per CPU che non disabilita né le interruzioni né la +prelazione. Il seguente codice funzionerà su entrambe i kernel con o senza +PREEMPT_RT:: + + local_lock_irq(&local_lock); + spin_lock(&lock); + +Un altro dettaglio da tenere a mente con local_lock è che ognuno di loro ha un +ambito di protezione ben preciso. Dunque, la seguente sostituzione è errate:: + + + func1() + { + local_irq_save(flags); -> local_lock_irqsave(&local_lock_1, flags); + func3(); + local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock_1, flags); + } + + func2() + { + local_irq_save(flags); -> local_lock_irqsave(&local_lock_2, flags); + func3(); + local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock_2, flags); + } + + func3() + { + lockdep_assert_irqs_disabled(); + access_protected_data(); + } + +Questo funziona correttamente su un kernel non-PREEMPT_RT, ma su un kernel +PREEMPT_RT local_lock_1 e local_lock_2 sono distinti e non possono serializzare +i chiamanti di func3(). L'*assert* di lockdep verrà attivato su un kernel +PREEMPT_RT perché local_lock_irqsave() non disabilita le interruzione a casa +della specifica semantica di spinlock_t in PREEMPT_RT. La corretta sostituzione +è:: + + func1() + { + local_irq_save(flags); -> local_lock_irqsave(&local_lock, flags); + func3(); + local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock, flags); + } + + func2() + { + local_irq_save(flags); -> local_lock_irqsave(&local_lock, flags); + func3(); + local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock, flags); + } + + func3() + { + lockdep_assert_held(&local_lock); + access_protected_data(); + } + +spinlock_t e rwlock_t +--------------------- + +Ci sono alcune conseguenze di cui tener conto dal cambiamento di semantica di +spinlock_t e rwlock_t sui kernel PREEMPT_RT. Per esempio, sui kernel non +PREEMPT_RT il seguente codice funziona come ci si aspetta:: + + local_irq_disable(); + spin_lock(&lock); + +ed è equivalente a:: + + spin_lock_irq(&lock); + +Lo stesso vale per rwlock_t e le varianti con _irqsave(). + +Sui kernel PREEMPT_RT questo codice non funzionerà perché gli rtmutex richiedono +un contesto con la possibilità di prelazione. Al suo posto, usate +spin_lock_irq() o spin_lock_irqsave() e le loro controparti per il rilascio. I +kernel PREEMPT_RT offrono un meccanismo local_lock per i casi in cui la +disabilitazione delle interruzioni ed acquisizione di un blocco devono rimanere +separati. Acquisire un local_lock àncora un processo ad una CPU permettendo cose +come un'acquisizione di un blocco con interruzioni disabilitate per singola CPU. + +Il tipico scenario è quando si vuole proteggere una variabile di processore nel +contesto di un thread:: + + + struct foo *p = get_cpu_ptr(&var1); + + spin_lock(&p->lock); + p->count += this_cpu_read(var2); + +Questo codice è corretto su un kernel non-PREEMPT_RT, ma non lo è su un +PREEMPT_RT. La modifica della semantica di spinlock_t su PREEMPT_RT non permette +di acquisire p->lock perché, implicitamente, get_cpu_ptr() disabilita la +prelazione. La seguente sostituzione funzionerà su entrambe i kernel:: + + struct foo *p; + + migrate_disable(); + p = this_cpu_ptr(&var1); + spin_lock(&p->lock); + p->count += this_cpu_read(var2); + +La funzione migrate_disable() assicura che il processo venga tenuto sulla CPU +corrente, e di conseguenza garantisce che gli accessi per-CPU alle variabili var1 e +var2 rimangano sulla stessa CPU fintanto che il processo rimane prelabile. + +La sostituzione con migrate_disable() non funzionerà nel seguente scenario:: + + func() + { + struct foo *p; + + migrate_disable(); + p = this_cpu_ptr(&var1); + p->val = func2(); + +Questo non funziona perché migrate_disable() non protegge dal ritorno da un +processo che aveva avuto il diritto di prelazione. Una sostituzione più adatta +per questo caso è:: + + func() + { + struct foo *p; + + local_lock(&foo_lock); + p = this_cpu_ptr(&var1); + p->val = func2(); + +Su un kernel non-PREEMPT_RT, questo codice protegge dal rientro disabilitando la +prelazione. Su un kernel PREEMPT_RT si ottiene lo stesso risultato acquisendo lo +spinlock di CPU. + +raw_spinlock_t su RT +-------------------- + +Acquisire un raw_spinlock_t disabilita la prelazione e possibilmente anche le +interruzioni, quindi la sezione critica deve evitare di acquisire uno spinlock_t +o rwlock_t. Per esempio, la sezione critica non deve fare allocazioni di +memoria. Su un kernel non-PREEMPT_RT il seguente codice funziona perfettamente:: + + raw_spin_lock(&lock); + p = kmalloc(sizeof(*p), GFP_ATOMIC); + +Ma lo stesso codice non funziona su un kernel PREEMPT_RT perché l'allocatore di +memoria può essere oggetto di prelazione e quindi non può essere chiamato in un +contesto atomico. Tuttavia, si può chiamare l'allocatore di memoria quando si +trattiene un blocco *non-raw* perché non disabilitano la prelazione sui kernel +PREEMPT_RT:: + + spin_lock(&lock); + p = kmalloc(sizeof(*p), GFP_ATOMIC); + + +bit spinlocks +------------- + +I kernel PREEMPT_RT non possono sostituire i bit spinlock perché un singolo bit +è troppo piccolo per farci stare un rtmutex. Dunque, la semantica dei bit +spinlock è mantenuta anche sui kernel PREEMPT_RT. Quindi, le precisazioni fatte +per raw_spinlock_t valgono anche qui. + +In PREEMPT_RT, alcuni bit spinlock sono sostituiti con normali spinlock_t usando +condizioni di preprocessore in base a dove vengono usati. Per contro, questo non +serve quando si sostituiscono gli spinlock_t. Invece, le condizioni poste sui +file d'intestazione e sul cuore dell'implementazione della sincronizzazione +permettono al compilatore di effettuare la sostituzione in modo trasparente. + + +Regole d'annidamento dei tipi di blocchi +======================================== + +Le regole principali sono: + + - I tipi di blocco appartenenti alla stessa categoria possono essere annidati + liberamente a patto che si rispetti l'ordine di blocco al fine di evitare + stalli. + + - I blocchi con sospensione non possono essere annidati in blocchi del tipo + CPU locale o ad attesa attiva + + - I blocchi ad attesa attiva e su CPU locale possono essere annidati nei + blocchi ad attesa con sospensione. + + - I blocchi ad attesa attiva possono essere annidati in qualsiasi altro tipo. + +Queste limitazioni si applicano ad entrambe i kernel con o senza PREEMPT_RT. + +Il fatto che un kernel PREEMPT_RT cambi i blocchi spinlock_t e rwlock_t dal tipo +ad attesa attiva a quello con sospensione, e che sostituisca local_lock con uno +spinlock_t per CPU, significa che non possono essere acquisiti quando si è in un +blocco raw_spinlock. Ne consegue il seguente ordine d'annidamento: + + 1) blocchi ad attesa con sospensione + 2) spinlock_t, rwlock_t, local_lock + 3) raw_spinlock_t e bit spinlocks + +Se queste regole verranno violate, allora lockdep se ne accorgerà e questo sia +con o senza PREEMPT_RT. |