xref: /linux/Documentation/translations/it_IT/locking/locktypes.rst (revision 79790b6818e96c58fe2bffee1b418c16e64e7b80)
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