← Documents Documentation/translations/it_IT/kernel-hacking/locking.rst GitHub 원문 ↗

Linux 6.18.37 · Translations

신뢰하기 어려운 커널 동기화 안내서

SMP 경쟁 상태, spinlock·mutex 선택, IRQ·softirq 동기화, 참조 카운팅, 교착, timer 수명, RCU와 per-CPU 설계를 설명합니다.

Source pathDocumentation/translations/it_IT/kernel-hacking/locking.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

locking.rst:1-1498

이 안내서는 실행 컨텍스트 조합별 최소 잠금부터 객체 수명, ABBA 교착, timer 종료 경쟁, RCU grace period와 per-CPU 데이터까지 잠금 설계의 전체 흐름을 예제 중심으로 설명합니다.

원문은 Linux 2.6 시기의 역사적 설명과 API도 포함합니다. 이 페이지는 로컬 Linux v6.18.37 원문 1,498줄을 보존해 번역하며, 실제 새 코드에는 현재 잠금·timer·RCU 하위 문서와 구현을 함께 확인해야 합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. include:: ../disclaimer-ita.rst
2
3 .. c:namespace:: it_IT
4
5 :Original: :ref:`Documentation/kernel-hacking/locking.rst <kernel_hacking_lock>`
6 :Translator: Federico Vaga <[email protected]>
7
8 .. _it_kernel_hacking_lock:
9
10 ==========================================
11 L'inaffidabile guida alla sincronizzazione
12 ==========================================
13
14 :Author: Rusty Russell
15
16 Introduzione
17 ============
18
19 Benvenuto, alla notevole ed inaffidabile guida ai problemi di sincronizzazione
20 (locking) nel kernel. Questo documento descrive il sistema di sincronizzazione
21 nel kernel Linux 2.6.
22
23 Dato il largo utilizzo del multi-threading e della prelazione nel kernel
24 Linux, chiunque voglia dilettarsi col kernel deve conoscere i concetti
25 fondamentali della concorrenza e della sincronizzazione nei sistemi
26 multi-processore.
27
28 Il problema con la concorrenza
29 ==============================
30
31 (Saltatelo se sapete già cos'è una corsa critica).
32
33 In un normale programma, potete incrementare un contatore nel seguente modo:
34
35 ::
36
37 contatore++;
38
39 Questo è quello che vi aspettereste che accada sempre:
40
41
42 .. table:: Risultati attesi
43
44 +------------------------------------+------------------------------------+
45 | Istanza 1 | Istanza 2 |
46 +====================================+====================================+
47 | leggi contatore (5) | |
48 +------------------------------------+------------------------------------+
49 | aggiungi 1 (6) | |
50 +------------------------------------+------------------------------------+
51 | scrivi contatore (6) | |
52 +------------------------------------+------------------------------------+
53 | | leggi contatore (6) |
54 +------------------------------------+------------------------------------+
55 | | aggiungi 1 (7) |
56 +------------------------------------+------------------------------------+
57 | | scrivi contatore (7) |
58 +------------------------------------+------------------------------------+
59
60 Questo è quello che potrebbe succedere in realtà:
61
62 .. table:: Possibile risultato
63
64 +------------------------------------+------------------------------------+
65 | Istanza 1 | Istanza 2 |
66 +====================================+====================================+
67 | leggi contatore (5) | |
68 +------------------------------------+------------------------------------+
69 | | leggi contatore (5) |
70 +------------------------------------+------------------------------------+
71 | aggiungi 1 (6) | |
72 +------------------------------------+------------------------------------+
73 | | aggiungi 1 (6) |
74 +------------------------------------+------------------------------------+
75 | scrivi contatore (6) | |
76 +------------------------------------+------------------------------------+
77 | | scrivi contatore (6) |
78 +------------------------------------+------------------------------------+
79
80
81 Corse critiche e sezioni critiche
82 ---------------------------------
83
84 Questa sovrapposizione, ovvero quando un risultato dipende dal tempo che
85 intercorre fra processi diversi, è chiamata corsa critica. La porzione
86 di codice che contiene questo problema è chiamata sezione critica.
87 In particolar modo da quando Linux ha incominciato a girare su
88 macchine multi-processore, le sezioni critiche sono diventate uno dei
89 maggiori problemi di progettazione ed implementazione del kernel.
90
91 La prelazione può sortire gli stessi effetti, anche se c'è una sola CPU:
92 interrompendo un processo nella sua sezione critica otterremo comunque
93 la stessa corsa critica. In questo caso, il thread che si avvicenda
94 nell'esecuzione potrebbe eseguire anch'esso la sezione critica.
95
96 La soluzione è quella di riconoscere quando avvengono questi accessi
97 simultanei, ed utilizzare i *lock* per accertarsi che solo un'istanza
98 per volta possa entrare nella sezione critica. Il kernel offre delle buone
99 funzioni a questo scopo. E poi ci sono quelle meno buone, ma farò finta
100 che non esistano.
101
102 Sincronizzazione nel kernel Linux
103 =================================
104
105 Se dovessi darvi un suggerimento sulla sincronizzazione: **mantenetela
106 semplice**.
107
108 Siate riluttanti nell'introduzione di nuovi *lock*.
109
110 I due principali tipi di *lock* nel kernel: spinlock e mutex
111 ------------------------------------------------------------
112
113 Ci sono due tipi principali di *lock* nel kernel. Il tipo fondamentale è lo
114 spinlock (``include/asm/spinlock.h``), un semplice *lock* che può essere
115 trattenuto solo da un processo: se non si può trattenere lo spinlock, allora
116 rimane in attesa attiva (in inglese *spinning*) finché non ci riesce.
117 Gli spinlock sono molto piccoli e rapidi, possono essere utilizzati ovunque.
118
119 Il secondo tipo è il mutex (``include/linux/mutex.h``): è come uno spinlock,
120 ma potreste bloccarvi trattenendolo. Se non potete trattenere un mutex
121 il vostro processo si auto-sospenderà; verrà riattivato quando il mutex
122 verrà rilasciato. Questo significa che il processore potrà occuparsi d'altro
123 mentre il vostro processo è in attesa. Esistono molti casi in cui non potete
124 permettervi di sospendere un processo (vedere
125 `Quali funzioni possono essere chiamate in modo sicuro dalle interruzioni?`_)
126 e quindi dovrete utilizzare gli spinlock.
127
128 Nessuno di questi *lock* è ricorsivo: vedere
129 `Stallo: semplice ed avanzato`_
130
131 I *lock* e i kernel per sistemi monoprocessore
132 ----------------------------------------------
133
134 Per i kernel compilati senza ``CONFIG_SMP`` e senza ``CONFIG_PREEMPT``
135 gli spinlock non esistono. Questa è un'ottima scelta di progettazione:
136 quando nessun altro processo può essere eseguito in simultanea, allora
137 non c'è la necessità di avere un *lock*.
138
139 Se il kernel è compilato senza ``CONFIG_SMP`` ma con ``CONFIG_PREEMPT``,
140 allora gli spinlock disabilitano la prelazione; questo è sufficiente a
141 prevenire le corse critiche. Nella maggior parte dei casi, possiamo considerare
142 la prelazione equivalente ad un sistema multi-processore senza preoccuparci
143 di trattarla indipendentemente.
144
145 Dovreste verificare sempre la sincronizzazione con le opzioni ``CONFIG_SMP`` e
146 ``CONFIG_PREEMPT`` abilitate, anche quando non avete un sistema
147 multi-processore, questo vi permetterà di identificare alcuni problemi
148 di sincronizzazione.
149
150 Come vedremo di seguito, i mutex continuano ad esistere perché sono necessari
151 per la sincronizzazione fra processi in contesto utente.
152
153 Sincronizzazione in contesto utente
154 -----------------------------------
155
156 Se avete una struttura dati che verrà utilizzata solo dal contesto utente,
157 allora, per proteggerla, potete utilizzare un semplice mutex
158 (``include/linux/mutex.h``). Questo è il caso più semplice: inizializzate il
159 mutex; invocate mutex_lock_interruptible() per trattenerlo e
160 mutex_unlock() per rilasciarlo. C'è anche mutex_lock()
161 ma questa dovrebbe essere evitata perché non ritorna in caso di segnali.
162
163 Per esempio: ``net/netfilter/nf_sockopt.c`` permette la registrazione
164 di nuove chiamate per setsockopt() e getsockopt()
165 usando la funzione nf_register_sockopt(). La registrazione e
166 la rimozione vengono eseguite solamente quando il modulo viene caricato
167 o scaricato (e durante l'avvio del sistema, qui non abbiamo concorrenza),
168 e la lista delle funzioni registrate viene consultata solamente quando
169 setsockopt() o getsockopt() sono sconosciute al sistema.
170 In questo caso ``nf_sockopt_mutex`` è perfetto allo scopo, in particolar modo
171 visto che setsockopt e getsockopt potrebbero dormire.
172
173 Sincronizzazione fra il contesto utente e i softirq
174 ---------------------------------------------------
175
176 Se un softirq condivide dati col contesto utente, avete due problemi.
177 Primo, il contesto utente corrente potrebbe essere interroto da un softirq,
178 e secondo, la sezione critica potrebbe essere eseguita da un altro
179 processore. Questo è quando spin_lock_bh()
180 (``include/linux/spinlock.h``) viene utilizzato. Questo disabilita i softirq
181 sul processore e trattiene il *lock*. Invece, spin_unlock_bh() fa
182 l'opposto. (Il suffisso '_bh' è un residuo storico che fa riferimento al
183 "Bottom Halves", il vecchio nome delle interruzioni software. In un mondo
184 perfetto questa funzione si chiamerebbe 'spin_lock_softirq()').
185
186 Da notare che in questo caso potete utilizzare anche spin_lock_irq()
187 o spin_lock_irqsave(), queste fermano anche le interruzioni hardware:
188 vedere `Contesto di interruzione hardware`_.
189
190 Questo funziona alla perfezione anche sui sistemi monoprocessore: gli spinlock
191 svaniscono e questa macro diventa semplicemente local_bh_disable()
192 (``include/linux/interrupt.h``), la quale impedisce ai softirq d'essere
193 eseguiti.
194
195 Sincronizzazione fra contesto utente e i tasklet
196 ------------------------------------------------
197
198 Questo caso è uguale al precedente, un tasklet viene eseguito da un softirq.
199
200 Sincronizzazione fra contesto utente e i timer
201 ----------------------------------------------
202
203 Anche questo caso è uguale al precedente, un timer viene eseguito da un
204 softirq.
205 Dal punto di vista della sincronizzazione, tasklet e timer sono identici.
206
207 Sincronizzazione fra tasklet e timer
208 ------------------------------------
209
210 Qualche volta un tasklet od un timer potrebbero condividere i dati con
211 un altro tasklet o timer
212
213 Lo stesso tasklet/timer
214 ~~~~~~~~~~~~~~~~~~~~~~~
215
216 Dato che un tasklet non viene mai eseguito contemporaneamente su due
217 processori, non dovete preoccuparvi che sia rientrante (ovvero eseguito
218 più volte in contemporanea), perfino su sistemi multi-processore.
219
220 Differenti tasklet/timer
221 ~~~~~~~~~~~~~~~~~~~~~~~~
222
223 Se un altro tasklet/timer vuole condividere dati col vostro tasklet o timer,
224 allora avrete bisogno entrambe di spin_lock() e
225 spin_unlock(). Qui spin_lock_bh() è inutile, siete già
226 in un tasklet ed avete la garanzia che nessun altro verrà eseguito sullo
227 stesso processore.
228
229 Sincronizzazione fra softirq
230 ----------------------------
231
232 Spesso un softirq potrebbe condividere dati con se stesso o un tasklet/timer.
233
234 Lo stesso softirq
235 ~~~~~~~~~~~~~~~~~
236
237 Lo stesso softirq può essere eseguito su un diverso processore: allo scopo
238 di migliorare le prestazioni potete utilizzare dati riservati ad ogni
239 processore (vedere `Dati per processore`_). Se siete arrivati
240 fino a questo punto nell'uso dei softirq, probabilmente tenete alla scalabilità
241 delle prestazioni abbastanza da giustificarne la complessità aggiuntiva.
242
243 Dovete utilizzare spin_lock() e spin_unlock() per
244 proteggere i dati condivisi.
245
246 Diversi Softirqs
247 ~~~~~~~~~~~~~~~~
248
249 Dovete utilizzare spin_lock() e spin_unlock() per
250 proteggere i dati condivisi, che siano timer, tasklet, diversi softirq o
251 lo stesso o altri softirq: uno qualsiasi di essi potrebbe essere in esecuzione
252 su un diverso processore.
253
254 .. _`it_hardirq-context`:
255
256 Contesto di interruzione hardware
257 =================================
258
259 Solitamente le interruzioni hardware comunicano con un tasklet o un softirq.
260 Spesso questo si traduce nel mettere in coda qualcosa da fare che verrà
261 preso in carico da un softirq.
262
263 Sincronizzazione fra interruzioni hardware e softirq/tasklet
264 ------------------------------------------------------------
265
266 Se un gestore di interruzioni hardware condivide dati con un softirq, allora
267 avrete due preoccupazioni. Primo, il softirq può essere interrotto da
268 un'interruzione hardware, e secondo, la sezione critica potrebbe essere
269 eseguita da un'interruzione hardware su un processore diverso. Questo è il caso
270 dove spin_lock_irq() viene utilizzato. Disabilita le interruzioni
271 sul processore che l'esegue, poi trattiene il lock. spin_unlock_irq()
272 fa l'opposto.
273
274 Il gestore d'interruzione hardware non ha bisogno di usare spin_lock_irq()
275 perché i softirq non possono essere eseguiti quando il gestore d'interruzione
276 hardware è in esecuzione: per questo si può usare spin_lock(), che è un po'
277 più veloce. L'unica eccezione è quando un altro gestore d'interruzioni
278 hardware utilizza lo stesso *lock*: spin_lock_irq() impedirà a questo
279 secondo gestore di interrompere quello in esecuzione.
280
281 Questo funziona alla perfezione anche sui sistemi monoprocessore: gli spinlock
282 svaniscono e questa macro diventa semplicemente local_irq_disable()
283 (``include/asm/smp.h``), la quale impedisce a softirq/tasklet/BH d'essere
284 eseguiti.
285
286 spin_lock_irqsave() (``include/linux/spinlock.h``) è una variante che
287 salva lo stato delle interruzioni in una variabile, questa verrà poi passata
288 a spin_unlock_irqrestore(). Questo significa che lo stesso codice
289 potrà essere utilizzato in un'interruzione hardware (dove le interruzioni sono
290 già disabilitate) e in un softirq (dove la disabilitazione delle interruzioni
291 è richiesta).
292
293 Da notare che i softirq (e quindi tasklet e timer) sono eseguiti al ritorno
294 da un'interruzione hardware, quindi spin_lock_irq() interrompe
295 anche questi. Tenuto conto di questo si può dire che
296 spin_lock_irqsave() è la funzione di sincronizzazione più generica
297 e potente.
298
299 Sincronizzazione fra due gestori d'interruzioni hardware
300 --------------------------------------------------------
301
302 Condividere dati fra due gestori di interruzione hardware è molto raro, ma se
303 succede, dovreste usare spin_lock_irqsave(): è una specificità
304 dell'architettura il fatto che tutte le interruzioni vengano interrotte
305 quando si eseguono di gestori di interruzioni.
306
307 Bigino della sincronizzazione
308 =============================
309
310 Pete Zaitcev ci offre il seguente riassunto:
311
312 - Se siete in un contesto utente (una qualsiasi chiamata di sistema)
313 e volete sincronizzarvi con altri processi, usate i mutex. Potete trattenere
314 il mutex e dormire (``copy_from_user(`` o ``kmalloc(x,GFP_KERNEL)``).
315
316 - Altrimenti (== i dati possono essere manipolati da un'interruzione) usate
317 spin_lock_irqsave() e spin_unlock_irqrestore().
318
319 - Evitate di trattenere uno spinlock per più di 5 righe di codice incluse
320 le chiamate a funzione (ad eccezione di quell per l'accesso come
321 readb()).
322
323 Tabella dei requisiti minimi
324 ----------------------------
325
326 La tabella seguente illustra i requisiti **minimi** per la sincronizzazione fra
327 diversi contesti. In alcuni casi, lo stesso contesto può essere eseguito solo
328 da un processore per volta, quindi non ci sono requisiti per la
329 sincronizzazione (per esempio, un thread può essere eseguito solo su un
330 processore alla volta, ma se deve condividere dati con un altro thread, allora
331 la sincronizzazione è necessaria).
332
333 Ricordatevi il suggerimento qui sopra: potete sempre usare
334 spin_lock_irqsave(), che è un sovrainsieme di tutte le altre funzioni
335 per spinlock.
336
337 ============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ==============
338 . IRQ Handler A IRQ Handler B Softirq A Softirq B Tasklet A Tasklet B Timer A Timer B User Context A User Context B
339 ============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ==============
340 IRQ Handler A None
341 IRQ Handler B SLIS None
342 Softirq A SLI SLI SL
343 Softirq B SLI SLI SL SL
344 Tasklet A SLI SLI SL SL None
345 Tasklet B SLI SLI SL SL SL None
346 Timer A SLI SLI SL SL SL SL None
347 Timer B SLI SLI SL SL SL SL SL None
348 User Context A SLI SLI SLBH SLBH SLBH SLBH SLBH SLBH None
349 User Context B SLI SLI SLBH SLBH SLBH SLBH SLBH SLBH MLI None
350 ============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ==============
351
352 Table: Tabella dei requisiti per la sincronizzazione
353
354 +--------+----------------------------+
355 | SLIS | spin_lock_irqsave |
356 +--------+----------------------------+
357 | SLI | spin_lock_irq |
358 +--------+----------------------------+
359 | SL | spin_lock |
360 +--------+----------------------------+
361 | SLBH | spin_lock_bh |
362 +--------+----------------------------+
363 | MLI | mutex_lock_interruptible |
364 +--------+----------------------------+
365
366 Table: Legenda per la tabella dei requisiti per la sincronizzazione
367
368 Le funzioni *trylock*
369 =====================
370
371 Ci sono funzioni che provano a trattenere un *lock* solo una volta e
372 ritornano immediatamente comunicato il successo od il fallimento
373 dell'operazione. Posso essere usate quando non serve accedere ai dati
374 protetti dal *lock* quando qualche altro thread lo sta già facendo
375 trattenendo il *lock*. Potrete acquisire il *lock* più tardi se vi
376 serve accedere ai dati protetti da questo *lock*.
377
378 La funzione spin_trylock() non ritenta di acquisire il *lock*,
379 se ci riesce al primo colpo ritorna un valore diverso da zero, altrimenti
380 se fallisce ritorna 0. Questa funzione può essere utilizzata in un qualunque
381 contesto, ma come spin_lock(): dovete disabilitare i contesti che
382 potrebbero interrompervi e quindi trattenere lo spinlock.
383
384 La funzione mutex_trylock() invece di sospendere il vostro processo
385 ritorna un valore diverso da zero se è possibile trattenere il lock al primo
386 colpo, altrimenti se fallisce ritorna 0. Nonostante non dorma, questa funzione
387 non può essere usata in modo sicuro in contesti di interruzione hardware o
388 software.
389
390 Esempi più comuni
391 =================
392
393 Guardiamo un semplice esempio: una memoria che associa nomi a numeri.
394 La memoria tiene traccia di quanto spesso viene utilizzato ogni oggetto;
395 quando è piena, l'oggetto meno usato viene eliminato.
396
397 Tutto in contesto utente
398 ------------------------
399
400 Nel primo esempio, supponiamo che tutte le operazioni avvengano in contesto
401 utente (in soldoni, da una chiamata di sistema), quindi possiamo dormire.
402 Questo significa che possiamo usare i mutex per proteggere la nostra memoria
403 e tutti gli oggetti che contiene. Ecco il codice::
404
405 #include <linux/list.h>
406 #include <linux/slab.h>
407 #include <linux/string.h>
408 #include <linux/mutex.h>
409 #include <asm/errno.h>
410
411 struct object
412 {
413 struct list_head list;
414 int id;
415 char name[32];
416 int popularity;
417 };
418
419 /* Protects the cache, cache_num, and the objects within it */
420 static DEFINE_MUTEX(cache_lock);
421 static LIST_HEAD(cache);
422 static unsigned int cache_num = 0;
423 #define MAX_CACHE_SIZE 10
424
425 /* Must be holding cache_lock */
426 static struct object *__cache_find(int id)
427 {
428 struct object *i;
429
430 list_for_each_entry(i, &cache, list)
431 if (i->id == id) {
432 i->popularity++;
433 return i;
434 }
435 return NULL;
436 }
437
438 /* Must be holding cache_lock */
439 static void __cache_delete(struct object *obj)
440 {
441 BUG_ON(!obj);
442 list_del(&obj->list);
443 kfree(obj);
444 cache_num--;
445 }
446
447 /* Must be holding cache_lock */
448 static void __cache_add(struct object *obj)
449 {
450 list_add(&obj->list, &cache);
451 if (++cache_num > MAX_CACHE_SIZE) {
452 struct object *i, *outcast = NULL;
453 list_for_each_entry(i, &cache, list) {
454 if (!outcast || i->popularity < outcast->popularity)
455 outcast = i;
456 }
457 __cache_delete(outcast);
458 }
459 }
460
461 int cache_add(int id, const char *name)
462 {
463 struct object *obj;
464
465 if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
466 return -ENOMEM;
467
468 strscpy(obj->name, name, sizeof(obj->name));
469 obj->id = id;
470 obj->popularity = 0;
471
472 mutex_lock(&cache_lock);
473 __cache_add(obj);
474 mutex_unlock(&cache_lock);
475 return 0;
476 }
477
478 void cache_delete(int id)
479 {
480 mutex_lock(&cache_lock);
481 __cache_delete(__cache_find(id));
482 mutex_unlock(&cache_lock);
483 }
484
485 int cache_find(int id, char *name)
486 {
487 struct object *obj;
488 int ret = -ENOENT;
489
490 mutex_lock(&cache_lock);
491 obj = __cache_find(id);
492 if (obj) {
493 ret = 0;
494 strcpy(name, obj->name);
495 }
496 mutex_unlock(&cache_lock);
497 return ret;
498 }
499
500 Da notare che ci assicuriamo sempre di trattenere cache_lock quando
501 aggiungiamo, rimuoviamo od ispezioniamo la memoria: sia la struttura
502 della memoria che il suo contenuto sono protetti dal *lock*. Questo
503 caso è semplice dato che copiamo i dati dall'utente e non permettiamo
504 mai loro di accedere direttamente agli oggetti.
505
506 C'è una piccola ottimizzazione qui: nella funzione cache_add()
507 impostiamo i campi dell'oggetto prima di acquisire il *lock*. Questo è
508 sicuro perché nessun altro potrà accedervi finché non lo inseriremo
509 nella memoria.
510
511 Accesso dal contesto utente
512 ---------------------------
513
514 Ora consideriamo il caso in cui cache_find() può essere invocata
515 dal contesto d'interruzione: sia hardware che software. Un esempio potrebbe
516 essere un timer che elimina oggetti dalla memoria.
517
518 Qui di seguito troverete la modifica nel formato *patch*: le righe ``-``
519 sono quelle rimosse, mentre quelle ``+`` sono quelle aggiunte.
520
521 ::
522
523 --- cache.c.usercontext 2003-12-09 13:58:54.000000000 +1100
524 +++ cache.c.interrupt 2003-12-09 14:07:49.000000000 +1100
525 @@ -12,7 +12,7 @@
526 int popularity;
527 };
528
529 -static DEFINE_MUTEX(cache_lock);
530 +static DEFINE_SPINLOCK(cache_lock);
531 static LIST_HEAD(cache);
532 static unsigned int cache_num = 0;
533 #define MAX_CACHE_SIZE 10
534 @@ -55,6 +55,7 @@
535 int cache_add(int id, const char *name)
536 {
537 struct object *obj;
538 + unsigned long flags;
539
540 if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
541 return -ENOMEM;
542 @@ -63,30 +64,33 @@
543 obj->id = id;
544 obj->popularity = 0;
545
546 - mutex_lock(&cache_lock);
547 + spin_lock_irqsave(&cache_lock, flags);
548 __cache_add(obj);
549 - mutex_unlock(&cache_lock);
550 + spin_unlock_irqrestore(&cache_lock, flags);
551 return 0;
552 }
553
554 void cache_delete(int id)
555 {
556 - mutex_lock(&cache_lock);
557 + unsigned long flags;
558 +
559 + spin_lock_irqsave(&cache_lock, flags);
560 __cache_delete(__cache_find(id));
561 - mutex_unlock(&cache_lock);
562 + spin_unlock_irqrestore(&cache_lock, flags);
563 }
564
565 int cache_find(int id, char *name)
566 {
567 struct object *obj;
568 int ret = -ENOENT;
569 + unsigned long flags;
570
571 - mutex_lock(&cache_lock);
572 + spin_lock_irqsave(&cache_lock, flags);
573 obj = __cache_find(id);
574 if (obj) {
575 ret = 0;
576 strcpy(name, obj->name);
577 }
578 - mutex_unlock(&cache_lock);
579 + spin_unlock_irqrestore(&cache_lock, flags);
580 return ret;
581 }
582
583 Da notare che spin_lock_irqsave() disabiliterà le interruzioni
584 se erano attive, altrimenti non farà niente (quando siamo già in un contesto
585 d'interruzione); dunque queste funzioni possono essere chiamante in
586 sicurezza da qualsiasi contesto.
587
588 Sfortunatamente, cache_add() invoca kmalloc() con
589 l'opzione ``GFP_KERNEL`` che è permessa solo in contesto utente. Ho supposto
590 che cache_add() venga chiamata dal contesto utente, altrimenti
591 questa opzione deve diventare un parametro di cache_add().
592
593 Esporre gli oggetti al di fuori del file
594 ----------------------------------------
595
596 Se i vostri oggetti contengono più informazioni, potrebbe non essere
597 sufficiente copiare i dati avanti e indietro: per esempio, altre parti del
598 codice potrebbero avere un puntatore a questi oggetti piuttosto che cercarli
599 ogni volta. Questo introduce due problemi.
600
601 Il primo problema è che utilizziamo ``cache_lock`` per proteggere gli oggetti:
602 dobbiamo renderlo dinamico così che il resto del codice possa usarlo. Questo
603 rende la sincronizzazione più complicata dato che non avviene più in un unico
604 posto.
605
606 Il secondo problema è il problema del ciclo di vita: se un'altra struttura
607 mantiene un puntatore ad un oggetto, presumibilmente si aspetta che questo
608 puntatore rimanga valido. Sfortunatamente, questo è garantito solo mentre
609 si trattiene il *lock*, altrimenti qualcuno potrebbe chiamare
610 cache_delete() o peggio, aggiungere un oggetto che riutilizza lo
611 stesso indirizzo.
612
613 Dato che c'è un solo *lock*, non potete trattenerlo a vita: altrimenti
614 nessun altro potrà eseguire il proprio lavoro.
615
616 La soluzione a questo problema è l'uso di un contatore di riferimenti:
617 chiunque punti ad un oggetto deve incrementare il contatore, e decrementarlo
618 quando il puntatore non viene più usato. Quando il contatore raggiunge lo zero
619 significa che non è più usato e l'oggetto può essere rimosso.
620
621 Ecco il codice::
622
623 --- cache.c.interrupt 2003-12-09 14:25:43.000000000 +1100
624 +++ cache.c.refcnt 2003-12-09 14:33:05.000000000 +1100
625 @@ -7,6 +7,7 @@
626 struct object
627 {
628 struct list_head list;
629 + unsigned int refcnt;
630 int id;
631 char name[32];
632 int popularity;
633 @@ -17,6 +18,35 @@
634 static unsigned int cache_num = 0;
635 #define MAX_CACHE_SIZE 10
636
637 +static void __object_put(struct object *obj)
638 +{
639 + if (--obj->refcnt == 0)
640 + kfree(obj);
641 +}
642 +
643 +static void __object_get(struct object *obj)
644 +{
645 + obj->refcnt++;
646 +}
647 +
648 +void object_put(struct object *obj)
649 +{
650 + unsigned long flags;
651 +
652 + spin_lock_irqsave(&cache_lock, flags);
653 + __object_put(obj);
654 + spin_unlock_irqrestore(&cache_lock, flags);
655 +}
656 +
657 +void object_get(struct object *obj)
658 +{
659 + unsigned long flags;
660 +
661 + spin_lock_irqsave(&cache_lock, flags);
662 + __object_get(obj);
663 + spin_unlock_irqrestore(&cache_lock, flags);
664 +}
665 +
666 /* Must be holding cache_lock */
667 static struct object *__cache_find(int id)
668 {
669 @@ -35,6 +65,7 @@
670 {
671 BUG_ON(!obj);
672 list_del(&obj->list);
673 + __object_put(obj);
674 cache_num--;
675 }
676
677 @@ -63,6 +94,7 @@
678 strscpy(obj->name, name, sizeof(obj->name));
679 obj->id = id;
680 obj->popularity = 0;
681 + obj->refcnt = 1; /* The cache holds a reference */
682
683 spin_lock_irqsave(&cache_lock, flags);
684 __cache_add(obj);
685 @@ -79,18 +111,15 @@
686 spin_unlock_irqrestore(&cache_lock, flags);
687 }
688
689 -int cache_find(int id, char *name)
690 +struct object *cache_find(int id)
691 {
692 struct object *obj;
693 - int ret = -ENOENT;
694 unsigned long flags;
695
696 spin_lock_irqsave(&cache_lock, flags);
697 obj = __cache_find(id);
698 - if (obj) {
699 - ret = 0;
700 - strcpy(name, obj->name);
701 - }
702 + if (obj)
703 + __object_get(obj);
704 spin_unlock_irqrestore(&cache_lock, flags);
705 - return ret;
706 + return obj;
707 }
708
709 Abbiamo incapsulato il contatore di riferimenti nelle tipiche funzioni
710 di 'get' e 'put'. Ora possiamo ritornare l'oggetto da cache_find()
711 col vantaggio che l'utente può dormire trattenendo l'oggetto (per esempio,
712 copy_to_user() per copiare il nome verso lo spazio utente).
713
714 Un altro punto da notare è che ho detto che il contatore dovrebbe incrementarsi
715 per ogni puntatore ad un oggetto: quindi il contatore di riferimenti è 1
716 quando l'oggetto viene inserito nella memoria. In altre versione il framework
717 non trattiene un riferimento per se, ma diventa più complicato.
718
719 Usare operazioni atomiche per il contatore di riferimenti
720 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
721
722 In sostanza, :c:type:`atomic_t` viene usato come contatore di riferimenti.
723 Ci sono un certo numbero di operazioni atomiche definite
724 in ``include/asm/atomic.h``: queste sono garantite come atomiche su qualsiasi
725 processore del sistema, quindi non sono necessari i *lock*. In questo caso è
726 più semplice rispetto all'uso degli spinlock, benché l'uso degli spinlock
727 sia più elegante per casi non banali. Le funzioni atomic_inc() e
728 atomic_dec_and_test() vengono usate al posto dei tipici operatori di
729 incremento e decremento, e i *lock* non sono più necessari per proteggere il
730 contatore stesso.
731
732 ::
733
734 --- cache.c.refcnt 2003-12-09 15:00:35.000000000 +1100
735 +++ cache.c.refcnt-atomic 2003-12-11 15:49:42.000000000 +1100
736 @@ -7,7 +7,7 @@
737 struct object
738 {
739 struct list_head list;
740 - unsigned int refcnt;
741 + atomic_t refcnt;
742 int id;
743 char name[32];
744 int popularity;
745 @@ -18,33 +18,15 @@
746 static unsigned int cache_num = 0;
747 #define MAX_CACHE_SIZE 10
748
749 -static void __object_put(struct object *obj)
750 -{
751 - if (--obj->refcnt == 0)
752 - kfree(obj);
753 -}
754 -
755 -static void __object_get(struct object *obj)
756 -{
757 - obj->refcnt++;
758 -}
759 -
760 void object_put(struct object *obj)
761 {
762 - unsigned long flags;
763 -
764 - spin_lock_irqsave(&cache_lock, flags);
765 - __object_put(obj);
766 - spin_unlock_irqrestore(&cache_lock, flags);
767 + if (atomic_dec_and_test(&obj->refcnt))
768 + kfree(obj);
769 }
770
771 void object_get(struct object *obj)
772 {
773 - unsigned long flags;
774 -
775 - spin_lock_irqsave(&cache_lock, flags);
776 - __object_get(obj);
777 - spin_unlock_irqrestore(&cache_lock, flags);
778 + atomic_inc(&obj->refcnt);
779 }
780
781 /* Must be holding cache_lock */
782 @@ -65,7 +47,7 @@
783 {
784 BUG_ON(!obj);
785 list_del(&obj->list);
786 - __object_put(obj);
787 + object_put(obj);
788 cache_num--;
789 }
790
791 @@ -94,7 +76,7 @@
792 strscpy(obj->name, name, sizeof(obj->name));
793 obj->id = id;
794 obj->popularity = 0;
795 - obj->refcnt = 1; /* The cache holds a reference */
796 + atomic_set(&obj->refcnt, 1); /* The cache holds a reference */
797
798 spin_lock_irqsave(&cache_lock, flags);
799 __cache_add(obj);
800 @@ -119,7 +101,7 @@
801 spin_lock_irqsave(&cache_lock, flags);
802 obj = __cache_find(id);
803 if (obj)
804 - __object_get(obj);
805 + object_get(obj);
806 spin_unlock_irqrestore(&cache_lock, flags);
807 return obj;
808 }
809
810 Proteggere l'oggetto stesso
811 ---------------------------
812
813 In questo esempio, assumiamo che gli oggetti (ad eccezione del contatore
814 di riferimenti) non cambino mai dopo la loro creazione. Se vogliamo permettere
815 al nome di cambiare abbiamo tre possibilità:
816
817 - Si può togliere static da ``cache_lock`` e dire agli utenti che devono
818 trattenere il *lock* prima di modificare il nome di un oggetto.
819
820 - Si può fornire una funzione cache_obj_rename() che prende il
821 *lock* e cambia il nome per conto del chiamante; si dirà poi agli utenti
822 di usare questa funzione.
823
824 - Si può decidere che ``cache_lock`` protegge solo la memoria stessa, ed
825 un altro *lock* è necessario per la protezione del nome.
826
827 Teoricamente, possiamo avere un *lock* per ogni campo e per ogni oggetto.
828 In pratica, le varianti più comuni sono:
829
830 - un *lock* che protegge l'infrastruttura (la lista ``cache`` di questo
831 esempio) e gli oggetti. Questo è quello che abbiamo fatto finora.
832
833 - un *lock* che protegge l'infrastruttura (inclusi i puntatori alla lista
834 negli oggetti), e un *lock* nell'oggetto per proteggere il resto
835 dell'oggetto stesso.
836
837 - *lock* multipli per proteggere l'infrastruttura (per esempio un *lock*
838 per ogni lista), possibilmente con un *lock* per oggetto.
839
840 Qui di seguito un'implementazione con "un lock per oggetto":
841
842 ::
843
844 --- cache.c.refcnt-atomic 2003-12-11 15:50:54.000000000 +1100
845 +++ cache.c.perobjectlock 2003-12-11 17:15:03.000000000 +1100
846 @@ -6,11 +6,17 @@
847
848 struct object
849 {
850 + /* These two protected by cache_lock. */
851 struct list_head list;
852 + int popularity;
853 +
854 atomic_t refcnt;
855 +
856 + /* Doesn't change once created. */
857 int id;
858 +
859 + spinlock_t lock; /* Protects the name */
860 char name[32];
861 - int popularity;
862 };
863
864 static DEFINE_SPINLOCK(cache_lock);
865 @@ -77,6 +84,7 @@
866 obj->id = id;
867 obj->popularity = 0;
868 atomic_set(&obj->refcnt, 1); /* The cache holds a reference */
869 + spin_lock_init(&obj->lock);
870
871 spin_lock_irqsave(&cache_lock, flags);
872 __cache_add(obj);
873
874 Da notare che ho deciso che il contatore di popolarità dovesse essere
875 protetto da ``cache_lock`` piuttosto che dal *lock* dell'oggetto; questo
876 perché è logicamente parte dell'infrastruttura (come
877 :c:type:`struct list_head <list_head>` nell'oggetto). In questo modo,
878 in __cache_add(), non ho bisogno di trattenere il *lock* di ogni
879 oggetto mentre si cerca il meno popolare.
880
881 Ho anche deciso che il campo id è immutabile, quindi non ho bisogno di
882 trattenere il lock dell'oggetto quando si usa __cache_find()
883 per leggere questo campo; il *lock* dell'oggetto è usato solo dal chiamante
884 che vuole leggere o scrivere il campo name.
885
886 Inoltre, da notare che ho aggiunto un commento che descrive i dati che sono
887 protetti dal *lock*. Questo è estremamente importante in quanto descrive il
888 comportamento del codice, che altrimenti sarebbe di difficile comprensione
889 leggendo solamente il codice. E come dice Alan Cox: “Lock data, not code”.
890
891 Problemi comuni
892 ===============
893
894 Stallo: semplice ed avanzato
895 ----------------------------
896
897 Esiste un tipo di baco dove un pezzo di codice tenta di trattenere uno
898 spinlock due volte: questo rimarrà in attesa attiva per sempre aspettando che
899 il *lock* venga rilasciato (in Linux spinlocks, rwlocks e mutex non sono
900 ricorsivi).
901 Questo è facile da diagnosticare: non è uno di quei problemi che ti tengono
902 sveglio 5 notti a parlare da solo.
903
904 Un caso un pochino più complesso; immaginate d'avere una spazio condiviso
905 fra un softirq ed il contesto utente. Se usate spin_lock() per
906 proteggerlo, il contesto utente potrebbe essere interrotto da un softirq
907 mentre trattiene il lock, da qui il softirq rimarrà in attesa attiva provando
908 ad acquisire il *lock* già trattenuto nel contesto utente.
909
910 Questi casi sono chiamati stalli (*deadlock*), e come mostrato qui sopra,
911 può succedere anche con un solo processore (Ma non sui sistemi
912 monoprocessore perché gli spinlock spariscano quando il kernel è compilato
913 con ``CONFIG_SMP``\ =n. Nonostante ciò, nel secondo caso avrete comunque
914 una corruzione dei dati).
915
916 Questi casi sono facili da diagnosticare; sui sistemi multi-processore
917 il supervisione (*watchdog*) o l'opzione di compilazione ``DEBUG_SPINLOCK``
918 (``include/linux/spinlock.h``) permettono di scovare immediatamente quando
919 succedono.
920
921 Esiste un caso più complesso che è conosciuto come l'abbraccio della morte;
922 questo coinvolge due o più *lock*. Diciamo che avete un vettore di hash in cui
923 ogni elemento è uno spinlock a cui è associata una lista di elementi con lo
924 stesso hash. In un gestore di interruzioni software, dovete modificare un
925 oggetto e spostarlo su un altro hash; quindi dovrete trattenete lo spinlock
926 del vecchio hash e di quello nuovo, quindi rimuovere l'oggetto dal vecchio ed
927 inserirlo nel nuovo.
928
929 Qui abbiamo due problemi. Primo, se il vostro codice prova a spostare un
930 oggetto all'interno della stessa lista, otterrete uno stallo visto che
931 tenterà di trattenere lo stesso *lock* due volte. Secondo, se la stessa
932 interruzione software su un altro processore sta tentando di spostare
933 un altro oggetto nella direzione opposta, potrebbe accadere quanto segue:
934
935 +---------------------------------+---------------------------------+
936 | CPU 1 | CPU 2 |
937 +=================================+=================================+
938 | Trattiene *lock* A -> OK | Trattiene *lock* B -> OK |
939 +---------------------------------+---------------------------------+
940 | Trattiene *lock* B -> attesa | Trattiene *lock* A -> attesa |
941 +---------------------------------+---------------------------------+
942
943 Table: Conseguenze
944
945 Entrambe i processori rimarranno in attesa attiva sul *lock* per sempre,
946 aspettando che l'altro lo rilasci. Sembra e puzza come un blocco totale.
947
948 Prevenire gli stalli
949 --------------------
950
951 I libri di testo vi diranno che se trattenete i *lock* sempre nello stesso
952 ordine non avrete mai un simile stallo. La pratica vi dirà che questo
953 approccio non funziona all'ingrandirsi del sistema: quando creo un nuovo
954 *lock* non ne capisco abbastanza del kernel per dire in quale dei 5000 *lock*
955 si incastrerà.
956
957 I *lock* migliori sono quelli incapsulati: non vengono esposti nei file di
958 intestazione, e non vengono mai trattenuti fuori dallo stesso file. Potete
959 rileggere questo codice e vedere che non ci sarà mai uno stallo perché
960 non tenterà mai di trattenere un altro *lock* quando lo ha già.
961 Le persone che usano il vostro codice non devono nemmeno sapere che voi
962 state usando dei *lock*.
963
964 Un classico problema deriva dall'uso di *callback* e di *hook*: se li
965 chiamate mentre trattenete un *lock*, rischiate uno stallo o un abbraccio
966 della morte (chi lo sa cosa farà una *callback*?).
967
968 Ossessiva prevenzione degli stalli
969 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
970
971 Gli stalli sono un problema, ma non così terribile come la corruzione dei dati.
972 Un pezzo di codice trattiene un *lock* di lettura, cerca in una lista,
973 fallisce nel trovare quello che vuole, quindi rilascia il *lock* di lettura,
974 trattiene un *lock* di scrittura ed inserisce un oggetto; questo genere di
975 codice presenta una corsa critica.
976
977 corsa fra temporizzatori: un passatempo del kernel
978 --------------------------------------------------
979
980 I temporizzatori potrebbero avere dei problemi con le corse critiche.
981 Considerate una collezione di oggetti (liste, hash, eccetera) dove ogni oggetto
982 ha un temporizzatore che sta per distruggerlo.
983
984 Se volete eliminare l'intera collezione (diciamo quando rimuovete un modulo),
985 potreste fare come segue::
986
987 /* THIS CODE BAD BAD BAD BAD: IF IT WAS ANY WORSE IT WOULD USE
988 HUNGARIAN NOTATION */
989 spin_lock_bh(&list_lock);
990
991 while (list) {
992 struct foo *next = list->next;
993 timer_delete(&list->timer);
994 kfree(list);
995 list = next;
996 }
997
998 spin_unlock_bh(&list_lock);
999
1000 Primo o poi, questo esploderà su un sistema multiprocessore perché un
1001 temporizzatore potrebbe essere già partiro prima di spin_lock_bh(),
1002 e prenderà il *lock* solo dopo spin_unlock_bh(), e cercherà
1003 di eliminare il suo oggetto (che però è già stato eliminato).
1005 Questo può essere evitato controllando il valore di ritorno di
1006 timer_delete(): se ritorna 1, il temporizzatore è stato già
1007 rimosso. Se 0, significa (in questo caso) che il temporizzatore è in
1008 esecuzione, quindi possiamo fare come segue::
1010 retry:
1011 spin_lock_bh(&list_lock);
1013 while (list) {
1014 struct foo *next = list->next;
1015 if (!timer_delete(&list->timer)) {
1016 /* Give timer a chance to delete this */
1017 spin_unlock_bh(&list_lock);
1018 goto retry;
1019 }
1020 kfree(list);
1021 list = next;
1022 }
1024 spin_unlock_bh(&list_lock);
1026 Un altro problema è l'eliminazione dei temporizzatori che si riavviano
1027 da soli (chiamando add_timer() alla fine della loro esecuzione).
1028 Dato che questo è un problema abbastanza comune con una propensione
1029 alle corse critiche, dovreste usare timer_delete_sync()
1030 (``include/linux/timer.h``) per gestire questo caso.
1032 Prima di rilasciare un temporizzatore dovreste chiamare la funzione
1033 timer_shutdown() o timer_shutdown_sync() di modo che non venga più riarmato.
1034 Ogni successivo tentativo di riarmare il temporizzatore verrà silenziosamente
1035 ignorato.
1037 Velocità della sincronizzazione
1038 ===============================
1040 Ci sono tre cose importanti da tenere in considerazione quando si valuta
1041 la velocità d'esecuzione di un pezzo di codice che necessita di
1042 sincronizzazione. La prima è la concorrenza: quante cose rimangono in attesa
1043 mentre qualcuno trattiene un *lock*. La seconda è il tempo necessario per
1044 acquisire (senza contese) e rilasciare un *lock*. La terza è di usare meno
1045 *lock* o di più furbi. Immagino che i *lock* vengano usati regolarmente,
1046 altrimenti, non sareste interessati all'efficienza.
1048 La concorrenza dipende da quanto a lungo un *lock* è trattenuto: dovreste
1049 trattenere un *lock* solo il tempo minimo necessario ma non un istante in più.
1050 Nella memoria dell'esempio precedente, creiamo gli oggetti senza trattenere
1051 il *lock*, poi acquisiamo il *lock* quando siamo pronti per inserirlo nella
1052 lista.
1054 Il tempo di acquisizione di un *lock* dipende da quanto danno fa
1055 l'operazione sulla *pipeline* (ovvero stalli della *pipeline*) e quant'è
1056 probabile che il processore corrente sia stato anche l'ultimo ad acquisire
1057 il *lock* (in pratica, il *lock* è nella memoria cache del processore
1058 corrente?): su sistemi multi-processore questa probabilità precipita
1059 rapidamente. Consideriamo un processore Intel Pentium III a 700Mhz: questo
1060 esegue un'istruzione in 0.7ns, un incremento atomico richiede 58ns, acquisire
1061 un *lock* che è nella memoria cache del processore richiede 160ns, e un
1062 trasferimento dalla memoria cache di un altro processore richiede altri
1063 170/360ns (Leggetevi l'articolo di Paul McKenney's `Linux Journal RCU
1064 article <http://www.linuxjournal.com/article.php?sid=6993>`__).
1066 Questi due obiettivi sono in conflitto: trattenere un *lock* per il minor
1067 tempo possibile potrebbe richiedere la divisione in più *lock* per diverse
1068 parti (come nel nostro ultimo esempio con un *lock* per ogni oggetto),
1069 ma questo aumenta il numero di acquisizioni di *lock*, ed il risultato
1070 spesso è che tutto è più lento che con un singolo *lock*. Questo è un altro
1071 argomento in favore della semplicità quando si parla di sincronizzazione.
1073 Il terzo punto è discusso di seguito: ci sono alcune tecniche per ridurre
1074 il numero di sincronizzazioni che devono essere fatte.
1076 Read/Write Lock Variants
1077 ------------------------
1079 Sia gli spinlock che i mutex hanno una variante per la lettura/scrittura
1080 (read/write): ``rwlock_t`` e :c:type:`struct rw_semaphore <rw_semaphore>`.
1081 Queste dividono gli utenti in due categorie: i lettori e gli scrittori.
1082 Se state solo leggendo i dati, potete acquisire il *lock* di lettura, ma
1083 per scrivere avrete bisogno del *lock* di scrittura. Molti possono trattenere
1084 il *lock* di lettura, ma solo uno scrittore alla volta può trattenere
1085 quello di scrittura.
1087 Se il vostro codice si divide chiaramente in codice per lettori e codice
1088 per scrittori (come nel nostro esempio), e il *lock* dei lettori viene
1089 trattenuto per molto tempo, allora l'uso di questo tipo di *lock* può aiutare.
1090 Questi sono leggermente più lenti rispetto alla loro versione normale, quindi
1091 nella pratica l'uso di ``rwlock_t`` non ne vale la pena.
1093 Evitare i *lock*: Read Copy Update
1094 --------------------------------------------
1096 Esiste un metodo di sincronizzazione per letture e scritture detto
1097 Read Copy Update. Con l'uso della tecnica RCU, i lettori possono scordarsi
1098 completamente di trattenere i *lock*; dato che nel nostro esempio ci
1099 aspettiamo d'avere più lettore che scrittori (altrimenti questa memoria
1100 sarebbe uno spreco) possiamo dire che questo meccanismo permette
1101 un'ottimizzazione.
1103 Come facciamo a sbarazzarci dei *lock* di lettura? Sbarazzarsi dei *lock* di
1104 lettura significa che uno scrittore potrebbe cambiare la lista sotto al naso
1105 dei lettori. Questo è abbastanza semplice: possiamo leggere una lista
1106 concatenata se lo scrittore aggiunge elementi alla fine e con certe
1107 precauzioni. Per esempio, aggiungendo ``new`` ad una lista concatenata
1108 chiamata ``list``::
1110 new->next = list->next;
1111 wmb();
1112 list->next = new;
1114 La funzione wmb() è una barriera di sincronizzazione delle
1115 scritture. Questa garantisce che la prima operazione (impostare l'elemento
1116 ``next`` del nuovo elemento) venga completata e vista da tutti i processori
1117 prima che venga eseguita la seconda operazione (che sarebbe quella di mettere
1118 il nuovo elemento nella lista). Questo è importante perché i moderni
1119 compilatori ed i moderni processori possono, entrambe, riordinare le istruzioni
1120 se non vengono istruiti altrimenti: vogliamo che i lettori non vedano
1121 completamente il nuovo elemento; oppure che lo vedano correttamente e quindi
1122 il puntatore ``next`` deve puntare al resto della lista.
1124 Fortunatamente, c'è una funzione che fa questa operazione sulle liste
1125 :c:type:`struct list_head <list_head>`: list_add_rcu()
1126 (``include/linux/list.h``).
1128 Rimuovere un elemento dalla lista è anche più facile: sostituiamo il puntatore
1129 al vecchio elemento con quello del suo successore, e i lettori vedranno
1130 l'elemento o lo salteranno.
1132 ::
1134 list->next = old->next;
1136 La funzione list_del_rcu() (``include/linux/list.h``) fa esattamente
1137 questo (la versione normale corrompe il vecchio oggetto, e non vogliamo che
1138 accada).
1140 Anche i lettori devono stare attenti: alcuni processori potrebbero leggere
1141 attraverso il puntatore ``next`` il contenuto dell'elemento successivo
1142 troppo presto, ma non accorgersi che il contenuto caricato è sbagliato quando
1143 il puntatore ``next`` viene modificato alla loro spalle. Ancora una volta
1144 c'è una funzione che viene in vostro aiuto list_for_each_entry_rcu()
1145 (``include/linux/list.h``). Ovviamente, gli scrittori possono usare
1146 list_for_each_entry() dato che non ci possono essere due scrittori
1147 in contemporanea.
1149 Il nostro ultimo dilemma è il seguente: quando possiamo realmente distruggere
1150 l'elemento rimosso? Ricordate, un lettore potrebbe aver avuto accesso a questo
1151 elemento proprio ora: se eliminiamo questo elemento ed il puntatore ``next``
1152 cambia, il lettore salterà direttamente nella spazzatura e scoppierà. Dobbiamo
1153 aspettare finché tutti i lettori che stanno attraversando la lista abbiano
1154 finito. Utilizziamo call_rcu() per registrare una funzione di
1155 richiamo che distrugga l'oggetto quando tutti i lettori correnti hanno
1156 terminato. In alternative, potrebbe essere usata la funzione
1157 synchronize_rcu() che blocca l'esecuzione finché tutti i lettori
1158 non terminano di ispezionare la lista.
1160 Ma come fa l'RCU a sapere quando i lettori sono finiti? Il meccanismo è
1161 il seguente: innanzi tutto i lettori accedono alla lista solo fra la coppia
1162 rcu_read_lock()/rcu_read_unlock() che disabilita la
1163 prelazione così che i lettori non vengano sospesi mentre stanno leggendo
1164 la lista.
1166 Poi, l'RCU aspetta finché tutti i processori non abbiano dormito almeno
1167 una volta; a questo punto, dato che i lettori non possono dormire, possiamo
1168 dedurre che un qualsiasi lettore che abbia consultato la lista durante la
1169 rimozione abbia già terminato, quindi la *callback* viene eseguita. Il vero
1170 codice RCU è un po' più ottimizzato di così, ma questa è l'idea di fondo.
1172 ::
1174 --- cache.c.perobjectlock 2003-12-11 17:15:03.000000000 +1100
1175 +++ cache.c.rcupdate 2003-12-11 17:55:14.000000000 +1100
1176 @@ -1,15 +1,18 @@
1177 #include <linux/list.h>
1178 #include <linux/slab.h>
1179 #include <linux/string.h>
1180 +#include <linux/rcupdate.h>
1181 #include <linux/mutex.h>
1182 #include <asm/errno.h>
1184 struct object
1185 {
1186 - /* These two protected by cache_lock. */
1187 + /* This is protected by RCU */
1188 struct list_head list;
1189 int popularity;
1191 + struct rcu_head rcu;
1192 +
1193 atomic_t refcnt;
1195 /* Doesn't change once created. */
1196 @@ -40,7 +43,7 @@
1197 {
1198 struct object *i;
1200 - list_for_each_entry(i, &cache, list) {
1201 + list_for_each_entry_rcu(i, &cache, list) {
1202 if (i->id == id) {
1203 i->popularity++;
1204 return i;
1205 @@ -49,19 +52,25 @@
1206 return NULL;
1207 }
1209 +/* Final discard done once we know no readers are looking. */
1210 +static void cache_delete_rcu(void *arg)
1211 +{
1212 + object_put(arg);
1213 +}
1214 +
1215 /* Must be holding cache_lock */
1216 static void __cache_delete(struct object *obj)
1217 {
1218 BUG_ON(!obj);
1219 - list_del(&obj->list);
1220 - object_put(obj);
1221 + list_del_rcu(&obj->list);
1222 cache_num--;
1223 + call_rcu(&obj->rcu, cache_delete_rcu);
1224 }
1226 /* Must be holding cache_lock */
1227 static void __cache_add(struct object *obj)
1228 {
1229 - list_add(&obj->list, &cache);
1230 + list_add_rcu(&obj->list, &cache);
1231 if (++cache_num > MAX_CACHE_SIZE) {
1232 struct object *i, *outcast = NULL;
1233 list_for_each_entry(i, &cache, list) {
1234 @@ -104,12 +114,11 @@
1235 struct object *cache_find(int id)
1236 {
1237 struct object *obj;
1238 - unsigned long flags;
1240 - spin_lock_irqsave(&cache_lock, flags);
1241 + rcu_read_lock();
1242 obj = __cache_find(id);
1243 if (obj)
1244 object_get(obj);
1245 - spin_unlock_irqrestore(&cache_lock, flags);
1246 + rcu_read_unlock();
1247 return obj;
1248 }
1250 Da notare che i lettori modificano il campo popularity nella funzione
1251 __cache_find(), e ora non trattiene alcun *lock*. Una soluzione
1252 potrebbe essere quella di rendere la variabile ``atomic_t``, ma per l'uso
1253 che ne abbiamo fatto qui, non ci interessano queste corse critiche perché un
1254 risultato approssimativo è comunque accettabile, quindi non l'ho cambiato.
1256 Il risultato è che la funzione cache_find() non ha bisogno di alcuna
1257 sincronizzazione con le altre funzioni, quindi è veloce su un sistema
1258 multi-processore tanto quanto lo sarebbe su un sistema mono-processore.
1260 Esiste un'ulteriore ottimizzazione possibile: vi ricordate il codice originale
1261 della nostra memoria dove non c'erano contatori di riferimenti e il chiamante
1262 semplicemente tratteneva il *lock* prima di accedere ad un oggetto? Questo è
1263 ancora possibile: se trattenete un *lock* nessuno potrà cancellare l'oggetto,
1264 quindi non avete bisogno di incrementare e decrementare il contatore di
1265 riferimenti.
1267 Ora, dato che il '*lock* di lettura' di un RCU non fa altro che disabilitare
1268 la prelazione, un chiamante che ha sempre la prelazione disabilitata fra le
1269 chiamate cache_find() e object_put() non necessita
1270 di incrementare e decrementare il contatore di riferimenti. Potremmo
1271 esporre la funzione __cache_find() dichiarandola non-static,
1272 e quel chiamante potrebbe usare direttamente questa funzione.
1274 Il beneficio qui sta nel fatto che il contatore di riferimenti no
1275 viene scritto: l'oggetto non viene alterato in alcun modo e quindi diventa
1276 molto più veloce su sistemi molti-processore grazie alla loro memoria cache.
1279 Dati per processore
1280 -------------------
1282 Un'altra tecnica comunemente usata per evitare la sincronizzazione è quella
1283 di duplicare le informazioni per ogni processore. Per esempio, se volete
1284 avere un contatore di qualcosa, potreste utilizzare uno spinlock ed un
1285 singolo contatore. Facile e pulito.
1287 Se questo dovesse essere troppo lento (solitamente non lo è, ma se avete
1288 dimostrato che lo è devvero), potreste usare un contatore per ogni processore
1289 e quindi non sarebbe più necessaria la mutua esclusione. Vedere
1290 DEFINE_PER_CPU(), get_cpu_var() e put_cpu_var()
1291 (``include/linux/percpu.h``).
1293 Il tipo di dato ``local_t``, la funzione cpu_local_inc() e tutte
1294 le altre funzioni associate, sono di particolare utilità per semplici contatori
1295 per-processore; su alcune architetture sono anche più efficienti
1296 (``include/asm/local.h``).
1298 Da notare che non esiste un modo facile ed affidabile per ottenere il valore
1299 di un simile contatore senza introdurre altri *lock*. In alcuni casi questo
1300 non è un problema.
1302 Dati che sono usati prevalentemente dai gestori d'interruzioni
1303 --------------------------------------------------------------
1305 Se i dati vengono utilizzati sempre dallo stesso gestore d'interruzioni,
1306 allora i *lock* non vi servono per niente: il kernel già vi garantisce che
1307 il gestore d'interruzione non verrà eseguito in contemporanea su diversi
1308 processori.
1310 Manfred Spraul fa notare che potreste comunque comportarvi così anche
1311 se i dati vengono occasionalmente utilizzati da un contesto utente o
1312 da un'interruzione software. Il gestore d'interruzione non utilizza alcun
1313 *lock*, e tutti gli altri accessi verranno fatti così::
1315 mutex_lock(&lock);
1316 disable_irq(irq);
1317 ...
1318 enable_irq(irq);
1319 mutex_unlock(&lock);
1321 La funzione disable_irq() impedisce al gestore d'interruzioni
1322 d'essere eseguito (e aspetta che finisca nel caso fosse in esecuzione su
1323 un altro processore). Lo spinlock, invece, previene accessi simultanei.
1324 Naturalmente, questo è più lento della semplice chiamata
1325 spin_lock_irq(), quindi ha senso solo se questo genere di accesso
1326 è estremamente raro.
1329 Quali funzioni possono essere chiamate in modo sicuro dalle interruzioni?
1330 =========================================================================
1332 Molte funzioni del kernel dormono (in sostanza, chiamano schedule())
1333 direttamente od indirettamente: non potete chiamarle se trattenere uno
1334 spinlock o avete la prelazione disabilitata, mai. Questo significa che
1335 dovete necessariamente essere nel contesto utente: chiamarle da un
1336 contesto d'interruzione è illegale.
1338 Alcune funzioni che dormono
1339 ---------------------------
1341 Le più comuni sono elencate qui di seguito, ma solitamente dovete leggere
1342 il codice per scoprire se altre chiamate sono sicure. Se chiunque altro
1343 le chiami dorme, allora dovreste poter dormire anche voi. In particolar
1344 modo, le funzioni di registrazione e deregistrazione solitamente si
1345 aspettano d'essere chiamante da un contesto utente e quindi che possono
1346 dormire.
1348 - Accessi allo spazio utente:
1350 - copy_from_user()
1352 - copy_to_user()
1354 - get_user()
1356 - put_user()
1358 - kmalloc(GFP_KERNEL) <kmalloc>`
1360 - mutex_lock_interruptible() and
1361 mutex_lock()
1363 C'è anche mutex_trylock() che però non dorme.
1364 Comunque, non deve essere usata in un contesto d'interruzione dato
1365 che la sua implementazione non è sicura in quel contesto.
1366 Anche mutex_unlock() non dorme mai. Non può comunque essere
1367 usata in un contesto d'interruzione perché un mutex deve essere rilasciato
1368 dallo stesso processo che l'ha acquisito.
1370 Alcune funzioni che non dormono
1371 -------------------------------
1373 Alcune funzioni possono essere chiamate tranquillamente da qualsiasi
1374 contesto, o trattenendo un qualsiasi *lock*.
1376 - printk()
1378 - kfree()
1380 - add_timer() e timer_delete()
1382 Riferimento per l'API dei Mutex
1383 ===============================
1385 .. kernel-doc:: include/linux/mutex.h
1386 :internal:
1388 .. kernel-doc:: kernel/locking/mutex.c
1389 :export:
1391 Riferimento per l'API dei Futex
1392 ===============================
1394 .. kernel-doc:: kernel/futex/core.c
1395 :internal:
1397 .. kernel-doc:: kernel/futex/futex.h
1398 :internal:
1400 .. kernel-doc:: kernel/futex/pi.c
1401 :internal:
1403 .. kernel-doc:: kernel/futex/requeue.c
1404 :internal:
1406 .. kernel-doc:: kernel/futex/waitwake.c
1407 :internal:
1409 Approfondimenti
1410 ===============
1412 - ``Documentation/locking/spinlocks.rst``: la guida di Linus Torvalds agli
1413 spinlock del kernel.
1415 - Unix Systems for Modern Architectures: Symmetric Multiprocessing and
1416 Caching for Kernel Programmers.
1418 L'introduzione alla sincronizzazione a livello di kernel di Curt Schimmel
1419 è davvero ottima (non è scritta per Linux, ma approssimativamente si adatta
1420 a tutte le situazioni). Il libro è costoso, ma vale ogni singolo spicciolo
1421 per capire la sincronizzazione nei sistemi multi-processore.
1422 [ISBN: 0201633388]
1424 Ringraziamenti
1425 ==============
1427 Grazie a Telsa Gwynne per aver formattato questa guida in DocBook, averla
1428 pulita e aggiunto un po' di stile.
1430 Grazie a Martin Pool, Philipp Rumpf, Stephen Rothwell, Paul Mackerras,
1431 Ruedi Aschwanden, Alan Cox, Manfred Spraul, Tim Waugh, Pete Zaitcev,
1432 James Morris, Robert Love, Paul McKenney, John Ashby per aver revisionato,
1433 corretto, maledetto e commentato.
1435 Grazie alla congrega per non aver avuto alcuna influenza su questo documento.
1437 Glossario
1438 =========
1440 prelazione
1441 Prima del kernel 2.5, o quando ``CONFIG_PREEMPT`` non è impostato, i processi
1442 in contesto utente non si avvicendano nell'esecuzione (in pratica, il
1443 processo userà il processore fino al proprio termine, a meno che non ci siano
1444 delle interruzioni). Con l'aggiunta di ``CONFIG_PREEMPT`` nella versione
1445 2.5.4 questo è cambiato: quando si è in contesto utente, processi con una
1446 priorità maggiore possono subentrare nell'esecuzione: gli spinlock furono
1447 cambiati per disabilitare la prelazioni, anche su sistemi monoprocessore.
1449 bh
1450 Bottom Half: per ragioni storiche, le funzioni che contengono '_bh' nel
1451 loro nome ora si riferiscono a qualsiasi interruzione software; per esempio,
1452 spin_lock_bh() blocca qualsiasi interuzione software sul processore
1453 corrente. I *Bottom Halves* sono deprecati, e probabilmente verranno
1454 sostituiti dai tasklet. In un dato momento potrà esserci solo un
1455 *bottom half* in esecuzione.
1457 contesto d'interruzione
1458 Non è il contesto utente: qui si processano le interruzioni hardware e
1459 software. La macro in_interrupt() ritorna vero.
1461 contesto utente
1462 Il kernel che esegue qualcosa per conto di un particolare processo (per
1463 esempio una chiamata di sistema) o di un thread del kernel. Potete
1464 identificare il processo con la macro ``current``. Da non confondere
1465 con lo spazio utente. Può essere interrotto sia da interruzioni software
1466 che hardware.
1468 interruzione hardware
1469 Richiesta di interruzione hardware. in_hardirq() ritorna vero in un
1470 gestore d'interruzioni hardware.
1472 interruzione software / softirq
1473 Gestore di interruzioni software: in_hardirq() ritorna falso;
1474 in_softirq() ritorna vero. I tasklet e le softirq sono entrambi
1475 considerati 'interruzioni software'.
1477 In soldoni, un softirq è uno delle 32 interruzioni software che possono
1478 essere eseguite su più processori in contemporanea. A volte si usa per
1479 riferirsi anche ai tasklet (in pratica tutte le interruzioni software).
1481 monoprocessore / UP
1482 (Uni-Processor) un solo processore, ovvero non è SMP. (``CONFIG_SMP=n``).
1484 multi-processore / SMP
1485 (Symmetric Multi-Processor) kernel compilati per sistemi multi-processore
1486 (``CONFIG_SMP=y``).
1488 spazio utente
1489 Un processo che esegue il proprio codice fuori dal kernel.
1491 tasklet
1492 Un'interruzione software registrabile dinamicamente che ha la garanzia
1493 d'essere eseguita solo su un processore alla volta.
1495 timer
1496 Un'interruzione software registrabile dinamicamente che viene eseguita
1497 (circa) in un determinato momento. Quando è in esecuzione è come un tasklet
1498 (infatti, sono chiamati da ``TIMER_SOFTIRQ``).

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

소개, 경쟁 상태와 임계 구역

1-101

이 문서는 이탈리아어 공통 면책 고지 `../disclaimer-ita.rst`를 포함하고 C namespace를 `it_IT`로 설정합니다. 공식 영어 원문은 `Documentation/kernel-hacking/locking.rst`의 `kernel_hacking_lock` 참조이며, 번역자는 Federico Vaga, 원 저자는 Rusty Russell입니다.

`_it_kernel_hacking_lock`은 이탈리아어 페이지 앵커입니다. 제목은 ‘신뢰하기 어려운 동기화 안내서’이며 원문은 Linux 2.6 시기의 잠금 체계를 설명하지만, 로컬 Linux v6.18.37 문서에 남아 있는 내용과 식별자를 그대로 번역합니다.

멀티스레딩과 커널 선점이 널리 쓰이므로 커널 개발자는 멀티프로세서 시스템의 동시성과 잠금 기초를 알아야 합니다.

단순한 `counter++`도 실제 기계 명령에서는 읽기, 1 더하기, 쓰기의 세 단계가 됩니다. 두 실행 인스턴스가 차례대로 완료되면 초기값 5는 7이 됩니다.

In un normale programma, potete incrementare un contatore nel seguente modo:

::

          contatore++;

Questo è quello che vi aspettereste che accada sempre:


.. table:: Risultati attesi

  +------------------------------------+------------------------------------+
  | Istanza 1                          | Istanza 2                          |
  +====================================+====================================+
  | leggi contatore (5)                |                                    |
  +------------------------------------+------------------------------------+
  | aggiungi 1 (6)                     |                                    |
  +------------------------------------+------------------------------------+
  | scrivi contatore (6)               |                                    |
  +------------------------------------+------------------------------------+
  |                                    | leggi contatore (6)                |
  +------------------------------------+------------------------------------+
  |                                    | aggiungi 1 (7)                     |
  +------------------------------------+------------------------------------+
  |                                    | scrivi contatore (7)               |
  +------------------------------------+------------------------------------+

그러나 두 인스턴스가 모두 5를 읽은 뒤 각각 6을 쓰면 증가 하나가 사라져 최종값은 6이 됩니다. 소스 한 줄이 원자적이라는 보장은 없습니다.

Questo è quello che potrebbe succedere in realtà:

.. table:: Possibile risultato

  +------------------------------------+------------------------------------+
  | Istanza 1                          | Istanza 2                          |
  +====================================+====================================+
  | leggi contatore (5)                |                                    |
  +------------------------------------+------------------------------------+
  |                                    | leggi contatore (5)                |
  +------------------------------------+------------------------------------+
  | aggiungi 1 (6)                     |                                    |
  +------------------------------------+------------------------------------+
  |                                    | aggiungi 1 (6)                     |
  +------------------------------------+------------------------------------+
  | scrivi contatore (6)               |                                    |
  +------------------------------------+------------------------------------+
  |                                    | scrivi contatore (6)               |
  +------------------------------------+------------------------------------+

여러 실행 흐름 사이의 시간 배치에 따라 결과가 달라지는 현상을 경쟁 상태라고 합니다. 이 문제가 생기는 코드 영역은 임계 구역입니다.

SMP 시스템에서는 여러 CPU가 동시에 임계 구역을 실행할 수 있어 핵심 설계 문제가 됩니다. 단일 CPU라도 선점이 임계 구역 중간에 다른 실행 흐름을 끼워 넣으면 같은 경쟁이 생깁니다.

해결책은 동시 접근 가능성을 식별하고 잠금을 사용해 한 번에 하나의 실행 인스턴스만 임계 구역에 들어가도록 만드는 것입니다.

기대한 counter++ 실행
단계인스턴스 1인스턴스 2공유 값
15 읽기대기5
26 계산·쓰기대기6
3완료6 읽기6
4완료7 계산·쓰기7

두 인스턴스가 순차 실행되면 두 증가가 모두 반영됩니다.

가능한 손실 갱신
단계인스턴스 1인스턴스 2공유 값
15 읽기5 읽기5
26 계산6 계산5
36 쓰기대기6
4완료6 쓰기6

두 인스턴스가 같은 이전 값을 읽으면 증가 하나가 사라집니다.

경쟁 상태의 형성
Shared stateTwo execution contexts
Interleaved read/modify/writeLost update
Identify critical sectionSerialize with lock

읽기-수정-쓰기가 겹치면 실행 순서가 결과를 바꿉니다.

.. include:: ../disclaimer-ita.rst

.. c:namespace:: it_IT

:Original: :ref:`Documentation/kernel-hacking/locking.rst <kernel_hacking_lock>`
:Translator: Federico Vaga <[email protected]>

.. _it_kernel_hacking_lock:

==========================================
L'inaffidabile guida alla sincronizzazione
==========================================

:Author: Rusty Russell

Introduzione
============

Benvenuto, alla notevole ed inaffidabile guida ai problemi di sincronizzazione
(locking) nel kernel. Questo documento descrive il sistema di sincronizzazione
nel kernel Linux 2.6.

Dato il largo utilizzo del multi-threading e della prelazione nel kernel
Linux, chiunque voglia dilettarsi col kernel deve conoscere i concetti
fondamentali della concorrenza e della sincronizzazione nei sistemi
multi-processore.

Il problema con la concorrenza
==============================

(Saltatelo se sapete già cos'è una corsa critica).

In un normale programma, potete incrementare un contatore nel seguente modo:

::

          contatore++;

Questo è quello che vi aspettereste che accada sempre:


.. table:: Risultati attesi

  +------------------------------------+------------------------------------+
  | Istanza 1                          | Istanza 2                          |
  +====================================+====================================+
  | leggi contatore (5)                |                                    |
  +------------------------------------+------------------------------------+
  | aggiungi 1 (6)                     |                                    |
  +------------------------------------+------------------------------------+
  | scrivi contatore (6)               |                                    |
  +------------------------------------+------------------------------------+
  |                                    | leggi contatore (6)                |
  +------------------------------------+------------------------------------+
  |                                    | aggiungi 1 (7)                     |
  +------------------------------------+------------------------------------+
  |                                    | scrivi contatore (7)               |
  +------------------------------------+------------------------------------+

Questo è quello che potrebbe succedere in realtà:

.. table:: Possibile risultato

  +------------------------------------+------------------------------------+
  | Istanza 1                          | Istanza 2                          |
  +====================================+====================================+
  | leggi contatore (5)                |                                    |
  +------------------------------------+------------------------------------+
  |                                    | leggi contatore (5)                |
  +------------------------------------+------------------------------------+
  | aggiungi 1 (6)                     |                                    |
  +------------------------------------+------------------------------------+
  |                                    | aggiungi 1 (6)                     |
  +------------------------------------+------------------------------------+
  | scrivi contatore (6)               |                                    |
  +------------------------------------+------------------------------------+
  |                                    | scrivi contatore (6)               |
  +------------------------------------+------------------------------------+


Corse critiche e sezioni critiche
---------------------------------

Questa sovrapposizione, ovvero quando un risultato dipende dal tempo che
intercorre fra processi diversi, è chiamata corsa critica. La porzione
di codice che contiene questo problema è chiamata sezione critica.
In particolar modo da quando Linux ha incominciato a girare su
macchine multi-processore, le sezioni critiche sono diventate uno dei
maggiori problemi di progettazione ed implementazione del kernel.

La prelazione può sortire gli stessi effetti, anche se c'è una sola CPU:
interrompendo un processo nella sua sezione critica otterremo comunque
la stessa corsa critica. In questo caso, il thread che si avvicenda
nell'esecuzione potrebbe eseguire anch'esso la sezione critica.

La soluzione è quella di riconoscere quando avvengono questi accessi
simultanei, ed utilizzare i *lock* per accertarsi che solo un'istanza
per volta possa entrare nella sezione critica. Il kernel offre delle buone
funzioni a questo scopo. E poi ci sono quelle meno buone, ma farò finta
che non esistano.

spinlock, mutex와 단일 프로세서

102-152

잠금 설계에서 가장 중요한 조언은 단순하게 유지하고 새 잠금을 도입하는 데 신중하라는 것입니다.

기본 잠금인 spinlock은 `include/asm/spinlock.h`에 정의됩니다. 한 실행 흐름만 보유할 수 있고 실패한 쪽은 잠금이 풀릴 때까지 CPU에서 능동 대기합니다.

spinlock은 작고 빠르며 잠들 수 없는 컨텍스트에서도 사용할 수 있습니다. 그러나 오래 보유하면 다른 CPU가 계속 회전해 자원을 낭비합니다.

mutex는 `include/linux/mutex.h`에 정의되며 얻지 못한 프로세스를 잠들게 하고, 해제되면 다시 깨웁니다. 기다리는 동안 CPU가 다른 작업을 할 수 있지만 인터럽트처럼 잠들 수 없는 컨텍스트에서는 사용할 수 없습니다.

spinlock과 mutex 모두 재귀 잠금이 아닙니다. 같은 실행 흐름이 같은 잠금을 다시 얻으려 하면 교착됩니다.

`CONFIG_SMP=n`과 `CONFIG_PREEMPT=n`인 커널에서는 spinlock이 사라집니다. 동시에 실행할 다른 흐름이 없으므로 상호 배제가 필요하지 않기 때문입니다.

SMP는 꺼져 있고 선점만 켜진 커널에서는 spinlock이 선점을 비활성화해 경쟁을 막습니다. 따라서 대다수 코드는 선점을 별도 특별 경우로 만들지 않고 SMP 동시성처럼 생각할 수 있습니다.

실제 멀티프로세서 장비가 없어도 `CONFIG_SMP`와 `CONFIG_PREEMPT`를 켜 잠금 문제를 검사해야 합니다. mutex는 사용자 컨텍스트의 프로세스 사이 동기화에 필요하므로 UP에서도 남습니다.

spinlock과 mutex
속성spinlockmutex
대기능동 회전프로세스 수면
잠들 수 없는 컨텍스트사용 가능사용 불가
긴 대기비효율적CPU를 다른 작업에 양보
재귀 획득불가불가

대기 방식과 호출 가능 컨텍스트가 핵심 차이입니다.

빌드 구성에 따른 spinlock
CONFIG_SMPCONFIG_PREEMPTspinlock 효과
nn제거됨
ny선점 비활성화
y무관CPU 간 상호 배제

동시성 원인이 무엇인지에 따라 구현 효과가 달라집니다.

잠금 선택의 첫 질문
Critical sectionCan current context sleep?
Yes, user context onlymutex
No or IRQ-visiblespinlock variant

잠들 수 있는 컨텍스트인지 먼저 판단합니다.

Sincronizzazione nel kernel Linux
=================================

Se dovessi darvi un suggerimento sulla sincronizzazione: **mantenetela
semplice**.

Siate riluttanti nell'introduzione di nuovi *lock*.

I due principali tipi di *lock* nel kernel: spinlock e mutex
------------------------------------------------------------

Ci sono due tipi principali di *lock* nel kernel. Il tipo fondamentale è lo
spinlock (``include/asm/spinlock.h``), un semplice *lock* che può essere
trattenuto solo da un processo: se non si può trattenere lo spinlock, allora
rimane in attesa attiva (in inglese *spinning*) finché non ci riesce.
Gli spinlock sono molto piccoli e rapidi, possono essere utilizzati ovunque.

Il secondo tipo è il mutex (``include/linux/mutex.h``): è come uno spinlock,
ma potreste bloccarvi trattenendolo. Se non potete trattenere un mutex
il vostro processo si auto-sospenderà; verrà riattivato quando il mutex
verrà rilasciato. Questo significa che il processore potrà occuparsi d'altro
mentre il vostro processo è in attesa. Esistono molti casi in cui non potete
permettervi di sospendere un processo (vedere
`Quali funzioni possono essere chiamate in modo sicuro dalle interruzioni?`_)
e quindi dovrete utilizzare gli spinlock.

Nessuno di questi *lock* è ricorsivo: vedere
`Stallo: semplice ed avanzato`_

I *lock* e i kernel per sistemi monoprocessore
----------------------------------------------

Per i kernel compilati senza ``CONFIG_SMP`` e senza ``CONFIG_PREEMPT``
gli spinlock non esistono. Questa è un'ottima scelta di progettazione:
quando nessun altro processo può essere eseguito in simultanea, allora
non c'è la necessità di avere un *lock*.

Se il kernel è compilato senza ``CONFIG_SMP`` ma con ``CONFIG_PREEMPT``,
allora gli spinlock disabilitano la prelazione; questo è sufficiente a
prevenire le corse critiche. Nella maggior parte dei casi, possiamo considerare
la prelazione equivalente ad un sistema multi-processore senza preoccuparci
di trattarla indipendentemente.

Dovreste verificare sempre la sincronizzazione con le opzioni ``CONFIG_SMP`` e
``CONFIG_PREEMPT`` abilitate, anche quando non avete un sistema
multi-processore, questo vi permetterà di identificare alcuni problemi
di sincronizzazione.

Come vedremo di seguito, i mutex continuano ad esistere perché sono necessari
per la sincronizzazione fra processi in contesto utente.

사용자 컨텍스트, softirq, tasklet, timer

153-255

자료구조가 사용자 컨텍스트에서만 쓰이면 단순 mutex로 보호할 수 있습니다. 초기화 후 `mutex_lock_interruptible()`로 얻고 `mutex_unlock()`으로 풉니다.

`mutex_lock()`은 신호가 와도 돌아오지 않으므로 피하는 편이 좋다고 원문은 권합니다. `net/netfilter/nf_sockopt.c`의 `nf_sockopt_mutex`는 로드·언로드 등록과 setsockopt/getsockopt 조회를 보호하는 사례입니다.

사용자 컨텍스트와 softirq가 자료를 공유하면 현재 CPU에서 softirq가 사용자를 선점할 수 있고 다른 CPU도 임계 구역을 실행할 수 있습니다. `spin_lock_bh()`는 현재 CPU의 softirq를 막고 spinlock을 얻으며 `spin_unlock_bh()`는 반대로 복원합니다.

`_bh`는 과거 bottom half 명칭의 흔적입니다. 같은 상황에서 `spin_lock_irq()` 또는 `spin_lock_irqsave()`를 써 하드 IRQ까지 막을 수도 있습니다.

UP 빌드에서는 spinlock 부분이 사라지고 `local_bh_disable()` 효과만 남아 softirq 실행을 막습니다.

tasklet과 timer는 softirq에서 실행되므로 사용자 컨텍스트와의 동기화 규칙이 같습니다. 잠금 관점에서 tasklet과 timer는 동일하게 다룹니다.

동일 tasklet은 동시에 두 CPU에서 실행되지 않으므로 자기 자신에 대한 재진입을 걱정할 필요가 없습니다.

서로 다른 tasklet 또는 timer가 자료를 공유하면 `spin_lock()`과 `spin_unlock()`이 필요합니다. 이미 tasklet 컨텍스트라 현재 CPU의 다른 tasklet이 실행되지 않으므로 `_bh` 변형은 불필요합니다.

같은 softirq 종류는 여러 CPU에서 동시에 실행될 수 있습니다. 성능이 중요하면 per-CPU 데이터를 고려할 수 있지만 공유 데이터에는 spinlock이 필요합니다.

서로 다른 softirq, timer, tasklet 사이의 공유 데이터도 어느 것이든 다른 CPU에서 실행될 수 있으므로 `spin_lock()`으로 보호합니다.

사용자 컨텍스트와 하위 처리 동기화
공유 조합최소 방법
사용자 ↔ 사용자mutex_lock_interruptible
사용자 ↔ softirq/tasklet/timerspin_lock_bh
서로 다른 tasklet/timerspin_lock
softirq ↔ softirqspin_lock 또는 per-CPU 분리

공유 상대가 어디서 실행되는지에 따라 최소 잠금이 달라집니다.

spin_lock_bh의 두 역할
User contextDisable local softirq
Acquire spinlockExclude remote CPUs
Critical sectionspin_unlock_bh

로컬 선점을 막는 것과 다른 CPU와 상호 배제하는 것을 함께 수행합니다.

tasklet/timer 자료 공유 판단
Same tasklet instanceNo reentrant execution
Different tasklet/timerMay run on another CPU
Shared mutable dataspin_lock

동일 인스턴스의 직렬화 보장과 다른 인스턴스의 병렬 가능성을 구분합니다.

Sincronizzazione in contesto utente
-----------------------------------

Se avete una struttura dati che verrà utilizzata solo dal contesto utente,
allora, per proteggerla, potete utilizzare un semplice mutex
(``include/linux/mutex.h``). Questo è il caso più semplice: inizializzate il
mutex; invocate mutex_lock_interruptible() per trattenerlo e
mutex_unlock() per rilasciarlo. C'è anche mutex_lock()
ma questa dovrebbe essere evitata perché non ritorna in caso di segnali.

Per esempio: ``net/netfilter/nf_sockopt.c`` permette la registrazione
di nuove chiamate per setsockopt() e getsockopt()
usando la funzione nf_register_sockopt(). La registrazione e
la rimozione vengono eseguite solamente quando il modulo viene caricato
o scaricato (e durante l'avvio del sistema, qui non abbiamo concorrenza),
e la lista delle funzioni registrate viene consultata solamente quando
setsockopt() o getsockopt() sono sconosciute al sistema.
In questo caso ``nf_sockopt_mutex`` è perfetto allo scopo, in particolar modo
visto che setsockopt e getsockopt potrebbero dormire.

Sincronizzazione fra il contesto utente e i softirq
---------------------------------------------------

Se un softirq condivide dati col contesto utente, avete due problemi.
Primo, il contesto utente corrente potrebbe essere interroto da un softirq,
e secondo, la sezione critica potrebbe essere eseguita da un altro
processore. Questo è quando spin_lock_bh()
(``include/linux/spinlock.h``) viene utilizzato. Questo disabilita i softirq
sul processore e trattiene il *lock*. Invece, spin_unlock_bh() fa
l'opposto. (Il suffisso '_bh' è un residuo storico che fa riferimento al
"Bottom Halves", il vecchio nome delle interruzioni software. In un mondo
perfetto questa funzione si chiamerebbe 'spin_lock_softirq()').

Da notare che in questo caso potete utilizzare anche spin_lock_irq()
o spin_lock_irqsave(), queste fermano anche le interruzioni hardware:
vedere `Contesto di interruzione hardware`_.

Questo funziona alla perfezione anche sui sistemi monoprocessore: gli spinlock
svaniscono e questa macro diventa semplicemente local_bh_disable()
(``include/linux/interrupt.h``), la quale impedisce ai softirq d'essere
eseguiti.

Sincronizzazione fra contesto utente e i tasklet
------------------------------------------------

Questo caso è uguale al precedente, un tasklet viene eseguito da un softirq.

Sincronizzazione fra contesto utente e i timer
----------------------------------------------

Anche questo caso è uguale al precedente, un timer viene eseguito da un
softirq.
Dal punto di vista della sincronizzazione, tasklet e timer sono identici.

Sincronizzazione fra tasklet e timer
------------------------------------

Qualche volta un tasklet od un timer potrebbero condividere i dati con
un altro tasklet o timer

Lo stesso tasklet/timer
~~~~~~~~~~~~~~~~~~~~~~~

Dato che un tasklet non viene mai eseguito contemporaneamente su due
processori, non dovete preoccuparvi che sia rientrante (ovvero eseguito
più volte in contemporanea), perfino su sistemi multi-processore.

Differenti tasklet/timer
~~~~~~~~~~~~~~~~~~~~~~~~

Se un altro tasklet/timer vuole condividere dati col vostro tasklet o timer,
allora avrete bisogno entrambe di spin_lock() e
spin_unlock(). Qui spin_lock_bh() è inutile, siete già
in un tasklet ed avete la garanzia che nessun altro verrà eseguito sullo
stesso processore.

Sincronizzazione fra softirq
----------------------------

Spesso un softirq potrebbe condividere dati con se stesso o un tasklet/timer.

Lo stesso softirq
~~~~~~~~~~~~~~~~~

Lo stesso softirq può essere eseguito su un diverso processore: allo scopo
di migliorare le prestazioni potete utilizzare dati riservati ad ogni
processore (vedere `Dati per processore`_). Se siete arrivati
fino a questo punto nell'uso dei softirq, probabilmente tenete alla scalabilità
delle prestazioni abbastanza da giustificarne la complessità aggiuntiva.

Dovete utilizzare spin_lock() e spin_unlock() per
proteggere i dati condivisi.

Diversi Softirqs
~~~~~~~~~~~~~~~~

Dovete utilizzare spin_lock() e spin_unlock() per
proteggere i dati condivisi, che siano timer, tasklet, diversi softirq o
lo stesso o altri softirq: uno qualsiasi di essi potrebbe essere in esecuzione
su un diverso processore.

.. _`it_hardirq-context`:

하드 IRQ, 최소 잠금표와 trylock

256-389

하드웨어 인터럽트는 보통 처리할 작업을 큐에 넣고 tasklet 또는 softirq가 나중에 가져가도록 통신합니다.

하드 IRQ와 softirq가 자료를 공유하면 softirq가 하드 IRQ에 선점될 수 있고 다른 CPU의 하드 IRQ도 임계 구역을 실행할 수 있습니다. softirq 쪽은 `spin_lock_irq()`로 현재 CPU IRQ를 끄고 잠금을 얻습니다.

하드 IRQ 핸들러에서는 softirq가 실행될 수 없으므로 보통 더 빠른 `spin_lock()`만 써도 됩니다. 다만 같은 잠금을 쓰는 다른 하드 IRQ 핸들러가 현재 핸들러를 선점할 수 있으면 `spin_lock_irq()`가 필요합니다.

UP에서는 spinlock 부분이 사라져 로컬 IRQ 비활성화만 남습니다. 원문은 이 효과가 softirq/tasklet/BH 실행도 막는다고 설명합니다.

`spin_lock_irqsave()`는 이전 IRQ 상태를 변수에 저장하고 `spin_unlock_irqrestore()`로 복원합니다. 이미 IRQ가 꺼진 하드 IRQ와 IRQ를 꺼야 하는 softirq 양쪽에서 같은 코드를 안전하게 쓸 수 있습니다.

softirq는 하드 IRQ 복귀 시 실행되므로 IRQ를 막으면 softirq도 막힙니다. 따라서 `spin_lock_irqsave()`는 가장 일반적이고 강력한 spinlock 변형입니다.

두 하드 IRQ 핸들러가 데이터를 공유하는 드문 경우에는 아키텍처가 핸들러 실행 중 모든 IRQ를 자동으로 막는다고 가정할 수 없으므로 `spin_lock_irqsave()`를 사용합니다.

요약 규칙은 사용자 컨텍스트끼리만 동기화하면 mutex, 인터럽트가 자료를 만질 수 있으면 `spin_lock_irqsave()`입니다. spinlock 임계 구역은 함수 호출을 포함해 5줄을 넘기지 않는 것을 원문이 경험칙으로 제시합니다.

최소 요구 표에서 `SLIS`는 `spin_lock_irqsave`, `SLI`는 `spin_lock_irq`, `SL`은 `spin_lock`, `SLBH`는 `spin_lock_bh`, `MLI`는 `mutex_lock_interruptible`입니다.

============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ==============
.              IRQ Handler A IRQ Handler B Softirq A Softirq B Tasklet A Tasklet B Timer A Timer B User Context A User Context B
============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ==============
IRQ Handler A  None
IRQ Handler B  SLIS          None
Softirq A      SLI           SLI           SL
Softirq B      SLI           SLI           SL        SL
Tasklet A      SLI           SLI           SL        SL        None
Tasklet B      SLI           SLI           SL        SL        SL        None
Timer A        SLI           SLI           SL        SL        SL        SL        None
Timer B        SLI           SLI           SL        SL        SL        SL        SL      None
User Context A SLI           SLI           SLBH      SLBH      SLBH      SLBH      SLBH    SLBH    None
User Context B SLI           SLI           SLBH      SLBH      SLBH      SLBH      SLBH    SLBH    MLI            None
============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ==============

Table: Tabella dei requisiti per la sincronizzazione

+--------+----------------------------+
| SLIS   | spin_lock_irqsave          |
+--------+----------------------------+
| SLI    | spin_lock_irq              |
+--------+----------------------------+
| SL     | spin_lock                  |
+--------+----------------------------+
| SLBH   | spin_lock_bh               |
+--------+----------------------------+
| MLI    | mutex_lock_interruptible   |
+--------+----------------------------+

Table: Legenda per la tabella dei requisiti per la sincronizzazione

`spin_trylock()`은 한 번만 시도해 성공하면 0이 아닌 값, 실패하면 0을 반환합니다. 어떤 컨텍스트에서도 쓸 수 있지만 `spin_lock()`처럼 자신을 선점할 컨텍스트를 먼저 비활성화해야 합니다.

`mutex_trylock()`도 잠들지 않고 즉시 성공 여부를 반환하지만 구현이 IRQ 안전하지 않으므로 하드웨어 또는 소프트웨어 인터럽트 컨텍스트에서 사용하면 안 됩니다.

잠금 약어
약어함수
SLISspin_lock_irqsave
SLIspin_lock_irq
SLspin_lock
SLBHspin_lock_bh
MLImutex_lock_interruptible

원문의 최소 요구 매트릭스에서 사용하는 약어입니다.

대표 컨텍스트 조합의 최소 잠금
컨텍스트 A컨텍스트 B최소 요구
IRQ handler다른 IRQ handlerSLIS
IRQ handlersoftirq/tasklet/timerSLI
softirqsoftirq/tasklet/timerSL
user contextsoftirq/tasklet/timerSLBH
user context다른 user contextMLI

전체 원문 매트릭스의 핵심 조합을 읽기 쉽게 재구성합니다.

trylock 반환과 제한
함수성공실패IRQ 사용
spin_trylock0이 아님0컨텍스트 비활성화 후 가능
mutex_trylock0이 아님0불가

즉시 실패할 수 있는 경로를 호출자가 처리해야 합니다.

IRQ-safe 잠금과 복원
flags variablespin_lock_irqsave
IRQ disabled + lock heldCritical section
spin_unlock_irqrestoreOriginal IRQ state

진입 전 상태를 저장하면 하드 IRQ와 softirq 공용 코드에서 쓸 수 있습니다.

Contesto di interruzione hardware
=================================

Solitamente le interruzioni hardware comunicano con un tasklet o un softirq.
Spesso questo si traduce nel mettere in coda qualcosa da fare che verrà
preso in carico da un softirq.

Sincronizzazione fra interruzioni hardware e softirq/tasklet
------------------------------------------------------------

Se un gestore di interruzioni hardware condivide dati con un softirq, allora
avrete due preoccupazioni. Primo, il softirq può essere interrotto da
un'interruzione hardware, e secondo, la sezione critica potrebbe essere
eseguita da un'interruzione hardware su un processore diverso. Questo è il caso
dove spin_lock_irq() viene utilizzato. Disabilita le interruzioni
sul processore che l'esegue, poi trattiene il lock. spin_unlock_irq()
fa l'opposto.

Il gestore d'interruzione hardware non ha bisogno di usare spin_lock_irq()
perché i softirq non possono essere eseguiti quando il gestore d'interruzione
hardware è in esecuzione: per questo si può usare spin_lock(), che è un po'
più veloce. L'unica eccezione è quando un altro gestore d'interruzioni
hardware utilizza lo stesso *lock*: spin_lock_irq() impedirà a questo
secondo gestore di interrompere quello in esecuzione.

Questo funziona alla perfezione anche sui sistemi monoprocessore: gli spinlock
svaniscono e questa macro diventa semplicemente local_irq_disable()
(``include/asm/smp.h``), la quale impedisce a softirq/tasklet/BH d'essere
eseguiti.

spin_lock_irqsave() (``include/linux/spinlock.h``) è una variante che
salva lo stato delle interruzioni in una variabile, questa verrà poi passata
a spin_unlock_irqrestore(). Questo significa che lo stesso codice
potrà essere utilizzato in un'interruzione hardware (dove le interruzioni sono
già disabilitate) e in un softirq (dove la disabilitazione delle interruzioni
è richiesta).

Da notare che i softirq (e quindi tasklet e timer) sono eseguiti al ritorno
da un'interruzione hardware, quindi spin_lock_irq() interrompe
anche questi. Tenuto conto di questo si può dire che
spin_lock_irqsave() è la funzione di sincronizzazione più generica
e potente.

Sincronizzazione fra due gestori d'interruzioni hardware
--------------------------------------------------------

Condividere dati fra due gestori di interruzione hardware è molto raro, ma se
succede, dovreste usare spin_lock_irqsave(): è una specificità
dell'architettura il fatto che tutte le interruzioni vengano interrotte
quando si eseguono di gestori di interruzioni.

Bigino della sincronizzazione
=============================

Pete Zaitcev ci offre il seguente riassunto:

-  Se siete in un contesto utente (una qualsiasi chiamata di sistema)
   e volete sincronizzarvi con altri processi, usate i mutex. Potete trattenere
   il mutex e dormire (``copy_from_user(`` o ``kmalloc(x,GFP_KERNEL)``).

-  Altrimenti (== i dati possono essere manipolati da un'interruzione) usate
   spin_lock_irqsave() e spin_unlock_irqrestore().

-  Evitate di trattenere uno spinlock per più di 5 righe di codice incluse
   le chiamate a funzione (ad eccezione di quell per l'accesso come
   readb()).

Tabella dei requisiti minimi
----------------------------

La tabella seguente illustra i requisiti **minimi** per la sincronizzazione fra
diversi contesti. In alcuni casi, lo stesso contesto può essere eseguito solo
da un processore per volta, quindi non ci sono requisiti per la
sincronizzazione (per esempio, un thread può essere eseguito solo su un
processore alla volta, ma se deve condividere dati con un altro thread, allora
la sincronizzazione è necessaria).

Ricordatevi il suggerimento qui sopra: potete sempre usare
spin_lock_irqsave(), che è un sovrainsieme di tutte le altre funzioni
per spinlock.

============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ==============
.              IRQ Handler A IRQ Handler B Softirq A Softirq B Tasklet A Tasklet B Timer A Timer B User Context A User Context B
============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ==============
IRQ Handler A  None
IRQ Handler B  SLIS          None
Softirq A      SLI           SLI           SL
Softirq B      SLI           SLI           SL        SL
Tasklet A      SLI           SLI           SL        SL        None
Tasklet B      SLI           SLI           SL        SL        SL        None
Timer A        SLI           SLI           SL        SL        SL        SL        None
Timer B        SLI           SLI           SL        SL        SL        SL        SL      None
User Context A SLI           SLI           SLBH      SLBH      SLBH      SLBH      SLBH    SLBH    None
User Context B SLI           SLI           SLBH      SLBH      SLBH      SLBH      SLBH    SLBH    MLI            None
============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ==============

Table: Tabella dei requisiti per la sincronizzazione

+--------+----------------------------+
| SLIS   | spin_lock_irqsave          |
+--------+----------------------------+
| SLI    | spin_lock_irq              |
+--------+----------------------------+
| SL     | spin_lock                  |
+--------+----------------------------+
| SLBH   | spin_lock_bh               |
+--------+----------------------------+
| MLI    | mutex_lock_interruptible   |
+--------+----------------------------+

Table: Legenda per la tabella dei requisiti per la sincronizzazione

Le funzioni *trylock*
=====================

Ci sono funzioni che provano a trattenere un *lock* solo una volta e
ritornano immediatamente comunicato il successo od il fallimento
dell'operazione. Posso essere usate quando non serve accedere ai dati
protetti dal *lock* quando qualche altro thread lo sta già facendo
trattenendo il *lock*. Potrete acquisire il *lock* più tardi se vi
serve accedere ai dati protetti da questo *lock*.

La funzione spin_trylock() non ritenta di acquisire il *lock*,
se ci riesce al primo colpo ritorna un valore diverso da zero, altrimenti
se fallisce ritorna 0. Questa funzione può essere utilizzata in un qualunque
contesto, ma come spin_lock(): dovete disabilitare i contesti che
potrebbero interrompervi e quindi trattenere lo spinlock.

La funzione mutex_trylock() invece di sospendere il vostro processo
ritorna un valore diverso da zero se è possibile trattenere il lock al primo
colpo, altrimenti se fallisce ritorna 0. Nonostante non dorma, questa funzione
non può essere usata in modo sicuro in contesti di interruzione hardware o
software.

사용자 컨텍스트 캐시 예제

390-510

공통 예제는 이름을 숫자 ID에 매핑하고 객체별 사용 빈도를 기록하는 캐시입니다. 가득 차면 가장 인기 없는 객체를 제거합니다.

첫 버전은 모든 작업이 시스템 호출 같은 사용자 컨텍스트에서 실행되어 잠들 수 있다고 가정하므로 하나의 mutex로 캐시와 객체를 보호합니다.

`struct object`에는 연결 리스트 노드, id, 이름, popularity가 있습니다. `DEFINE_MUTEX(cache_lock)`, `LIST_HEAD(cache)`, `cache_num`, 최대 크기 10을 선언합니다.

`__cache_find()`, `__cache_delete()`, `__cache_add()`는 호출자가 `cache_lock`을 보유해야 하는 내부 함수입니다. 검색은 인기도를 올리고, 삭제는 목록 제거·해제·개수 감소, 추가는 한도 초과 시 최저 인기도 객체 제거를 수행합니다.

공개 `cache_add()`, `cache_delete()`, `cache_find()`는 mutex를 얻어 내부 함수를 호출합니다. `cache_add()`는 잠금 전에 `GFP_KERNEL`로 객체를 할당하고 필드를 초기화합니다.

객체가 목록에 들어가기 전에는 다른 흐름이 접근할 수 없으므로 잠금 밖에서 새 객체 필드를 준비하는 최적화는 안전합니다. 잠금 보유 시간을 줄이는 좋은 예입니다.

원문 C 코드는 구조체, 내부 도우미, 세 공개 함수를 한 덩어리로 제공하며 아래 원형을 보존합니다.

e tutti gli oggetti che contiene. Ecco il codice::

    #include <linux/list.h>
    #include <linux/slab.h>
    #include <linux/string.h>
    #include <linux/mutex.h>
    #include <asm/errno.h>

    struct object
    {
            struct list_head list;
            int id;
            char name[32];
            int popularity;
    };

    /* Protects the cache, cache_num, and the objects within it */
    static DEFINE_MUTEX(cache_lock);
    static LIST_HEAD(cache);
    static unsigned int cache_num = 0;
    #define MAX_CACHE_SIZE 10

    /* Must be holding cache_lock */
    static struct object *__cache_find(int id)
    {
            struct object *i;

            list_for_each_entry(i, &cache, list)
                    if (i->id == id) {
                            i->popularity++;
                            return i;
                    }
            return NULL;
    }

    /* Must be holding cache_lock */
    static void __cache_delete(struct object *obj)
    {
            BUG_ON(!obj);
            list_del(&obj->list);
            kfree(obj);
            cache_num--;
    }

    /* Must be holding cache_lock */
    static void __cache_add(struct object *obj)
    {
            list_add(&obj->list, &cache);
            if (++cache_num > MAX_CACHE_SIZE) {
                    struct object *i, *outcast = NULL;
                    list_for_each_entry(i, &cache, list) {
                            if (!outcast || i->popularity < outcast->popularity)
                                    outcast = i;
                    }
                    __cache_delete(outcast);
            }
    }

    int cache_add(int id, const char *name)
    {
            struct object *obj;

            if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
                    return -ENOMEM;

            strscpy(obj->name, name, sizeof(obj->name));
            obj->id = id;
            obj->popularity = 0;

            mutex_lock(&cache_lock);
            __cache_add(obj);
            mutex_unlock(&cache_lock);
            return 0;
    }

    void cache_delete(int id)
    {
            mutex_lock(&cache_lock);
            __cache_delete(__cache_find(id));
            mutex_unlock(&cache_lock);
    }

    int cache_find(int id, char *name)
    {
            struct object *obj;
            int ret = -ENOENT;

            mutex_lock(&cache_lock);
            obj = __cache_find(id);
            if (obj) {
                    ret = 0;
                    strcpy(name, obj->name);
            }
            mutex_unlock(&cache_lock);
            return ret;
    }
캐시 잠금이 보호하는 상태
상태보호 방법
cache 리스트cache_lock
cache_numcache_lock
객체 popularity와 namecache_lock
목록 밖 새 객체 초기화아직 공유되지 않아 잠금 불필요

첫 버전은 하나의 mutex가 구조와 내용을 모두 보호합니다.

cache_add의 잠금 축소
kmalloc + initialize objectNo other references
mutex_lock(cache_lock)Insert into shared list
Evict if neededmutex_unlock

공유되기 전 준비는 잠금 밖에서 하고 공개 시점만 직렬화합니다.

Esempi più comuni
=================

Guardiamo un semplice esempio: una memoria che associa nomi a numeri.
La memoria tiene traccia di quanto spesso viene utilizzato ogni oggetto;
quando è piena, l'oggetto meno usato viene eliminato.

Tutto in contesto utente
------------------------

Nel primo esempio, supponiamo che tutte le operazioni avvengano in contesto
utente (in soldoni, da una chiamata di sistema), quindi possiamo dormire.
Questo significa che possiamo usare i mutex per proteggere la nostra memoria
e tutti gli oggetti che contiene. Ecco il codice::

    #include <linux/list.h>
    #include <linux/slab.h>
    #include <linux/string.h>
    #include <linux/mutex.h>
    #include <asm/errno.h>

    struct object
    {
            struct list_head list;
            int id;
            char name[32];
            int popularity;
    };

    /* Protects the cache, cache_num, and the objects within it */
    static DEFINE_MUTEX(cache_lock);
    static LIST_HEAD(cache);
    static unsigned int cache_num = 0;
    #define MAX_CACHE_SIZE 10

    /* Must be holding cache_lock */
    static struct object *__cache_find(int id)
    {
            struct object *i;

            list_for_each_entry(i, &cache, list)
                    if (i->id == id) {
                            i->popularity++;
                            return i;
                    }
            return NULL;
    }

    /* Must be holding cache_lock */
    static void __cache_delete(struct object *obj)
    {
            BUG_ON(!obj);
            list_del(&obj->list);
            kfree(obj);
            cache_num--;
    }

    /* Must be holding cache_lock */
    static void __cache_add(struct object *obj)
    {
            list_add(&obj->list, &cache);
            if (++cache_num > MAX_CACHE_SIZE) {
                    struct object *i, *outcast = NULL;
                    list_for_each_entry(i, &cache, list) {
                            if (!outcast || i->popularity < outcast->popularity)
                                    outcast = i;
                    }
                    __cache_delete(outcast);
            }
    }

    int cache_add(int id, const char *name)
    {
            struct object *obj;

            if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
                    return -ENOMEM;

            strscpy(obj->name, name, sizeof(obj->name));
            obj->id = id;
            obj->popularity = 0;

            mutex_lock(&cache_lock);
            __cache_add(obj);
            mutex_unlock(&cache_lock);
            return 0;
    }

    void cache_delete(int id)
    {
            mutex_lock(&cache_lock);
            __cache_delete(__cache_find(id));
            mutex_unlock(&cache_lock);
    }

    int cache_find(int id, char *name)
    {
            struct object *obj;
            int ret = -ENOENT;

            mutex_lock(&cache_lock);
            obj = __cache_find(id);
            if (obj) {
                    ret = 0;
                    strcpy(name, obj->name);
            }
            mutex_unlock(&cache_lock);
            return ret;
    }

Da notare che ci assicuriamo sempre di trattenere cache_lock quando
aggiungiamo, rimuoviamo od ispezioniamo la memoria: sia la struttura
della memoria che il suo contenuto sono protetti dal *lock*. Questo
caso è semplice dato che copiamo i dati dall'utente e non permettiamo
mai loro di accedere direttamente agli oggetti.

C'è una piccola ottimizzazione qui: nella funzione cache_add()
impostiamo i campi dell'oggetto prima di acquisire il *lock*. Questo è
sicuro perché nessun altro potrà accedervi finché non lo inseriremo
nella memoria.

인터럽트 접근과 참조 카운팅

511-718

다음 버전은 `cache_find()`나 삭제 작업이 하드웨어·소프트웨어 인터럽트에서도 실행될 수 있다고 가정합니다. mutex를 `DEFINE_SPINLOCK`으로 바꾸고 공개 함수에서 `spin_lock_irqsave()`와 `spin_unlock_irqrestore()`를 사용합니다.

이 쌍은 사용자 컨텍스트에서 IRQ를 끄고, 이미 IRQ 컨텍스트라면 기존 상태를 유지하므로 같은 함수가 어느 컨텍스트에서 호출되어도 잠금 자체는 안전합니다.

다만 `cache_add()`의 `kmalloc(..., GFP_KERNEL)`은 여전히 사용자 컨텍스트에서만 허용됩니다. 인터럽트에서도 추가해야 한다면 할당 플래그를 함수 인자로 받아 `GFP_ATOMIC` 같은 선택을 가능하게 해야 합니다.

::

    --- cache.c.usercontext 2003-12-09 13:58:54.000000000 +1100
    +++ cache.c.interrupt   2003-12-09 14:07:49.000000000 +1100
    @@ -12,7 +12,7 @@
             int popularity;
     };

    -static DEFINE_MUTEX(cache_lock);
    +static DEFINE_SPINLOCK(cache_lock);
     static LIST_HEAD(cache);
     static unsigned int cache_num = 0;
     #define MAX_CACHE_SIZE 10
    @@ -55,6 +55,7 @@
     int cache_add(int id, const char *name)
     {
             struct object *obj;
    +        unsigned long flags;

             if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
                     return -ENOMEM;
    @@ -63,30 +64,33 @@
             obj->id = id;
             obj->popularity = 0;

    -        mutex_lock(&cache_lock);
    +        spin_lock_irqsave(&cache_lock, flags);
             __cache_add(obj);
    -        mutex_unlock(&cache_lock);
    +        spin_unlock_irqrestore(&cache_lock, flags);
             return 0;
     }

     void cache_delete(int id)
     {
    -        mutex_lock(&cache_lock);
    +        unsigned long flags;
    +
    +        spin_lock_irqsave(&cache_lock, flags);
             __cache_delete(__cache_find(id));
    -        mutex_unlock(&cache_lock);
    +        spin_unlock_irqrestore(&cache_lock, flags);
     }

     int cache_find(int id, char *name)
     {
             struct object *obj;
             int ret = -ENOENT;
    +        unsigned long flags;

    -        mutex_lock(&cache_lock);
    +        spin_lock_irqsave(&cache_lock, flags);
             obj = __cache_find(id);
             if (obj) {
                     ret = 0;
                     strcpy(name, obj->name);
             }
    -        mutex_unlock(&cache_lock);
    +        spin_unlock_irqrestore(&cache_lock, flags);
             return ret;
     }

객체 포인터를 파일 밖으로 노출하면 잠금과 수명이라는 두 문제가 생깁니다. 외부 코드가 객체를 수정하려면 잠금 정책도 공개해야 하고, 잠금을 풀고도 포인터가 유효하려면 삭제와 재사용을 막아야 합니다.

전역 잠금을 포인터 사용 기간 내내 잡을 수는 없습니다. 다른 작업을 모두 막기 때문입니다. 해법은 포인터를 보유한 주체마다 참조 카운터를 증가시키고 사용이 끝나면 감소시키는 것입니다.

카운터가 0이 되면 더 이상 사용자가 없어 객체를 해제할 수 있습니다. 캐시 자체도 객체를 보유하는 하나의 참조로 계산해 삽입 시 1로 시작합니다.

`object_get()`과 `object_put()`은 전형적인 get/put API로 카운터 조작을 감쌉니다. `cache_find()`는 객체를 찾은 뒤 잠금 안에서 참조를 추가해 반환하므로 호출자는 잠금을 풀고 잠들면서 `copy_to_user()` 같은 작업을 할 수 있습니다.

삭제는 목록에서 객체를 제거하고 캐시가 보유한 참조를 내려놓습니다. 다른 외부 참조가 남아 있으면 즉시 해제되지 않습니다.

Ecco il codice::

    --- cache.c.interrupt   2003-12-09 14:25:43.000000000 +1100
    +++ cache.c.refcnt  2003-12-09 14:33:05.000000000 +1100
    @@ -7,6 +7,7 @@
     struct object
     {
             struct list_head list;
    +        unsigned int refcnt;
             int id;
             char name[32];
             int popularity;
    @@ -17,6 +18,35 @@
     static unsigned int cache_num = 0;
     #define MAX_CACHE_SIZE 10

    +static void __object_put(struct object *obj)
    +{
    +        if (--obj->refcnt == 0)
    +                kfree(obj);
    +}
    +
    +static void __object_get(struct object *obj)
    +{
    +        obj->refcnt++;
    +}
    +
    +void object_put(struct object *obj)
    +{
    +        unsigned long flags;
    +
    +        spin_lock_irqsave(&cache_lock, flags);
    +        __object_put(obj);
    +        spin_unlock_irqrestore(&cache_lock, flags);
    +}
    +
    +void object_get(struct object *obj)
    +{
    +        unsigned long flags;
    +
    +        spin_lock_irqsave(&cache_lock, flags);
    +        __object_get(obj);
    +        spin_unlock_irqrestore(&cache_lock, flags);
    +}
    +
     /* Must be holding cache_lock */
     static struct object *__cache_find(int id)
     {
    @@ -35,6 +65,7 @@
     {
             BUG_ON(!obj);
             list_del(&obj->list);
    +        __object_put(obj);
             cache_num--;
     }

    @@ -63,6 +94,7 @@
             strscpy(obj->name, name, sizeof(obj->name));
             obj->id = id;
             obj->popularity = 0;
    +        obj->refcnt = 1; /* The cache holds a reference */

             spin_lock_irqsave(&cache_lock, flags);
             __cache_add(obj);
    @@ -79,18 +111,15 @@
             spin_unlock_irqrestore(&cache_lock, flags);
     }

    -int cache_find(int id, char *name)
    +struct object *cache_find(int id)
     {
             struct object *obj;
    -        int ret = -ENOENT;
             unsigned long flags;

             spin_lock_irqsave(&cache_lock, flags);
             obj = __cache_find(id);
    -        if (obj) {
    -                ret = 0;
    -                strcpy(name, obj->name);
    -        }
    +        if (obj)
    +                __object_get(obj);
             spin_unlock_irqrestore(&cache_lock, flags);
    -        return ret;
    +        return obj;
     }

객체 포인터를 파일 밖으로 넘기는 순간에는 잠금이 보호하던 범위와 포인터의 유효 기간이 분리됩니다. `cache_lock`을 잡은 동안 찾은 주소라도 잠금을 놓은 직후 다른 실행 흐름이 `cache_delete()`를 호출하면 해제될 수 있고, 같은 주소가 새 객체에 재사용되면 호출자는 전혀 다른 객체를 가리키게 됩니다.

이 수명 문제는 참조 계수로 해결합니다. 객체를 가리키는 각 소유자는 `get`으로 계수를 올리고 사용을 마칠 때 `put`으로 내립니다. 계수가 0이 되는 순간에만 객체를 해제하므로, 호출자는 객체를 보유한 채 잠들거나 `copy_to_user()`처럼 수면 가능한 작업을 수행할 수 있습니다.

캐시 자체도 하나의 참조를 소유합니다. 따라서 새 객체를 캐시에 넣을 때 `refcnt`를 1로 시작하고, `__cache_delete()`가 목록에서 제거할 때 그 캐시 참조를 내려놓습니다. `cache_find()`는 잠금 안에서 객체를 찾은 뒤 호출자 몫의 참조를 먼저 올리고 포인터를 반환해야 합니다.

`__object_get()`과 `__object_put()`은 호출자가 이미 `cache_lock`을 보유한다는 내부 규약을 따르고, 공개 `object_get()`과 `object_put()`은 스스로 IRQ 상태를 저장해 잠금을 잡습니다. 이 구분은 중복 잠금을 피하면서도 파일 밖의 호출자에게 안전한 수명 API를 제공하려는 것입니다.

객체 포인터 공개 후 책임
문제해법
동시 필드 수정잠금 정책 또는 전용 API
잠금 해제 후 포인터 유효성참조 카운팅
캐시에서 제거캐시 참조만 감소
최종 외부 참조 해제카운터 0에서 kfree

상호 배제와 수명 관리는 서로 다른 문제입니다.

참조 카운터 수명
Insert objectrefcnt = 1 for cache
cache_findget reference
Remove from cachedrop cache reference
Last object_putrefcnt 0 -> kfree

목록 포함 여부와 객체 생존 여부를 분리합니다.

조회 경쟁 방지
spin_lock_irqsaveFind object
Object existsIncrement refcnt
spin_unlock_irqrestoreReturn stable pointer

찾기와 참조 획득을 같은 잠금 구간에 넣어 삭제 사이 틈을 없앱니다.

Accesso dal contesto utente
---------------------------

Ora consideriamo il caso in cui cache_find() può essere invocata
dal contesto d'interruzione: sia hardware che software. Un esempio potrebbe
essere un timer che elimina oggetti dalla memoria.

Qui di seguito troverete la modifica nel formato *patch*: le righe ``-``
sono quelle rimosse, mentre quelle ``+`` sono quelle aggiunte.

::

    --- cache.c.usercontext 2003-12-09 13:58:54.000000000 +1100
    +++ cache.c.interrupt   2003-12-09 14:07:49.000000000 +1100
    @@ -12,7 +12,7 @@
             int popularity;
     };

    -static DEFINE_MUTEX(cache_lock);
    +static DEFINE_SPINLOCK(cache_lock);
     static LIST_HEAD(cache);
     static unsigned int cache_num = 0;
     #define MAX_CACHE_SIZE 10
    @@ -55,6 +55,7 @@
     int cache_add(int id, const char *name)
     {
             struct object *obj;
    +        unsigned long flags;

             if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
                     return -ENOMEM;
    @@ -63,30 +64,33 @@
             obj->id = id;
             obj->popularity = 0;

    -        mutex_lock(&cache_lock);
    +        spin_lock_irqsave(&cache_lock, flags);
             __cache_add(obj);
    -        mutex_unlock(&cache_lock);
    +        spin_unlock_irqrestore(&cache_lock, flags);
             return 0;
     }

     void cache_delete(int id)
     {
    -        mutex_lock(&cache_lock);
    +        unsigned long flags;
    +
    +        spin_lock_irqsave(&cache_lock, flags);
             __cache_delete(__cache_find(id));
    -        mutex_unlock(&cache_lock);
    +        spin_unlock_irqrestore(&cache_lock, flags);
     }

     int cache_find(int id, char *name)
     {
             struct object *obj;
             int ret = -ENOENT;
    +        unsigned long flags;

    -        mutex_lock(&cache_lock);
    +        spin_lock_irqsave(&cache_lock, flags);
             obj = __cache_find(id);
             if (obj) {
                     ret = 0;
                     strcpy(name, obj->name);
             }
    -        mutex_unlock(&cache_lock);
    +        spin_unlock_irqrestore(&cache_lock, flags);
             return ret;
     }

Da notare che spin_lock_irqsave() disabiliterà le interruzioni
se erano attive, altrimenti non farà niente (quando siamo già in un contesto
d'interruzione); dunque queste funzioni possono essere chiamante in
sicurezza da qualsiasi contesto.

Sfortunatamente, cache_add() invoca kmalloc() con
l'opzione ``GFP_KERNEL`` che è permessa solo in contesto utente. Ho supposto
che cache_add() venga chiamata dal contesto utente, altrimenti
questa opzione deve diventare un parametro di cache_add().

Esporre gli oggetti al di fuori del file
----------------------------------------

Se i vostri oggetti contengono più informazioni, potrebbe non essere
sufficiente copiare i dati avanti e indietro: per esempio, altre parti del
codice potrebbero avere un puntatore a questi oggetti piuttosto che cercarli
ogni volta. Questo introduce due problemi.

Il primo problema è che utilizziamo ``cache_lock`` per proteggere gli oggetti:
dobbiamo renderlo dinamico così che il resto del codice possa usarlo. Questo
rende la sincronizzazione più complicata dato che non avviene più in un unico
posto.

Il secondo problema è il problema del ciclo di vita: se un'altra struttura
mantiene un puntatore ad un oggetto, presumibilmente si aspetta che questo
puntatore rimanga valido. Sfortunatamente, questo è garantito solo mentre
si trattiene il *lock*, altrimenti qualcuno potrebbe chiamare
cache_delete() o peggio, aggiungere un oggetto che riutilizza lo
stesso indirizzo.

Dato che c'è un solo *lock*, non potete trattenerlo a vita: altrimenti
nessun altro potrà eseguire il proprio lavoro.

La soluzione a questo problema è l'uso di un contatore di riferimenti:
chiunque punti ad un oggetto deve incrementare il contatore, e decrementarlo
quando il puntatore non viene più usato. Quando il contatore raggiunge lo zero
significa che non è più usato e l'oggetto può essere rimosso.

Ecco il codice::

    --- cache.c.interrupt   2003-12-09 14:25:43.000000000 +1100
    +++ cache.c.refcnt  2003-12-09 14:33:05.000000000 +1100
    @@ -7,6 +7,7 @@
     struct object
     {
             struct list_head list;
    +        unsigned int refcnt;
             int id;
             char name[32];
             int popularity;
    @@ -17,6 +18,35 @@
     static unsigned int cache_num = 0;
     #define MAX_CACHE_SIZE 10

    +static void __object_put(struct object *obj)
    +{
    +        if (--obj->refcnt == 0)
    +                kfree(obj);
    +}
    +
    +static void __object_get(struct object *obj)
    +{
    +        obj->refcnt++;
    +}
    +
    +void object_put(struct object *obj)
    +{
    +        unsigned long flags;
    +
    +        spin_lock_irqsave(&cache_lock, flags);
    +        __object_put(obj);
    +        spin_unlock_irqrestore(&cache_lock, flags);
    +}
    +
    +void object_get(struct object *obj)
    +{
    +        unsigned long flags;
    +
    +        spin_lock_irqsave(&cache_lock, flags);
    +        __object_get(obj);
    +        spin_unlock_irqrestore(&cache_lock, flags);
    +}
    +
     /* Must be holding cache_lock */
     static struct object *__cache_find(int id)
     {
    @@ -35,6 +65,7 @@
     {
             BUG_ON(!obj);
             list_del(&obj->list);
    +        __object_put(obj);
             cache_num--;
     }

    @@ -63,6 +94,7 @@
             strscpy(obj->name, name, sizeof(obj->name));
             obj->id = id;
             obj->popularity = 0;
    +        obj->refcnt = 1; /* The cache holds a reference */

             spin_lock_irqsave(&cache_lock, flags);
             __cache_add(obj);
    @@ -79,18 +111,15 @@
             spin_unlock_irqrestore(&cache_lock, flags);
     }

    -int cache_find(int id, char *name)
    +struct object *cache_find(int id)
     {
             struct object *obj;
    -        int ret = -ENOENT;
             unsigned long flags;

             spin_lock_irqsave(&cache_lock, flags);
             obj = __cache_find(id);
    -        if (obj) {
    -                ret = 0;
    -                strcpy(name, obj->name);
    -        }
    +        if (obj)
    +                __object_get(obj);
             spin_unlock_irqrestore(&cache_lock, flags);
    -        return ret;
    +        return obj;
     }

Abbiamo incapsulato il contatore di riferimenti nelle tipiche funzioni
di 'get' e 'put'. Ora possiamo ritornare l'oggetto da cache_find()
col vantaggio che l'utente può dormire trattenendo l'oggetto (per esempio,
copy_to_user() per copiare il nome verso lo spazio utente).

Un altro punto da notare è che ho detto che il contatore dovrebbe incrementarsi
per ogni puntatore ad un oggetto: quindi il contatore di riferimenti è 1
quando l'oggetto viene inserito nella memoria. In altre versione il framework
non trattiene un riferimento per se, ma diventa più complicato.

원자 참조 카운터와 객체별 잠금

719-890

참조 카운터는 `atomic_t`로 바꿀 수 있습니다. `include/asm/atomic.h`의 연산은 모든 CPU에서 원자적이므로 카운터 자체를 위한 spinlock이 필요 없습니다.

`atomic_inc()`는 참조를 늘리고 `atomic_dec_and_test()`는 감소 후 0인지 검사해 최종 해제를 결정합니다. 초기값은 `atomic_set(&obj->refcnt, 1)`로 설정합니다.

원자 연산은 단순 카운터에는 더 간단하지만 비단순 상태 전이에는 spinlock이 더 명확할 수 있습니다.

::

    --- cache.c.refcnt  2003-12-09 15:00:35.000000000 +1100
    +++ cache.c.refcnt-atomic   2003-12-11 15:49:42.000000000 +1100
    @@ -7,7 +7,7 @@
     struct object
     {
             struct list_head list;
    -        unsigned int refcnt;
    +        atomic_t refcnt;
             int id;
             char name[32];
             int popularity;
    @@ -18,33 +18,15 @@
     static unsigned int cache_num = 0;
     #define MAX_CACHE_SIZE 10

    -static void __object_put(struct object *obj)
    -{
    -        if (--obj->refcnt == 0)
    -                kfree(obj);
    -}
    -
    -static void __object_get(struct object *obj)
    -{
    -        obj->refcnt++;
    -}
    -
     void object_put(struct object *obj)
     {
    -        unsigned long flags;
    -
    -        spin_lock_irqsave(&cache_lock, flags);
    -        __object_put(obj);
    -        spin_unlock_irqrestore(&cache_lock, flags);
    +        if (atomic_dec_and_test(&obj->refcnt))
    +                kfree(obj);
     }

     void object_get(struct object *obj)
     {
    -        unsigned long flags;
    -
    -        spin_lock_irqsave(&cache_lock, flags);
    -        __object_get(obj);
    -        spin_unlock_irqrestore(&cache_lock, flags);
    +        atomic_inc(&obj->refcnt);
     }

     /* Must be holding cache_lock */
    @@ -65,7 +47,7 @@
     {
             BUG_ON(!obj);
             list_del(&obj->list);
    -        __object_put(obj);
    +        object_put(obj);
             cache_num--;
     }

    @@ -94,7 +76,7 @@
             strscpy(obj->name, name, sizeof(obj->name));
             obj->id = id;
             obj->popularity = 0;
    -        obj->refcnt = 1; /* The cache holds a reference */
    +        atomic_set(&obj->refcnt, 1); /* The cache holds a reference */

             spin_lock_irqsave(&cache_lock, flags);
             __cache_add(obj);
    @@ -119,7 +101,7 @@
             spin_lock_irqsave(&cache_lock, flags);
             obj = __cache_find(id);
             if (obj)
    -                __object_get(obj);
    +                object_get(obj);
             spin_unlock_irqrestore(&cache_lock, flags);
             return obj;
     }

참조 계수만 보호하려고 전역 spinlock을 잡는 비용은 `atomic_t`로 줄일 수 있습니다. `atomic_inc()`는 참조 획득을 원자적으로 수행하고, `atomic_dec_and_test()`는 감소 결과가 0인 마지막 참조인지 한 연산으로 판정하여 참일 때만 `kfree()`를 실행합니다.

변경 뒤에는 `object_get()`과 `object_put()`이 더 이상 `cache_lock`을 요구하지 않습니다. 다만 원자 연산은 참조 계수 하나의 갱신만 보장할 뿐 객체의 다른 필드나 목록 구조까지 보호하지 않으므로, 목록 삽입·삭제와 객체 내용 변경에는 각각의 잠금 규약이 계속 필요합니다.

초기화도 일반 대입 대신 `atomic_set(&obj->refcnt, 1)`을 사용합니다. 캐시가 가진 최초 참조와 조회자가 얻는 추가 참조가 같은 원자 계수에 합쳐지며, 목록에서 제거되더라도 외부 참조가 남아 있으면 메모리는 즉시 해제되지 않습니다.

객체 생성 뒤 이름 변경을 허용하려면 세 선택이 있습니다. 전역 `cache_lock`을 외부에 공개하고 호출자가 잡게 하거나, 잠금을 내부에서 잡는 `cache_obj_rename()`을 제공하거나, 캐시 구조와 객체 필드를 서로 다른 잠금으로 보호합니다.

실제 설계에서는 인프라와 모든 객체를 하나의 잠금으로 보호하거나, 리스트 연결과 인프라는 전역 잠금으로 보호하고 객체 나머지는 객체별 잠금으로 보호하거나, 여러 인프라 잠금과 객체별 잠금을 조합합니다.

객체별 잠금 버전은 `cache_lock`이 list와 popularity를 보호하고, `id`는 생성 뒤 불변이며, `obj->lock`이 name을 보호하도록 주석으로 명시합니다.

popularity를 전역 잠금 아래 두면 가장 인기 없는 객체를 찾을 때 모든 객체 잠금을 차례로 얻지 않아도 됩니다. `id`는 불변이라 조회 시 객체 잠금이 필요 없습니다.

어떤 잠금이 어떤 데이터를 보호하는지 주석으로 기록하는 것은 코드의 동작 계약입니다. 원문은 Alan Cox의 ‘Lock data, not code’라는 원칙을 인용합니다.

Qui di seguito un'implementazione con "un lock per oggetto":

::

    --- cache.c.refcnt-atomic   2003-12-11 15:50:54.000000000 +1100
    +++ cache.c.perobjectlock   2003-12-11 17:15:03.000000000 +1100
    @@ -6,11 +6,17 @@

     struct object
     {
    +        /* These two protected by cache_lock. */
             struct list_head list;
    +        int popularity;
    +
             atomic_t refcnt;
    +
    +        /* Doesn't change once created. */
             int id;
    +
    +        spinlock_t lock; /* Protects the name */
             char name[32];
    -        int popularity;
     };

     static DEFINE_SPINLOCK(cache_lock);
    @@ -77,6 +84,7 @@
             obj->id = id;
             obj->popularity = 0;
             atomic_set(&obj->refcnt, 1); /* The cache holds a reference */
    +        spin_lock_init(&obj->lock);

             spin_lock_irqsave(&cache_lock, flags);
             __cache_add(obj);

객체의 `name`을 변경 가능하게 만들 때는 세 가지 선택지가 있습니다. 전역 `cache_lock`을 외부에 공개하거나, 잠금을 내부에서 잡는 `cache_obj_rename()` 같은 함수를 제공하거나, 캐시 인프라와 객체 내용에 서로 다른 잠금을 둘 수 있습니다. 외부 공개는 규약을 넓혀 교착 분석을 어렵게 하므로 보통 캡슐화된 API가 더 낫습니다.

객체별 잠금 설계에서는 `cache_lock`이 목록 링크와 `popularity`처럼 캐시 정책에 속한 상태를 보호합니다. 각 객체의 `lock`은 변경 가능한 `name`만 보호하고, 생성 뒤 바뀌지 않는 `id`는 잠금 없이 읽을 수 있습니다. 이 경계 덕분에 최소 인기 객체를 찾을 때 모든 객체 잠금을 차례로 잡지 않아도 됩니다.

잠금이 무엇을 보호하는지는 코드 주석으로 명확히 기록해야 합니다. `/* These two protected by cache_lock. */`, `/* Doesn't change once created. */`, `/* Protects the name */` 같은 표시는 단순 설명이 아니라 유지보수자가 올바른 잠금을 선택하도록 하는 동기화 계약입니다.

원문의 원칙 `Lock data, not code`는 함수 단위로 막연히 잠그지 말고 공유 데이터와 불변식을 기준으로 잠금 범위를 정하라는 뜻입니다. 같은 함수도 서로 독립적인 데이터를 다룰 수 있고, 같은 데이터는 여러 함수에서 접근할 수 있으므로 보호 대상은 코드 위치보다 데이터 소유권으로 설명해야 합니다.

객체 필드별 보호 정책
필드보호
listcache_lock
popularitycache_lock
refcntatomic_t 연산
id생성 뒤 불변
nameobj->lock

객체별 잠금 예제의 데이터 소유권입니다.

잠금 세분화 선택
설계장점비용
전역 잠금 하나단순한 계약경쟁 범위 큼
전역 + 객체별객체별 병렬 수정잠금 순서 관리
여러 인프라 + 객체별세밀한 병렬성가장 복잡

잠금 수가 늘면 병렬성은 좋아질 수 있지만 규칙은 복잡해집니다.

원자 참조 감소
object_putatomic_dec_and_test
FalseOther references remain
Truekfree object

0으로 바뀐 단 하나의 실행 흐름만 객체를 해제합니다.

Usare operazioni atomiche per il contatore di riferimenti
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In sostanza, :c:type:`atomic_t` viene usato come contatore di riferimenti.
Ci sono un certo numbero di operazioni atomiche definite
in ``include/asm/atomic.h``: queste sono garantite come atomiche su qualsiasi
processore del sistema, quindi non sono necessari i *lock*. In questo caso è
più semplice rispetto all'uso degli spinlock, benché l'uso degli spinlock
sia più elegante per casi non banali. Le funzioni atomic_inc() e
atomic_dec_and_test() vengono usate al posto dei tipici operatori di
incremento e decremento, e i *lock* non sono più necessari per proteggere il
contatore stesso.

::

    --- cache.c.refcnt  2003-12-09 15:00:35.000000000 +1100
    +++ cache.c.refcnt-atomic   2003-12-11 15:49:42.000000000 +1100
    @@ -7,7 +7,7 @@
     struct object
     {
             struct list_head list;
    -        unsigned int refcnt;
    +        atomic_t refcnt;
             int id;
             char name[32];
             int popularity;
    @@ -18,33 +18,15 @@
     static unsigned int cache_num = 0;
     #define MAX_CACHE_SIZE 10

    -static void __object_put(struct object *obj)
    -{
    -        if (--obj->refcnt == 0)
    -                kfree(obj);
    -}
    -
    -static void __object_get(struct object *obj)
    -{
    -        obj->refcnt++;
    -}
    -
     void object_put(struct object *obj)
     {
    -        unsigned long flags;
    -
    -        spin_lock_irqsave(&cache_lock, flags);
    -        __object_put(obj);
    -        spin_unlock_irqrestore(&cache_lock, flags);
    +        if (atomic_dec_and_test(&obj->refcnt))
    +                kfree(obj);
     }

     void object_get(struct object *obj)
     {
    -        unsigned long flags;
    -
    -        spin_lock_irqsave(&cache_lock, flags);
    -        __object_get(obj);
    -        spin_unlock_irqrestore(&cache_lock, flags);
    +        atomic_inc(&obj->refcnt);
     }

     /* Must be holding cache_lock */
    @@ -65,7 +47,7 @@
     {
             BUG_ON(!obj);
             list_del(&obj->list);
    -        __object_put(obj);
    +        object_put(obj);
             cache_num--;
     }

    @@ -94,7 +76,7 @@
             strscpy(obj->name, name, sizeof(obj->name));
             obj->id = id;
             obj->popularity = 0;
    -        obj->refcnt = 1; /* The cache holds a reference */
    +        atomic_set(&obj->refcnt, 1); /* The cache holds a reference */

             spin_lock_irqsave(&cache_lock, flags);
             __cache_add(obj);
    @@ -119,7 +101,7 @@
             spin_lock_irqsave(&cache_lock, flags);
             obj = __cache_find(id);
             if (obj)
    -                __object_get(obj);
    +                object_get(obj);
             spin_unlock_irqrestore(&cache_lock, flags);
             return obj;
     }

Proteggere l'oggetto stesso
---------------------------

In questo esempio, assumiamo che gli oggetti (ad eccezione del contatore
di riferimenti) non cambino mai dopo la loro creazione. Se vogliamo permettere
al nome di cambiare abbiamo tre possibilità:

-  Si può togliere static da ``cache_lock`` e dire agli utenti che devono
   trattenere il *lock* prima di modificare il nome di un oggetto.

-  Si può fornire una funzione cache_obj_rename() che prende il
   *lock* e cambia il nome per conto del chiamante; si dirà poi agli utenti
   di usare questa funzione.

-  Si può decidere che ``cache_lock`` protegge solo la memoria stessa, ed
   un altro *lock* è necessario per la protezione del nome.

Teoricamente, possiamo avere un *lock* per ogni campo e per ogni oggetto.
In pratica, le varianti più comuni sono:

-  un *lock* che protegge l'infrastruttura (la lista ``cache`` di questo
   esempio) e gli oggetti. Questo è quello che abbiamo fatto finora.

-  un *lock* che protegge l'infrastruttura (inclusi i puntatori alla lista
   negli oggetti), e un *lock* nell'oggetto per proteggere il resto
   dell'oggetto stesso.

-  *lock* multipli per proteggere l'infrastruttura (per esempio un *lock*
   per ogni lista), possibilmente con un *lock* per oggetto.

Qui di seguito un'implementazione con "un lock per oggetto":

::

    --- cache.c.refcnt-atomic   2003-12-11 15:50:54.000000000 +1100
    +++ cache.c.perobjectlock   2003-12-11 17:15:03.000000000 +1100
    @@ -6,11 +6,17 @@

     struct object
     {
    +        /* These two protected by cache_lock. */
             struct list_head list;
    +        int popularity;
    +
             atomic_t refcnt;
    +
    +        /* Doesn't change once created. */
             int id;
    +
    +        spinlock_t lock; /* Protects the name */
             char name[32];
    -        int popularity;
     };

     static DEFINE_SPINLOCK(cache_lock);
    @@ -77,6 +84,7 @@
             obj->id = id;
             obj->popularity = 0;
             atomic_set(&obj->refcnt, 1); /* The cache holds a reference */
    +        spin_lock_init(&obj->lock);

             spin_lock_irqsave(&cache_lock, flags);
             __cache_add(obj);

Da notare che ho deciso che il contatore di popolarità dovesse essere
protetto da ``cache_lock`` piuttosto che dal *lock* dell'oggetto; questo
perché è logicamente parte dell'infrastruttura (come
:c:type:`struct list_head <list_head>` nell'oggetto). In questo modo,
in __cache_add(), non ho bisogno di trattenere il *lock* di ogni
oggetto mentre si cerca il meno popolare.

Ho anche deciso che il campo id è immutabile, quindi non ho bisogno di
trattenere il lock dell'oggetto quando si usa __cache_find()
per leggere questo campo; il *lock* dell'oggetto è usato solo dal chiamante
che vuole leggere o scrivere il campo name.

Inoltre, da notare che ho aggiunto un commento che descrive i dati che sono
protetti dal *lock*. Questo è estremamente importante in quanto descrive il
comportamento del codice, che altrimenti sarebbe di difficile comprensione
leggendo solamente il codice. E come dice Alan Cox: “Lock data, not code”.

교착과 timer 경쟁

891-1036

같은 spinlock을 두 번 얻으면 영원히 스스로 해제를 기다립니다. spinlock, rwlock, mutex는 재귀 잠금이 아닙니다.

사용자 컨텍스트와 softirq가 공유하는 데이터를 단순 `spin_lock()`으로 보호하면, 사용자가 잠금을 보유한 채 softirq에 선점되고 softirq가 같은 잠금을 회전 대기하는 단일 CPU 교착이 생길 수 있습니다.

SMP에서는 watchdog이나 `DEBUG_SPINLOCK`이 이런 문제를 찾는 데 도움을 줍니다. UP에서 spinlock이 사라지는 구성이라도 잘못된 보호 때문에 데이터 손상은 남을 수 있습니다.

둘 이상의 잠금을 반대 순서로 얻으면 ABBA 또는 ‘죽음의 포옹’ 교착이 생깁니다. CPU 1이 A를 잡고 B를 기다리는 동시에 CPU 2가 B를 잡고 A를 기다리면 둘 다 영원히 진행하지 못합니다.

ABBA 교착
단계CPU 1CPU 2
1lock A 획득lock B 획득
2lock B 대기lock A 대기
결과진행 불가진행 불가

두 CPU가 서로 상대가 보유한 잠금을 기다립니다.

교과서의 동일 잠금 순서 규칙은 필요하지만 수천 개 잠금이 있는 큰 시스템에서 전역 순서를 이해하기 어렵습니다. 더 좋은 잠금은 한 파일 안에 캡슐화되어 헤더로 노출되지 않고 외부 호출자가 존재조차 몰라도 되는 잠금입니다.

잠금을 보유한 채 callback이나 hook을 호출하면 그 코드가 무엇을 얻을지 알 수 없어 교착 위험이 큽니다.

교착을 지나치게 피하려다 읽기 잠금을 풀고 쓰기 잠금을 다시 얻는 사이 상태가 바뀌는 경쟁을 만들 수도 있습니다. 잠금 전환 사이에는 조건을 다시 검사해야 합니다.

timer가 객체를 삭제하는 목록을 모듈 제거 때 한꺼번에 해제하면 timer callback이 이미 시작된 상태와 경쟁할 수 있습니다. 단순히 `spin_lock_bh()` 안에서 timer를 지우고 객체를 해제하면 callback이 잠금 뒤에 실행되어 이미 해제된 객체를 다시 만질 수 있습니다.

Se volete eliminare l'intera collezione (diciamo quando rimuovete un modulo),
potreste fare come segue::

            /* THIS CODE BAD BAD BAD BAD: IF IT WAS ANY WORSE IT WOULD USE
               HUNGARIAN NOTATION */
            spin_lock_bh(&list_lock);

            while (list) {
                    struct foo *next = list->next;
                    timer_delete(&list->timer);
                    kfree(list);
                    list = next;
            }

            spin_unlock_bh(&list_lock);

목록 전체를 제거하는 첫 코드는 `spin_lock_bh()` 아래에서 각 timer를 삭제하고 객체를 즉시 해제하지만, 다른 CPU에서 timer 콜백이 이미 시작된 경우를 막지 못합니다. 그 콜백은 목록 잠금이 풀린 뒤 진행하여 이미 해제된 객체를 다시 삭제하려 하므로 use-after-free가 됩니다.

`timer_delete()`가 1이면 timer가 제거된 것이고, 0이면 이 문맥에서는 callback이 실행 중일 수 있습니다. 0일 때 잠금을 풀고 처음부터 재시도해 callback이 객체를 제거할 기회를 주는 예제가 제시됩니다.

Questo può essere evitato controllando il valore di ritorno di
timer_delete(): se ritorna 1, il temporizzatore è stato già
rimosso. Se 0, significa (in questo caso) che il temporizzatore è in
esecuzione, quindi possiamo fare come segue::

            retry:
                    spin_lock_bh(&list_lock);

                    while (list) {
                            struct foo *next = list->next;
                            if (!timer_delete(&list->timer)) {
                                    /* Give timer a chance to delete this */
                                    spin_unlock_bh(&list_lock);
                                    goto retry;
                            }
                            kfree(list);
                            list = next;
                    }

                    spin_unlock_bh(&list_lock);

수정된 코드는 `timer_delete()`가 1을 반환할 때만 timer가 성공적으로 제거되어 더 실행되지 않는다고 판단하고 객체를 해제합니다. 0이면 이 상황에서는 콜백이 실행 중일 수 있으므로 목록 잠금을 풀고 `retry`로 돌아가 콜백이 객체를 처리할 기회를 줍니다.

재시도 전에 잠금을 반드시 풀어야 합니다. 실행 중인 timer 콜백도 같은 `list_lock`을 얻어야 끝날 수 있는데 제거 경로가 잠금을 잡은 채 기다리면 양쪽이 서로의 진행을 막아 교착됩니다. 재검사 루프는 목록이 그 사이 바뀔 수 있음을 전제로 처음부터 다시 순회합니다.

콜백이 끝에서 스스로 `add_timer()`를 호출해 재무장하는 경우에는 단순 삭제만으로 종료를 보장할 수 없습니다. `timer_delete_sync()`는 실행 중 콜백과 동기화하고, 객체를 놓기 전 `timer_shutdown()` 또는 `timer_shutdown_sync()`를 호출하면 이후 재무장 시도를 조용히 무시하여 수명 종료를 확정합니다.

스스로 `add_timer()`로 재무장하는 timer에는 `timer_delete_sync()`를 사용합니다. 객체를 해제하기 전에는 `timer_shutdown()` 또는 `timer_shutdown_sync()`로 이후 재무장을 조용히 무시하게 해야 합니다.

ABBA 형성
CPU 1 holds Await B
CPU 2 holds Bwait A
Circular waitDeadlock

반대 잠금 순서는 각 CPU가 한 자원씩 가진 채 상대를 기다리게 합니다.

timer 삭제 경쟁 회피
Hold list_locktimer_delete
Returns 1Free object
Returns 0Unlock and retry
Final teardowntimer_shutdown_sync

실행 중 callback을 발견하면 잠금을 풀어 완료 기회를 준 뒤 다시 순회합니다.

timer 종료 API의 목적
API목적
timer_delete대기 timer 제거 여부 확인
timer_delete_sync실행 중 callback과 동기화
timer_shutdown이후 재무장 차단
timer_shutdown_sync재무장 차단과 실행 완료 동기화

삭제와 재무장 방지를 구분합니다.

Problemi comuni
===============

Stallo: semplice ed avanzato
----------------------------

Esiste un tipo di  baco dove un pezzo di codice tenta di trattenere uno
spinlock due volte: questo rimarrà in attesa attiva per sempre aspettando che
il *lock* venga rilasciato (in Linux spinlocks, rwlocks e mutex non sono
ricorsivi).
Questo è facile da diagnosticare: non è uno di quei problemi che ti tengono
sveglio 5 notti a parlare da solo.

Un caso un pochino più complesso; immaginate d'avere una spazio condiviso
fra un softirq ed il contesto utente. Se usate spin_lock() per
proteggerlo, il contesto utente potrebbe essere interrotto da un softirq
mentre trattiene il lock, da qui il softirq rimarrà in attesa attiva provando
ad acquisire il *lock* già trattenuto nel contesto utente.

Questi casi sono chiamati stalli (*deadlock*), e come mostrato qui sopra,
può succedere anche con un solo processore (Ma non sui sistemi
monoprocessore perché gli spinlock spariscano quando il kernel è compilato
con ``CONFIG_SMP``\ =n. Nonostante ciò, nel secondo caso avrete comunque
una corruzione dei dati).

Questi casi sono facili da diagnosticare; sui sistemi multi-processore
il supervisione (*watchdog*) o l'opzione di compilazione ``DEBUG_SPINLOCK``
(``include/linux/spinlock.h``) permettono di scovare immediatamente quando
succedono.

Esiste un caso più complesso che è conosciuto come l'abbraccio della morte;
questo coinvolge due o più *lock*. Diciamo che avete un vettore di hash in cui
ogni elemento è uno spinlock a cui è associata una lista di elementi con lo
stesso hash. In un gestore di interruzioni software, dovete modificare un
oggetto e spostarlo su un altro hash; quindi dovrete trattenete lo spinlock
del vecchio hash e di quello nuovo, quindi rimuovere l'oggetto dal vecchio ed
inserirlo nel nuovo.

Qui abbiamo due problemi. Primo, se il vostro codice prova a spostare un
oggetto all'interno della stessa lista, otterrete uno stallo visto che
tenterà di trattenere lo stesso *lock* due volte. Secondo, se la stessa
interruzione software su un altro processore sta tentando di spostare
un altro oggetto nella direzione opposta, potrebbe accadere quanto segue:

+---------------------------------+---------------------------------+
| CPU 1                           | CPU 2                           |
+=================================+=================================+
| Trattiene *lock* A -> OK        | Trattiene *lock* B -> OK        |
+---------------------------------+---------------------------------+
| Trattiene *lock* B -> attesa    | Trattiene *lock* A -> attesa    |
+---------------------------------+---------------------------------+

Table: Conseguenze

Entrambe i processori rimarranno in attesa attiva sul *lock* per sempre,
aspettando che l'altro lo rilasci. Sembra e puzza come un blocco totale.

Prevenire gli stalli
--------------------

I libri di testo vi diranno che se trattenete i *lock* sempre nello stesso
ordine non avrete mai un simile stallo. La pratica vi dirà che questo
approccio non funziona all'ingrandirsi del sistema: quando creo un nuovo
*lock* non ne capisco abbastanza del kernel per dire in quale dei 5000 *lock*
si incastrerà.

I *lock* migliori sono quelli incapsulati: non vengono esposti nei file di
intestazione, e non vengono mai trattenuti fuori dallo stesso file. Potete
rileggere questo codice e vedere che non ci sarà mai uno stallo perché
non tenterà mai di trattenere un altro *lock* quando lo ha già.
Le persone che usano il vostro codice non devono nemmeno sapere che voi
state usando dei *lock*.

Un classico problema deriva dall'uso di *callback* e di *hook*: se li
chiamate mentre trattenete un *lock*, rischiate uno stallo o un abbraccio
della morte (chi lo sa cosa farà una *callback*?).

Ossessiva prevenzione degli stalli
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Gli stalli sono un problema, ma non così terribile come la corruzione dei dati.
Un pezzo di codice trattiene un *lock* di lettura, cerca in una lista,
fallisce nel trovare quello che vuole, quindi rilascia il *lock* di lettura,
trattiene un *lock* di scrittura ed inserisce un oggetto; questo genere di
codice presenta una corsa critica.

corsa fra temporizzatori: un passatempo del kernel
--------------------------------------------------

I temporizzatori potrebbero avere dei problemi con le corse critiche.
Considerate una collezione di oggetti (liste, hash, eccetera) dove ogni oggetto
ha un temporizzatore che sta per distruggerlo.

Se volete eliminare l'intera collezione (diciamo quando rimuovete un modulo),
potreste fare come segue::

            /* THIS CODE BAD BAD BAD BAD: IF IT WAS ANY WORSE IT WOULD USE
               HUNGARIAN NOTATION */
            spin_lock_bh(&list_lock);

            while (list) {
                    struct foo *next = list->next;
                    timer_delete(&list->timer);
                    kfree(list);
                    list = next;
            }

            spin_unlock_bh(&list_lock);

Primo o poi, questo esploderà su un sistema multiprocessore perché un
temporizzatore potrebbe essere già partiro prima di spin_lock_bh(),
e prenderà il *lock* solo dopo spin_unlock_bh(), e cercherà
di eliminare il suo oggetto (che però è già stato eliminato).

Questo può essere evitato controllando il valore di ritorno di
timer_delete(): se ritorna 1, il temporizzatore è stato già
rimosso. Se 0, significa (in questo caso) che il temporizzatore è in
esecuzione, quindi possiamo fare come segue::

            retry:
                    spin_lock_bh(&list_lock);

                    while (list) {
                            struct foo *next = list->next;
                            if (!timer_delete(&list->timer)) {
                                    /* Give timer a chance to delete this */
                                    spin_unlock_bh(&list_lock);
                                    goto retry;
                            }
                            kfree(list);
                            list = next;
                    }

                    spin_unlock_bh(&list_lock);

Un altro problema è l'eliminazione dei temporizzatori che si riavviano
da soli (chiamando add_timer() alla fine della loro esecuzione).
Dato che questo è un problema abbastanza comune con una propensione
alle corse critiche, dovreste usare timer_delete_sync()
(``include/linux/timer.h``) per gestire questo caso.

Prima di rilasciare un temporizzatore dovreste chiamare la funzione
timer_shutdown() o timer_shutdown_sync() di modo che non venga più riarmato.
Ogni successivo tentativo di riarmare il temporizzatore verrà silenziosamente
ignorato.

잠금 성능과 읽기/쓰기 변형

1037-1092

잠금 성능은 경쟁 정도, 비경쟁 획득·해제 비용, 잠금 수를 줄이거나 더 적합한 기법을 쓰는지의 세 요소로 평가합니다.

경쟁은 잠금 보유 시간에 좌우되므로 필요한 최소 시간만 보유해야 합니다. 캐시 예제처럼 새 객체를 잠금 밖에서 만들고 목록 삽입 직전에만 잠금을 잡습니다.

획득 비용은 파이프라인 정지와 잠금 캐시라인이 현재 CPU에 있는지에 영향을 받습니다. 원문은 700MHz Pentium III의 역사적 수치로 명령 0.7ns, 원자 증가 58ns, 로컬 캐시 잠금 160ns, 다른 CPU 캐시 전송 추가 170/360ns를 제시합니다.

잠금을 더 세분화하면 보유 시간과 경쟁은 줄 수 있지만 획득 횟수와 복잡성이 늘어 오히려 단일 잠금보다 느려질 수 있습니다. 다시 단순성이 중요합니다.

spinlock과 mutex에는 읽기/쓰기 변형 `rwlock_t`와 `struct rw_semaphore`가 있습니다. 여러 reader는 동시에 읽기 잠금을 가질 수 있지만 writer는 하나만 쓰기 잠금을 가집니다.

reader와 writer 코드가 명확히 나뉘고 reader 잠금이 오래 유지되는 경우 도움이 될 수 있습니다. 변형 자체가 일반 잠금보다 약간 느려 실제로 `rwlock_t`가 이득이 아닌 경우가 많습니다.

잠금 성능의 세 축
질문
경쟁몇 실행 흐름이 기다리는가
획득 비용비경쟁 lock/unlock 자체 비용은 얼마인가
설계잠금 횟수나 공유 자체를 줄일 수 있는가

하나의 지표만 최적화하면 전체 성능이 나빠질 수 있습니다.

읽기/쓰기 잠금 허용
보유자동시 reader동시 writer
reader lock여러 명0
writer lock01

reader 병렬성과 writer 배타성을 제공합니다.

잠금 세분화의 상충
Split one lockShorter hold time
More lock acquisitionsMore overhead and ordering
Benchmark workloadChoose simpler adequate design

작은 임계 구역과 많은 획득 비용 사이에서 실제 측정이 필요합니다.

Velocità della sincronizzazione
===============================

Ci sono tre cose importanti da tenere in considerazione quando si valuta
la velocità d'esecuzione di un pezzo di codice che necessita di
sincronizzazione. La prima è la concorrenza: quante cose rimangono in attesa
mentre qualcuno trattiene un *lock*. La seconda è il tempo necessario per
acquisire (senza contese) e rilasciare un *lock*. La terza è di usare meno
*lock* o di più furbi. Immagino che i *lock* vengano usati regolarmente,
altrimenti, non sareste interessati all'efficienza.

La concorrenza dipende da quanto a lungo un *lock* è trattenuto: dovreste
trattenere un *lock* solo il tempo minimo necessario ma non un istante in più.
Nella memoria dell'esempio precedente, creiamo gli oggetti senza trattenere
il *lock*, poi acquisiamo il *lock* quando siamo pronti per inserirlo nella
lista.

Il tempo di acquisizione di un *lock* dipende da quanto danno fa
l'operazione sulla *pipeline* (ovvero stalli della *pipeline*) e quant'è
probabile che il processore corrente sia stato anche l'ultimo ad acquisire
il *lock* (in pratica, il *lock* è nella memoria cache del processore
corrente?): su sistemi multi-processore questa probabilità precipita
rapidamente. Consideriamo un processore Intel Pentium III a 700Mhz: questo
esegue un'istruzione in 0.7ns, un incremento atomico richiede 58ns, acquisire
un *lock* che è nella memoria cache del processore richiede 160ns, e un
trasferimento dalla memoria cache di un altro processore richiede altri
170/360ns (Leggetevi l'articolo di Paul McKenney's `Linux Journal RCU
article <http://www.linuxjournal.com/article.php?sid=6993>`__).

Questi due obiettivi sono in conflitto: trattenere un *lock* per il minor
tempo possibile potrebbe richiedere la divisione in più *lock* per diverse
parti (come nel nostro ultimo esempio con un *lock* per ogni oggetto),
ma questo aumenta il numero di acquisizioni di *lock*, ed il risultato
spesso è che tutto è più lento che con un singolo *lock*. Questo è un altro
argomento in favore della semplicità quando si parla di sincronizzazione.

Il terzo punto è discusso di seguito: ci sono alcune tecniche per ridurre
il numero di sincronizzazioni che devono essere fatte.

Read/Write Lock Variants
------------------------

Sia gli spinlock che i mutex hanno una variante per la lettura/scrittura
(read/write): ``rwlock_t`` e :c:type:`struct rw_semaphore <rw_semaphore>`.
Queste dividono gli utenti in due categorie: i lettori e gli scrittori.
Se state solo leggendo i dati, potete acquisire il *lock* di lettura, ma
per scrivere avrete bisogno del *lock* di scrittura. Molti possono trattenere
il *lock* di lettura, ma solo uno scrittore alla volta può trattenere
quello di scrittura.

Se il vostro codice si divide chiaramente in codice per lettori e codice
per scrittori (come nel nostro esempio), e il *lock* dei lettori viene
trattenuto per molto tempo, allora l'uso di questo tipo di *lock* può aiutare.
Questi sono leggermente più lenti rispetto alla loro versione normale, quindi
nella pratica l'uso di ``rwlock_t`` non ne vale la pena.

잠금을 피하는 Read-Copy-Update

1093-1278

Read-Copy-Update(RCU)는 reader가 일반 잠금을 얻지 않도록 하는 읽기 중심 동기화 기법입니다. reader가 writer보다 많은 캐시 예제에서 유리합니다.

writer가 reader 아래에서 목록을 바꾸더라도 reader가 새 노드를 전혀 보지 않거나 완전히 초기화된 노드만 보게 게시 순서를 보장하면 안전하게 읽을 수 있습니다.

새 노드를 연결할 때 먼저 `new->next`를 설정하고 `wmb()` 쓰기 메모리 장벽 뒤에 목록 포인터를 새 노드로 바꿉니다. 컴파일러와 CPU의 재정렬 때문에 이 순서가 필요합니다.

precauzioni. Per esempio, aggiungendo ``new`` ad una lista concatenata
chiamata ``list``::

            new->next = list->next;
            wmb();
            list->next = new;

RCU 삽입에서 `new->next`를 먼저 완성하고 `wmb()` 뒤에 `list->next = new`로 게시하는 순서는 필수입니다. 컴파일러나 CPU가 두 저장을 뒤집으면 reader가 목록에서는 새 노드를 보면서 그 노드의 다음 포인터는 아직 초기화되지 않은 상태를 관찰할 수 있습니다.

쓰기 장벽은 reader에게 두 가지 일관된 관찰만 허용하려는 장치입니다. reader는 새 노드를 아직 보지 못해 기존 목록을 따르거나, 새 노드를 보았다면 이미 올바르게 연결된 `next`까지 볼 수 있어야 합니다. `list_add_rcu()`가 이 게시 규약을 목록 API로 캡슐화합니다.

`list_add_rcu()`는 `struct list_head` 목록에 이 게시 절차를 구현합니다. 삭제는 이전 노드가 새 노드를 건너뛰게 만들며 `list_del_rcu()`가 제거된 객체를 즉시 손상시키지 않고 수행합니다.

Rimuovere un elemento dalla lista è anche più facile: sostituiamo il puntatore
al vecchio elemento con quello del suo successore, e i lettori vedranno
l'elemento o lo salteranno.

::

            list->next = old->next;

La funzione list_del_rcu() (``include/linux/list.h``) fa esattamente
questo (la versione normale corrompe il vecchio oggetto, e non vogliamo che
accada).

삭제는 새 reader가 더 이상 old 노드에 도달하지 못하도록 선행 노드의 포인터를 후속 노드로 바꿉니다. 그러나 삭제 직전에 old를 읽은 reader는 여전히 그 메모리를 사용할 수 있으므로 `list_del_rcu()` 직후 객체를 해제해서는 안 됩니다.

일반 `list_del()`은 제거한 노드의 링크를 디버깅 값 등으로 덮어쓸 수 있어 기존 reader를 깨뜨릴 수 있지만, `list_del_rcu()`는 RCU reader가 남은 순회를 끝낼 수 있는 형태를 유지합니다. reader 쪽도 의존성 순서를 지키는 `list_for_each_entry_rcu()`를 사용해야 합니다.

reader도 포인터와 다음 노드 내용을 잘못된 순서로 읽지 않도록 `list_for_each_entry_rcu()`를 사용합니다. writer는 서로 직렬화되어 있으므로 일반 `list_for_each_entry()`를 사용할 수 있습니다.

제거된 객체는 기존 reader가 여전히 보고 있을 수 있어 즉시 해제하면 안 됩니다. `call_rcu()`로 현재 reader가 끝난 뒤 실행할 callback을 등록하거나 `synchronize_rcu()`로 grace period가 끝날 때까지 기다립니다.

reader는 `rcu_read_lock()`과 `rcu_read_unlock()` 사이에서 목록을 접근합니다. 원문 설명에서 이 구간은 선점을 막아 reader가 목록을 읽는 도중 잠들지 않게 합니다.

모든 CPU가 적어도 한 번 quiescent state를 지나면 제거 시점에 존재하던 reader가 끝났다고 판단할 수 있고 callback을 실행합니다. 실제 RCU 구현은 더 최적화되어 있지만 이것이 기본 개념입니다.

RCU 패치는 `struct rcu_head`를 객체에 추가하고 검색을 `list_for_each_entry_rcu`, 삭제를 `list_del_rcu`와 `call_rcu`, 추가를 `list_add_rcu`, 조회 구간을 `rcu_read_lock/unlock`으로 바꿉니다.

::

    --- cache.c.perobjectlock   2003-12-11 17:15:03.000000000 +1100
    +++ cache.c.rcupdate    2003-12-11 17:55:14.000000000 +1100
    @@ -1,15 +1,18 @@
     #include <linux/list.h>
     #include <linux/slab.h>
     #include <linux/string.h>
    +#include <linux/rcupdate.h>
     #include <linux/mutex.h>
     #include <asm/errno.h>

     struct object
     {
    -        /* These two protected by cache_lock. */
    +        /* This is protected by RCU */
             struct list_head list;
             int popularity;

    +        struct rcu_head rcu;
    +
             atomic_t refcnt;

             /* Doesn't change once created. */
    @@ -40,7 +43,7 @@
     {
             struct object *i;

    -        list_for_each_entry(i, &cache, list) {
    +        list_for_each_entry_rcu(i, &cache, list) {
                     if (i->id == id) {
                             i->popularity++;
                             return i;
    @@ -49,19 +52,25 @@
             return NULL;
     }

    +/* Final discard done once we know no readers are looking. */
    +static void cache_delete_rcu(void *arg)
    +{
    +        object_put(arg);
    +}
    +
     /* Must be holding cache_lock */
     static void __cache_delete(struct object *obj)
     {
             BUG_ON(!obj);
    -        list_del(&obj->list);
    -        object_put(obj);
    +        list_del_rcu(&obj->list);
             cache_num--;
    +        call_rcu(&obj->rcu, cache_delete_rcu);
     }

     /* Must be holding cache_lock */
     static void __cache_add(struct object *obj)
     {
    -        list_add(&obj->list, &cache);
    +        list_add_rcu(&obj->list, &cache);
             if (++cache_num > MAX_CACHE_SIZE) {
                     struct object *i, *outcast = NULL;
                     list_for_each_entry(i, &cache, list) {
    @@ -104,12 +114,11 @@
     struct object *cache_find(int id)
     {
             struct object *obj;
    -        unsigned long flags;

    -        spin_lock_irqsave(&cache_lock, flags);
    +        rcu_read_lock();
             obj = __cache_find(id);
             if (obj)
                     object_get(obj);
    -        spin_unlock_irqrestore(&cache_lock, flags);
    +        rcu_read_unlock();
             return obj;
     }

`call_rcu()`는 현재 reader들이 끝난 뒤 실행할 회수 콜백을 등록하고 즉시 반환합니다. 반대로 `synchronize_rcu()`는 grace period가 끝날 때까지 호출자를 기다리게 합니다. 비동기 회수와 동기 대기 중 어느 쪽을 쓸지는 호출 경로가 잠들 수 있는지와 지연 요구에 따라 정합니다.

reader는 `rcu_read_lock()`과 `rcu_read_unlock()` 사이에서 목록을 순회합니다. 이 문서의 개념적 설명에서는 reader가 그 구간에서 잠들지 못하도록 하고, 모든 CPU가 적어도 한 번 quiescent state를 지나면 제거 당시 존재하던 reader가 모두 끝났다고 판단합니다. 실제 커널 구현은 이 기본 아이디어를 더 정교하게 최적화합니다.

예제의 writer는 계속 `cache_lock`으로 서로 직렬화되지만 reader인 `cache_find()`는 그 잠금 대신 RCU read-side 구간을 사용합니다. 찾은 객체에는 구간을 벗어나기 전에 `object_get()`으로 참조를 추가하므로, RCU 보호가 끝난 뒤에도 호출자가 객체를 안전하게 보유할 수 있습니다.

`popularity`는 reader들이 잠금 없이 증가하므로 갱신 손실이 생길 수 있습니다. 예제는 이 값이 퇴출 대상을 고르는 근사 통계라 정확한 합계가 필요 없다고 판단해 경합을 허용합니다. 정확성이 요구되는 상태라면 `atomic_t`나 별도 동기화를 적용해야 합니다.

호출자가 RCU read-side 구간 전체에서 선점을 비활성화한 채 객체를 사용한다면 별도 참조 계수 증가와 감소를 생략할 여지도 있습니다. 이 최적화는 객체 캐시라인에 쓰기를 만들지 않아 SMP 확장성이 좋아지지만, 사용 기간이 RCU 보호 구간을 절대 벗어나지 않는다는 강한 API 계약이 필요합니다.

reader가 `popularity`를 잠금 없이 증가시키는 경쟁은 근사값이면 충분하다는 응용 요구 때문에 허용됩니다. 정확성이 필요하면 `atomic_t` 같은 별도 보호가 필요합니다.

결과적으로 `cache_find()`는 writer와 전통적인 잠금 경쟁 없이 SMP에서도 UP에 가까운 읽기 성능을 얻습니다.

호출자가 조회부터 사용 종료까지 선점을 계속 비활성화한다면 객체 삭제가 완료될 수 없으므로 별도 참조 카운터 증가·감소를 피할 수 있습니다. 변경 쓰기가 없어져 캐시라인 이동도 줄어듭니다.

RCU 목록 연산
작업RCU API
추가list_add_rcu
reader 순회list_for_each_entry_rcu
삭제 연결list_del_rcu
지연 해제call_rcu
동기 대기synchronize_rcu

일반 목록 API 대신 게시·순회·삭제 수명을 보장하는 변형을 사용합니다.

RCU 노드 게시
Initialize new->nextWrite barrier
Publish list->next = newReader sees complete node

초기화가 전역 연결보다 먼저 관찰되도록 메모리 순서를 보장합니다.

RCU 제거와 해제
list_del_rcuNode no longer reachable by new readers
Existing readers finishRCU grace period
call_rcu callbackobject_put / free

논리적 제거와 물리적 메모리 해제 사이에 grace period가 있습니다.

RCU reader와 writer 책임
역할책임
readerrcu_read_lock 구간과 RCU 순회 API
writerwriter 직렬화, RCU 추가·삭제 API
reclaimergrace period 뒤 메모리 해제

reader는 매우 가볍지만 writer가 게시 순서와 지연 해제를 책임집니다.

Evitare i *lock*: Read Copy Update
--------------------------------------------

Esiste un metodo di sincronizzazione per letture e scritture detto
Read Copy Update. Con l'uso della tecnica RCU, i lettori possono scordarsi
completamente di trattenere i *lock*; dato che nel nostro esempio ci
aspettiamo d'avere più lettore che scrittori (altrimenti questa memoria
sarebbe uno spreco) possiamo dire che questo meccanismo permette
un'ottimizzazione.

Come facciamo a sbarazzarci dei *lock* di lettura? Sbarazzarsi dei *lock* di
lettura significa che uno scrittore potrebbe cambiare la lista sotto al naso
dei lettori. Questo è abbastanza semplice: possiamo leggere una lista
concatenata se lo scrittore aggiunge elementi alla fine e con certe
precauzioni. Per esempio, aggiungendo ``new`` ad una lista concatenata
chiamata ``list``::

            new->next = list->next;
            wmb();
            list->next = new;

La funzione wmb() è una barriera di sincronizzazione delle
scritture. Questa garantisce che la prima operazione (impostare l'elemento
``next`` del nuovo elemento) venga completata e vista da tutti i processori
prima che venga eseguita la seconda operazione (che sarebbe quella di mettere
il nuovo elemento nella lista). Questo è importante perché i moderni
compilatori ed i moderni processori possono, entrambe, riordinare le istruzioni
se non vengono istruiti altrimenti: vogliamo che i lettori non vedano
completamente il nuovo elemento; oppure che lo vedano correttamente e quindi
il puntatore ``next`` deve puntare al resto della lista.

Fortunatamente, c'è una funzione che fa questa operazione sulle liste
:c:type:`struct list_head <list_head>`: list_add_rcu()
(``include/linux/list.h``).

Rimuovere un elemento dalla lista è anche più facile: sostituiamo il puntatore
al vecchio elemento con quello del suo successore, e i lettori vedranno
l'elemento o lo salteranno.

::

            list->next = old->next;

La funzione list_del_rcu() (``include/linux/list.h``) fa esattamente
questo (la versione normale corrompe il vecchio oggetto, e non vogliamo che
accada).

Anche i lettori devono stare attenti: alcuni processori potrebbero leggere
attraverso il puntatore ``next`` il contenuto dell'elemento successivo
troppo presto, ma non accorgersi che il contenuto caricato è sbagliato quando
il puntatore ``next`` viene modificato alla loro spalle. Ancora una volta
c'è una funzione che viene in vostro aiuto list_for_each_entry_rcu()
(``include/linux/list.h``). Ovviamente, gli scrittori possono usare
list_for_each_entry() dato che non ci possono essere due scrittori
in contemporanea.

Il nostro ultimo dilemma è il seguente: quando possiamo realmente distruggere
l'elemento rimosso? Ricordate, un lettore potrebbe aver avuto accesso a questo
elemento proprio ora: se eliminiamo questo elemento ed il puntatore ``next``
cambia, il lettore salterà direttamente nella spazzatura e scoppierà. Dobbiamo
aspettare finché tutti i lettori che stanno attraversando la lista abbiano
finito. Utilizziamo call_rcu() per registrare una funzione di
richiamo che distrugga l'oggetto quando tutti i lettori correnti hanno
terminato. In alternative, potrebbe essere usata la funzione
synchronize_rcu() che blocca l'esecuzione finché tutti i lettori
non terminano di ispezionare la lista.

Ma come fa l'RCU a sapere quando i lettori sono finiti? Il meccanismo è
il seguente: innanzi tutto i lettori accedono alla lista solo fra la coppia
rcu_read_lock()/rcu_read_unlock() che disabilita la
prelazione così che i lettori non vengano sospesi mentre stanno leggendo
la lista.

Poi, l'RCU aspetta finché tutti i processori non abbiano dormito almeno
una volta; a questo punto, dato che i lettori non possono dormire, possiamo
dedurre che un qualsiasi lettore che abbia consultato la lista durante la
rimozione abbia già terminato, quindi la *callback* viene eseguita. Il vero
codice RCU è un po' più ottimizzato di così, ma questa è l'idea di fondo.

::

    --- cache.c.perobjectlock   2003-12-11 17:15:03.000000000 +1100
    +++ cache.c.rcupdate    2003-12-11 17:55:14.000000000 +1100
    @@ -1,15 +1,18 @@
     #include <linux/list.h>
     #include <linux/slab.h>
     #include <linux/string.h>
    +#include <linux/rcupdate.h>
     #include <linux/mutex.h>
     #include <asm/errno.h>

     struct object
     {
    -        /* These two protected by cache_lock. */
    +        /* This is protected by RCU */
             struct list_head list;
             int popularity;

    +        struct rcu_head rcu;
    +
             atomic_t refcnt;

             /* Doesn't change once created. */
    @@ -40,7 +43,7 @@
     {
             struct object *i;

    -        list_for_each_entry(i, &cache, list) {
    +        list_for_each_entry_rcu(i, &cache, list) {
                     if (i->id == id) {
                             i->popularity++;
                             return i;
    @@ -49,19 +52,25 @@
             return NULL;
     }

    +/* Final discard done once we know no readers are looking. */
    +static void cache_delete_rcu(void *arg)
    +{
    +        object_put(arg);
    +}
    +
     /* Must be holding cache_lock */
     static void __cache_delete(struct object *obj)
     {
             BUG_ON(!obj);
    -        list_del(&obj->list);
    -        object_put(obj);
    +        list_del_rcu(&obj->list);
             cache_num--;
    +        call_rcu(&obj->rcu, cache_delete_rcu);
     }

     /* Must be holding cache_lock */
     static void __cache_add(struct object *obj)
     {
    -        list_add(&obj->list, &cache);
    +        list_add_rcu(&obj->list, &cache);
             if (++cache_num > MAX_CACHE_SIZE) {
                     struct object *i, *outcast = NULL;
                     list_for_each_entry(i, &cache, list) {
    @@ -104,12 +114,11 @@
     struct object *cache_find(int id)
     {
             struct object *obj;
    -        unsigned long flags;

    -        spin_lock_irqsave(&cache_lock, flags);
    +        rcu_read_lock();
             obj = __cache_find(id);
             if (obj)
                     object_get(obj);
    -        spin_unlock_irqrestore(&cache_lock, flags);
    +        rcu_read_unlock();
             return obj;
     }

Da notare che i lettori modificano il campo popularity nella funzione
__cache_find(), e ora non trattiene alcun *lock*. Una soluzione
potrebbe essere quella di rendere la variabile ``atomic_t``, ma per l'uso
che ne abbiamo fatto qui, non ci interessano queste corse critiche perché un
risultato approssimativo è comunque accettabile, quindi non l'ho cambiato.

Il risultato è che la funzione cache_find() non ha bisogno di alcuna
sincronizzazione con le altre funzioni, quindi è veloce su un sistema
multi-processore tanto quanto lo sarebbe su un sistema mono-processore.

Esiste un'ulteriore ottimizzazione possibile: vi ricordate il codice originale
della nostra memoria dove non c'erano contatori di riferimenti e il chiamante
semplicemente tratteneva il *lock* prima di accedere ad un oggetto? Questo è
ancora possibile: se trattenete un *lock* nessuno potrà cancellare l'oggetto,
quindi non avete bisogno di incrementare e decrementare il contatore di
riferimenti.

Ora, dato che il '*lock* di lettura' di un RCU non fa altro che disabilitare
la prelazione, un chiamante che ha sempre la prelazione disabilitata fra le
chiamate cache_find() e object_put() non necessita
di incrementare e decrementare il contatore di riferimenti. Potremmo
esporre la funzione __cache_find() dichiarandola non-static,
e quel chiamante potrebbe usare direttamente questa funzione.

Il beneficio qui sta nel fatto che il contatore di riferimenti no
viene scritto: l'oggetto non viene alterato in alcun modo e quindi diventa
molto più veloce su sistemi molti-processore grazie alla loro memoria cache.

per-CPU와 IRQ 중심 데이터

1279-1337

동기화를 피하는 또 다른 방법은 CPU마다 정보를 복제하는 것입니다. 단일 카운터와 spinlock이 충분히 빠르면 그 단순한 설계를 우선합니다.

실측으로 병목임이 확인되면 CPU별 카운터를 두어 상호 배제를 없앨 수 있습니다. `include/linux/percpu.h`의 `DEFINE_PER_CPU()`, `get_cpu_var()`, `put_cpu_var()`를 사용합니다.

단순 CPU별 카운터에는 `include/asm/local.h`의 `local_t`, `cpu_local_inc()` 계열이 유용하고 일부 아키텍처에서 더 효율적입니다.

CPU별 값을 하나의 정확한 총합으로 읽으려면 다시 동기화가 필요해 간단하고 신뢰할 만한 방법이 없습니다. 근사 통계처럼 정확한 순간값이 필요 없는 경우에 적합합니다.

데이터가 항상 동일 IRQ 핸들러에서만 쓰이면 커널이 동일 핸들러의 CPU 간 동시 실행을 막으므로 별도 잠금이 필요 없습니다.

사용자 컨텍스트나 softirq가 아주 드물게 같은 자료를 접근한다면 그 경로에서 mutex를 잡고 `disable_irq(irq)`로 핸들러를 막은 뒤 작업하고 `enable_irq()`와 mutex 해제를 수행할 수 있습니다.

Manfred Spraul fa notare che potreste comunque comportarvi così anche
se i dati vengono occasionalmente utilizzati da un contesto utente o
da un'interruzione software. Il gestore d'interruzione non utilizza alcun
*lock*, e tutti gli altri accessi verranno fatti così::

        mutex_lock(&lock);
        disable_irq(irq);
        ...
        enable_irq(irq);
        mutex_unlock(&lock);

`disable_irq()`는 다른 CPU에서 이미 실행 중인 핸들러가 끝날 때까지 기다립니다. 이 방식은 `spin_lock_irq()`보다 느리므로 비IRQ 접근이 극히 드문 경우에만 의미가 있습니다.

공유 카운터 설계
설계장점제약
단일 카운터 + spinlock정확하고 단순공유 캐시라인 경쟁
per-CPU 카운터업데이트 무잠금정확한 합산이 어려움

성능 문제가 입증되기 전에는 단일 잠금 설계가 우선입니다.

드문 비IRQ 접근
Rare user accessmutex_lock
disable_irqWait running handler
Access dataenable_irq
mutex_unlockDone

핸들러의 빠른 경로에는 잠금을 추가하지 않고 드문 경로가 IRQ를 동기 정지합니다.

Dati per processore
-------------------

Un'altra tecnica comunemente usata per evitare la sincronizzazione è quella
di duplicare le informazioni per ogni processore. Per esempio, se volete
avere un contatore di qualcosa, potreste utilizzare uno spinlock ed un
singolo contatore. Facile e pulito.

Se questo dovesse essere troppo lento (solitamente non lo è, ma se avete
dimostrato che lo è devvero), potreste usare un contatore per ogni processore
e quindi non sarebbe più necessaria la mutua esclusione. Vedere
DEFINE_PER_CPU(), get_cpu_var() e put_cpu_var()
(``include/linux/percpu.h``).

Il tipo di dato ``local_t``, la funzione cpu_local_inc() e tutte
le altre funzioni associate, sono di particolare utilità per semplici contatori
per-processore; su alcune architetture sono anche più efficienti
(``include/asm/local.h``).

Da notare che non esiste un modo facile ed affidabile per ottenere il valore
di un simile contatore senza introdurre altri *lock*. In alcuni casi questo
non è un problema.

Dati che sono usati prevalentemente dai gestori d'interruzioni
--------------------------------------------------------------

Se i dati vengono utilizzati sempre dallo stesso gestore d'interruzioni,
allora i *lock* non vi servono per niente: il kernel già vi garantisce che
il gestore d'interruzione non verrà eseguito in contemporanea su diversi
processori.

Manfred Spraul fa notare che potreste comunque comportarvi così anche
se i dati vengono occasionalmente utilizzati da un contesto utente o
da un'interruzione software. Il gestore d'interruzione non utilizza alcun
*lock*, e tutti gli altri accessi verranno fatti così::

        mutex_lock(&lock);
        disable_irq(irq);
        ...
        enable_irq(irq);
        mutex_unlock(&lock);

La funzione disable_irq() impedisce al gestore d'interruzioni
d'essere eseguito (e aspetta che finisca nel caso fosse in esecuzione su
un altro processore). Lo spinlock, invece, previene accessi simultanei.
Naturalmente, questo è più lento della semplice chiamata
spin_lock_irq(), quindi ha senso solo se questo genere di accesso
è estremamente raro.


Quali funzioni possono essere chiamate in modo sicuro dalle interruzioni?
=========================================================================

Molte funzioni del kernel dormono (in sostanza, chiamano schedule())
direttamente od indirettamente: non potete chiamarle se trattenere uno
spinlock o avete la prelazione disabilitata, mai. Questo significa che
dovete necessariamente essere nel contesto utente: chiamarle da un
contesto d'interruzione è illegale.

IRQ 안전 함수와 mutex/futex API

1338-1408

많은 커널 함수는 직접 또는 간접으로 `schedule()`을 호출해 잠듭니다. spinlock을 보유하거나 선점이 비활성화된 상태에서는 절대 호출할 수 없으며 IRQ 컨텍스트에서도 불법입니다.

다른 호출자가 잠들 수 있는 함수라면 자신도 잠들 수 있어야 합니다. 등록·등록 해제 함수는 보통 사용자 컨텍스트를 기대해 잠들 수 있습니다.

대표 수면 함수에는 `copy_from_user()`, `copy_to_user()`, `get_user()`, `put_user()`, `kmalloc(GFP_KERNEL)`, `mutex_lock_interruptible()`, `mutex_lock()`이 있습니다.

`mutex_trylock()`은 잠들지 않지만 IRQ 안전 구현이 아니므로 인터럽트에서 사용하면 안 됩니다. `mutex_unlock()`도 잠들지 않지만 mutex는 획득한 같은 프로세스가 풀어야 하므로 IRQ에서 대신 해제할 수 없습니다.

어느 컨텍스트에서도 호출할 수 있는 대표 비수면 함수는 `printk()`, `kfree()`, `add_timer()`, `timer_delete()`입니다.

mutex API 참조는 `include/linux/mutex.h`의 내부 문서와 `kernel/locking/mutex.c`의 내보낸 문서를 `kernel-doc` 지시문으로 포함합니다.

futex API 참조는 `kernel/futex/core.c`, `kernel/futex/futex.h`, `kernel/futex/pi.c`, `kernel/futex/requeue.c`, `kernel/futex/waitwake.c`의 내부 문서를 포함합니다.

잠드는 대표 함수
범주함수
사용자 메모리copy_from_user, copy_to_user, get_user, put_user
할당kmalloc(GFP_KERNEL)
mutex 획득mutex_lock_interruptible, mutex_lock

이 함수들은 사용자 컨텍스트이며 잠금·선점 제약을 만족할 때만 호출합니다.

잠들지 않는 대표 함수
함수특징
printk어느 컨텍스트에서도 진단 출력
kfree메모리 해제
add_timertimer 예약
timer_deletetimer 제거

비수면이라는 사실과 별개로 각 API의 다른 컨텍스트 규칙도 확인해야 합니다.

kernel-doc API 소스
API소스
mutexinclude/linux/mutex.h, kernel/locking/mutex.c
futex corekernel/futex/core.c, futex.h
futex PI/requeue/waitpi.c, requeue.c, waitwake.c

페이지 하단에서 자동 추출하는 구현 문서입니다.

호출 전 수면 점검
Kernel function callMay sleep directly or indirectly?
YesRequire user context and no spinlock
NoCheck remaining IRQ-specific constraints

함수 이름만이 아니라 전체 호출 그래프가 schedule에 도달하는지 확인합니다.

Alcune funzioni che dormono
---------------------------

Le più comuni sono elencate qui di seguito, ma solitamente dovete leggere
il codice per scoprire se altre chiamate sono sicure. Se chiunque altro
le chiami dorme, allora dovreste poter dormire anche voi. In particolar
modo, le funzioni di registrazione e deregistrazione solitamente si
aspettano d'essere chiamante da un contesto utente e quindi che possono
dormire.

-  Accessi allo spazio utente:

   -  copy_from_user()

   -  copy_to_user()

   -  get_user()

   -  put_user()

-  kmalloc(GFP_KERNEL) <kmalloc>`

-  mutex_lock_interruptible() and
   mutex_lock()

   C'è anche mutex_trylock() che però non dorme.
   Comunque, non deve essere usata in un contesto d'interruzione dato
   che la sua implementazione non è sicura in quel contesto.
   Anche mutex_unlock() non dorme mai. Non può comunque essere
   usata in un contesto d'interruzione perché un mutex deve essere rilasciato
   dallo stesso processo che l'ha acquisito.

Alcune funzioni che non dormono
-------------------------------

Alcune funzioni possono essere chiamate tranquillamente da qualsiasi
contesto, o trattenendo un qualsiasi *lock*.

-  printk()

-  kfree()

-  add_timer() e timer_delete()

Riferimento per l'API dei Mutex
===============================

.. kernel-doc:: include/linux/mutex.h
   :internal:

.. kernel-doc:: kernel/locking/mutex.c
   :export:

Riferimento per l'API dei Futex
===============================

.. kernel-doc:: kernel/futex/core.c
   :internal:

.. kernel-doc:: kernel/futex/futex.h
   :internal:

.. kernel-doc:: kernel/futex/pi.c
   :internal:

.. kernel-doc:: kernel/futex/requeue.c
   :internal:

.. kernel-doc:: kernel/futex/waitwake.c
   :internal:

추가 자료와 감사의 글

1409-1436

추가 자료로 `Documentation/locking/spinlocks.rst`의 Linus Torvalds spinlock 안내서를 제시합니다.

Curt Schimmel의 ‘Unix Systems for Modern Architectures: Symmetric Multiprocessing and Caching for Kernel Programmers’도 커널 수준 SMP 동기화 입문서로 추천하며 ISBN 0201633388을 기록합니다.

Telsa Gwynne은 문서를 DocBook으로 포맷하고 정리했으며 스타일을 더했습니다.

Martin Pool, Philipp Rumpf, Stephen Rothwell, Paul Mackerras, Ruedi Aschwanden, Alan Cox, Manfred Spraul, Tim Waugh, Pete Zaitcev, James Morris, Robert Love, Paul McKenney, John Ashby가 검토·수정·논평에 기여했습니다.

추가 학습 자료
자료범위
Documentation/locking/spinlocks.rstLinux 커널 spinlock
Unix Systems for Modern ArchitecturesSMP와 커널 캐시

잠금 기초를 더 깊게 공부할 수 있는 두 자료입니다.

학습 경로
Locking guideKernel spinlocks guide
Implementation practiceSMP architecture and caching

이 안내서의 원칙을 전용 spinlock 문서와 SMP 서적으로 확장합니다.

Approfondimenti
===============

-  ``Documentation/locking/spinlocks.rst``: la guida di Linus Torvalds agli
   spinlock del kernel.

-  Unix Systems for Modern Architectures: Symmetric Multiprocessing and
   Caching for Kernel Programmers.

   L'introduzione alla sincronizzazione a livello di kernel di Curt Schimmel
   è davvero ottima (non è scritta per Linux, ma approssimativamente si adatta
   a tutte le situazioni). Il libro è costoso, ma vale ogni singolo spicciolo
   per capire la sincronizzazione nei sistemi multi-processore.
   [ISBN: 0201633388]

Ringraziamenti
==============

Grazie a Telsa Gwynne per aver formattato questa guida in DocBook, averla
pulita e aggiunto un po' di stile.

Grazie a Martin Pool, Philipp Rumpf, Stephen Rothwell, Paul Mackerras,
Ruedi Aschwanden, Alan Cox, Manfred Spraul, Tim Waugh, Pete Zaitcev,
James Morris, Robert Love, Paul McKenney, John Ashby per aver revisionato,
corretto, maledetto e commentato.

Grazie alla congrega per non aver avuto alcuna influenza su questo documento.

용어집

1437-1498

선점(preemption)은 더 높은 우선순위 프로세스가 사용자 컨텍스트의 커널 실행을 대신하는 기능입니다. 원문은 Linux 2.5.4의 `CONFIG_PREEMPT` 도입과 함께 UP에서도 spinlock이 선점을 끄도록 바뀌었다고 설명합니다.

BH(bottom half)는 역사적 소프트웨어 인터럽트 이름입니다. 함수명의 `_bh`는 현재 CPU에서 모든 소프트웨어 인터럽트를 막는다는 뜻이며, 과거 bottom half는 폐기 방향이고 한 번에 하나만 실행되었습니다.

인터럽트 컨텍스트는 사용자 컨텍스트가 아닌 하드웨어·소프트웨어 인터럽트 처리 상태이며 `in_interrupt()`가 참입니다.

사용자 컨텍스트는 특정 프로세스 또는 커널 스레드를 대신해 커널이 실행하는 상태입니다. `current`로 프로세스를 식별하며 사용자 공간 자체와는 다르고 하드·소프트 IRQ 모두에 선점될 수 있습니다.

하드웨어 인터럽트 핸들러에서는 `in_hardirq()`가 참입니다. 소프트웨어 인터럽트에서는 `in_hardirq()`가 거짓이고 `in_softirq()`가 참이며 tasklet도 소프트웨어 인터럽트로 봅니다.

softirq는 여러 CPU에서 동시에 실행될 수 있는 정적 소프트웨어 인터럽트 종류를 가리키며 문맥에 따라 tasklet을 포함한 소프트웨어 인터럽트 전체를 뜻하기도 합니다.

UP는 `CONFIG_SMP=n`인 단일 프로세서 구성이고 SMP는 `CONFIG_SMP=y`인 대칭 멀티프로세서 커널입니다.

사용자 공간은 프로세스가 커널 밖에서 자신의 코드를 실행하는 상태입니다.

tasklet은 동적 등록 가능한 소프트웨어 인터럽트로 동일 tasklet이 한 번에 한 CPU에서만 실행됩니다. timer도 동적으로 등록되며 지정 시점 근처에 실행되고 실행 중에는 tasklet과 같은 성질을 가지며 `TIMER_SOFTIRQ`에서 호출됩니다.

컨텍스트 용어
용어의미판별·구성
user context프로세스를 대신한 커널 실행current 유효
interrupt context하드·소프트 IRQ 처리in_interrupt()
hard IRQ하드웨어 인터럽트 핸들러in_hardirq()
softirq소프트웨어 인터럽트in_softirq()

원문 용어집의 실행 위치와 판별 방법을 정리합니다.

시스템 구성 용어
용어설명
UPCONFIG_SMP=n, 단일 프로세서
SMPCONFIG_SMP=y, 대칭 멀티프로세서
preemption더 높은 우선순위 실행 흐름의 선점

프로세서 수와 선점 설정을 구분합니다.

지연 실행 용어
종류등록동시성
softirq정적 종류여러 CPU 동시 가능
tasklet동적동일 인스턴스는 한 CPU
timer동적실행 시 tasklet과 유사

softirq 계열의 등록과 동시 실행 보장입니다.

Glossario
=========

prelazione
  Prima del kernel 2.5, o quando ``CONFIG_PREEMPT`` non è impostato, i processi
  in contesto utente non si avvicendano nell'esecuzione (in pratica, il
  processo userà il processore fino al proprio termine, a meno che non ci siano
  delle interruzioni). Con l'aggiunta di ``CONFIG_PREEMPT`` nella versione
  2.5.4 questo è cambiato: quando si è in contesto utente, processi con una
  priorità maggiore possono subentrare nell'esecuzione: gli spinlock furono
  cambiati per disabilitare la prelazioni, anche su sistemi monoprocessore.

bh
  Bottom Half: per ragioni storiche, le funzioni che contengono '_bh' nel
  loro nome ora si riferiscono a qualsiasi interruzione software; per esempio,
  spin_lock_bh() blocca qualsiasi interuzione software sul processore
  corrente. I *Bottom Halves* sono deprecati, e probabilmente verranno
  sostituiti dai tasklet. In un dato momento potrà esserci solo un
  *bottom half* in esecuzione.

contesto d'interruzione
  Non è il contesto utente: qui si processano le interruzioni hardware e
  software. La macro in_interrupt() ritorna vero.

contesto utente
  Il kernel che esegue qualcosa per conto di un particolare processo (per
  esempio una chiamata di sistema) o di un thread del kernel. Potete
  identificare il processo con la macro ``current``. Da non confondere
  con lo spazio utente. Può essere interrotto sia da interruzioni software
  che hardware.

interruzione hardware
  Richiesta di interruzione hardware. in_hardirq() ritorna vero in un
  gestore d'interruzioni hardware.

interruzione software / softirq
  Gestore di interruzioni software: in_hardirq() ritorna falso;
  in_softirq() ritorna vero. I tasklet e le softirq sono entrambi
  considerati 'interruzioni software'.

  In soldoni, un softirq è uno delle 32 interruzioni software che possono
  essere eseguite su più processori in contemporanea. A volte si usa per
  riferirsi anche ai tasklet (in pratica tutte le interruzioni software).

monoprocessore / UP
  (Uni-Processor) un solo processore, ovvero non è SMP. (``CONFIG_SMP=n``).

multi-processore / SMP
  (Symmetric Multi-Processor) kernel compilati per sistemi multi-processore
  (``CONFIG_SMP=y``).

spazio utente
  Un processo che esegue il proprio codice fuori dal kernel.

tasklet
  Un'interruzione software registrabile dinamicamente che ha la garanzia
  d'essere eseguita solo su un processore alla volta.

timer
  Un'interruzione software registrabile dinamicamente che viene eseguita
  (circa) in un determinato momento. Quando è in esecuzione è come un tasklet
  (infatti, sono chiamati da ``TIMER_SOFTIRQ``).