aboutsummaryrefslogtreecommitdiffstats
path: root/Documentation/translations/it_IT/locking/locktypes.rst
blob: 1c7056283b9de5cca9f1f4ef5b3809eae2992848 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
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.