← Documents Documentation/translations/it_IT/locking/locktypes.rst GitHub 원문 ↗

Linux 6.18.37 · Translations

잠금 종류와 사용 규칙

sleeping·CPU 로컬·회전 잠금의 범주와 PREEMPT_RT 의미 변화, task 상태 보존 및 중첩 규칙을 설명합니다.

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

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

1. 요약·해설

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

요약·해설

locktypes.rst:1-547

잠금 API를 sleeping, CPU 로컬, spinning 범주로 나누고 PREEMPT_RT에서 `local_lock`, `spinlock_t`, `rwlock_t`의 구현 의미가 어떻게 달라지는지 설명합니다.

후반부는 IRQ·per-CPU 접근·task 상태 보존의 함정과 sleeping 잠금에서 raw 및 bit spinlock으로 내려가는 중첩 순서를 실제 코드로 정리합니다.

2. 영어 원문 전체

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

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

3. 한국어 전문 번역

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

잠금 범주와 소유자 의미

1-97

Kernel locking primitive는 sleeping lock, CPU local lock, spinning lock의 세 category로 나눌 수 있다. 이 문서는 각 lock을 개념적으로 설명하고 PREEMPT_RT를 포함한 nesting rule을 제시한다.

CategoryLock type기본 규칙
Sleepingmutex, rt_mutex, semaphore, rw_semaphore, ww_mutex, percpu_rw_semaphorePreemptible task context에서만 획득
CPU locallocal_lockNon-RT에서는 preemption/IRQ disable wrapper이며 inter-CPU serialization을 제공하지 않음
Spinningraw_spinlock_t, bit spinlockPreemption을 암묵적으로 disable
Non-PREEMPT_RT에서 spinningspinlock_t, rwlock_traw spinning semantics
PREEMPT_RT에서 sleeping으로 변환local_lock, spinlock_t, rwlock_trt_mutex 기반 또는 per-CPU sleeping lock semantics

Sleeping lock 구현이 다른 context에서 try_lock()을 허용하더라도 try_lock뿐 아니라 unlock 안전성과 debugging version까지 검토해야 한다. 다른 선택지가 없다면 모를까 sleeping lock을 비-task context에서 획득하지 않는 것이 원칙이다.

CPU에서 preemption 또는 interrupt를 disable하는 것은 순수한 CPU-local concurrency control이다. 다른 CPU와의 동시 접근을 직렬화하지 못한다.

Spinning lock suffix추가 보호
_bh()Bottom half, 즉 soft interrupt disable/enable
_irq()Interrupt disable/enable
_irqsave/restore()기존 interrupt-disabled state 저장 후 disable / 저장 state 복원

Semaphore를 제외한 앞의 lock type은 strict owner semantics를 갖는다. Lock을 획득한 context 또는 task가 직접 release해야 한다. rw_semaphore에는 reader를 위해 non-owner release를 허용하는 특별 interface가 있다.

커널 잠금은 잠들 수 있는 잠금, CPU 로컬 잠금, 회전 잠금으로 나뉩니다. 어떤 API가 현재 커널 설정에서 어느 범주에 속하는지를 먼저 판단해야 해당 문맥에서 선점·인터럽트·이주가 허용되는지 결정할 수 있습니다.

`mutex`, `rt_mutex`, semaphore 계열은 task 문맥에서 대기할 수 있고, `local_lock`은 비 RT 커널에서 CPU 로컬 선점 또는 IRQ 제어를 이름 있는 범위로 표현합니다. `raw_spinlock_t`와 bit spinlock은 모든 설정에서 실제 회전 잠금이며, `spinlock_t`와 `rwlock_t`는 PREEMPT_RT에서 잠들 수 있는 구현으로 바뀝니다.

semaphore의 일부 예외를 빼면 잠금을 획득한 실행 문맥이나 task가 직접 해제해야 하는 엄격한 소유자 의미를 따릅니다. 다른 문맥이 대신 해제하도록 설계하면 잠금 디버깅과 우선순위 상속의 전제가 무너집니다.

.. SPDX-License-Identifier: GPL-2.0

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

.. _it_kernel_hacking_locktypes:

========================================
Tipologie di blocco e le loro istruzioni
========================================

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

Il kernel fornisce un certo numero di primitive di blocco che possiamo dividere
in tre categorie:

  - blocchi ad attesa con sospensione
  - blocchi locali per CPU
  - blocchi ad attesa attiva

Questo documento descrive questi tre tipi e fornisce istruzioni su come
annidarli, ed usarli su kernel PREEMPT_RT.

Categorie di blocchi
====================

Blocchi ad attesa con sospensione
---------------------------------

I blocchi ad attesa con sospensione possono essere acquisiti solo in un contesti
dov'è possibile la prelazione.

Diverse implementazioni permettono di usare try_lock() anche in altri contesti,
nonostante ciò è bene considerare anche la sicurezza dei corrispondenti
unlock(). Inoltre, vanno prese in considerazione anche le varianti di *debug*
di queste primitive. Insomma, non usate i blocchi ad attesa con sospensioni in
altri contesti a meno che proprio non vi siano alternative.

In questa categoria troviamo:

 - mutex
 - rt_mutex
 - semaphore
 - rw_semaphore
 - ww_mutex
 - percpu_rw_semaphore

Nei kernel con PREEMPT_RT, i seguenti blocchi sono convertiti in blocchi ad
attesa con sospensione:

 - local_lock
 - spinlock_t
 - rwlock_t

Blocchi locali per CPU
----------------------

 - local_lock

Su kernel non-PREEMPT_RT, le funzioni local_lock gestiscono le primitive di
disabilitazione di prelazione ed interruzioni. Al contrario di altri meccanismi,
la disabilitazione della prelazione o delle interruzioni sono puri meccanismi
per il controllo della concorrenza su una CPU e quindi non sono adatti per la
gestione della concorrenza inter-CPU.

Blocchi ad attesa attiva
------------------------

 - raw_spinlcok_t
 - bit spinlocks

 Nei kernel non-PREEMPT_RT, i seguenti blocchi sono ad attesa attiva:

 - spinlock_t
 - rwlock_t

Implicitamente, i blocchi ad attesa attiva disabilitano la prelazione e le
funzioni lock/unlock hanno anche dei suffissi per gestire il livello di
protezione:

 ===================  =========================================================================
 _bh()                disabilita / abilita  *bottom halves* (interruzioni software)
 _irq()               disabilita / abilita le interruzioni
 _irqsave/restore()   salva e disabilita le interruzioni / ripristina ed attiva le interruzioni
 ===================  =========================================================================

Semantica del proprietario
==========================

Eccetto i semafori, i sopracitati tipi di blocchi hanno tutti una semantica
molto stringente riguardo al proprietario di un blocco:

  Il contesto (attività) che ha acquisito il blocco deve rilasciarlo

I semafori rw_semaphores hanno un'interfaccia speciale che permette anche ai non
proprietari del blocco di rilasciarlo per i lettori.

rtmutex와 semaphore

98-132

RT-mutex는 priority inheritance(PI)를 지원하는 mutex다. Non-PREEMPT_RT kernel에서는 preemption-disabled와 interrupt-disabled section 때문에 PI에 한계가 있다.

PREEMPT_RT에서도 PI가 preemption-disabled 또는 interrupt-disabled code region을 preempt할 수는 없다. 대신 PREEMPT_RT는 interrupt handler와 soft interrupt를 포함한 이런 region 대부분을 preemptible task context에서 실행하도록 변환한다. 이 변환 덕분에 spinlock_t와 rwlock_t를 RT-mutex로 구현할 수 있다.

Semaphore는 counting semaphore 구현이다. Serialization과 waiting 두 목적으로 자주 쓰였지만 새 use case는 mutex와 completion처럼 serialization mechanism과 wait mechanism을 분리해야 한다.

PREEMPT_RT는 semaphore 구현을 바꾸지 않는다. Counting semaphore에는 owner 개념이 없어 priority inheritance를 제공할 수 없기 때문이다. 알 수 없는 owner의 priority는 boost할 수 없다. 따라서 semaphore에서 block하면 priority inversion이 생길 수 있다.

`rt_mutex`는 소유자가 있는 mutex이므로 높은 우선순위 대기자의 우선순위를 소유자에게 전달할 수 있습니다. PREEMPT_RT는 IRQ와 softirq 작업 대부분을 선점 가능한 task 문맥으로 옮겨 이 우선순위 상속이 작동할 수 있는 영역을 넓힙니다.

counting semaphore는 소유자가 정해지지 않아 우선순위 상속 대상을 알 수 없습니다. 따라서 새 코드는 직렬화에는 mutex를, 사건 대기에는 completion을 사용해 두 역할을 분리해야 하며, PREEMPT_RT에서도 semaphore 구현과 역전 위험은 그대로 남습니다.

rtmutex
=======

I blocchi a mutua esclusione RT (*rtmutex*) sono un sistema a mutua esclusione
con supporto all'ereditarietà della priorità (PI).

Questo meccanismo ha delle limitazioni sui kernel non-PREEMPT_RT dovuti alla
prelazione e alle sezioni con interruzioni disabilitate.

Chiaramente, questo meccanismo non può avvalersi della prelazione su una sezione
dove la prelazione o le interruzioni sono disabilitate; anche sui kernel
PREEMPT_RT. Tuttavia, i kernel PREEMPT_RT eseguono la maggior parte delle
sezioni in contesti dov'è possibile la prelazione, specialmente in contesti
d'interruzione (anche software). Questa conversione permette a spinlock_t e
rwlock_t di essere implementati usando rtmutex.

semaphore
=========

La primitiva semaphore implementa un semaforo con contatore.

I semafori vengono spesso utilizzati per la serializzazione e l'attesa, ma per
nuovi casi d'uso si dovrebbero usare meccanismi diversi, come mutex e
completion.

semaphore e PREEMPT_RT
----------------------

I kernel PREEMPT_RT non cambiano l'implementazione di semaphore perché non hanno
un concetto di proprietario, dunque impediscono a PREEMPT_RT d'avere
l'ereditarietà della priorità sui semafori. Un proprietario sconosciuto non può
ottenere una priorità superiore. Di consequenza, bloccarsi sui semafori porta
all'inversione di priorità.

rw_semaphore와 PREEMPT_RT

133-160

rw_semaphore는 여러 reader 또는 한 writer를 허용하는 lock mechanism이다. Non-PREEMPT_RT 구현은 fair하므로 writer starvation을 막는다. 기본적으로 strict owner semantics를 따르지만 reader용 non-owner release special-purpose interface가 있으며 kernel configuration과 무관하게 동작한다.

PREEMPT_RT는 rw_semaphore를 별도의 rt_mutex 기반 구현으로 mapping하여 fairness를 바꾼다. rw_semaphore writer는 여러 reader에게 자기 priority를 넘겨줄 수 없다. 따라서 preempt된 low-priority reader가 lock을 계속 보유하면 high-priority writer도 starvation될 수 있다.

반대 방향에서는 reader들이 writer에게 priority를 전달할 수 있다. Preempt된 low-priority writer는 lock을 release할 때까지 priority가 boost되므로 그 writer 때문에 reader가 starvation되는 일을 막는다.

`rw_semaphore`는 여러 reader 또는 한 writer를 허용합니다. 비 RT 구현은 writer 기아를 막는 공정성을 제공하지만 PREEMPT_RT의 rt_mutex 기반 구현에서는 한 writer가 여러 reader에게 우선순위를 동시에 전달할 수 없어 낮은 우선순위 reader가 높은 우선순위 writer를 지연시킬 수 있습니다.

반대 방향에서는 reader들이 단일 writer에게 우선순위를 전달할 수 있으므로 선점된 낮은 우선순위 writer가 해제 시점까지 boost됩니다. 읽기와 쓰기 방향의 지연 보장이 대칭이 아니라는 점을 실시간 설계에서 고려해야 합니다.

rw_semaphore
============

Il blocco rw_semaphore è un meccanismo che permette più lettori ma un solo scrittore.

Sui kernel non-PREEMPT_RT l'implementazione è imparziale, quindi previene
l'inedia dei processi scrittori.

Questi blocchi hanno una semantica molto stringente riguardo il proprietario, ma
offre anche interfacce speciali che permettono ai processi non proprietari di
rilasciare un processo lettore. Queste interfacce funzionano indipendentemente
dalla configurazione del kernel.

rw_semaphore e PREEMPT_RT
-------------------------

I kernel PREEMPT_RT sostituiscono i rw_semaphore con un'implementazione basata
su rt_mutex, e questo ne modifica l'imparzialità:

 Dato che uno scrittore rw_semaphore non può assicurare la propria priorità ai
 suoi lettori, un lettore con priorità più bassa che ha subito la prelazione
 continuerà a trattenere il blocco, quindi porta all'inedia anche gli scrittori
 con priorità più alta. Per contro, dato che i lettori possono garantire la
 propria priorità agli scrittori, uno scrittore a bassa priorità che subisce la
 prelazione vedrà la propria priorità alzata finché non rilascerà il blocco, e
 questo preverrà l'inedia dei processi lettori a causa di uno scrittore.

local_lock의 CPU 로컬 범위

161-212

local_lock은 preemption 또는 interrupt disable로 보호하는 critical section에 이름 있는 scope를 제공한다. Non-PREEMPT_RT에서 operation은 다음 primitive로 mapping된다.

local_lock APINon-PREEMPT_RT mapping
local_lock(&llock)preempt_disable()
local_unlock(&llock)preempt_enable()
local_lock_irq(&llock)local_irq_disable()
local_unlock_irq(&llock)local_irq_enable()
local_lock_irqsave(&llock)local_irq_save()
local_unlock_irqrestore(&llock)local_irq_restore()

이름 있는 scope에는 두 장점이 있다. Lock name은 static analysis에 사용할 수 있고 보호 범위를 분명히 문서화한다. 일반 preemption·IRQ primitive는 이름과 scope가 없어 어떤 data를 보호하는지 알기 어렵다.

Lockdep를 enable하면 local_lock에 lockmap이 생겨 보호가 올바른지 검증할 수 있다. 예를 들어 preempt_disable()로 보호하는 function이 interrupt 또는 soft-interrupt context에서 불리는 오류를 찾는다. lockdep_assert_held(&llock)도 다른 locking primitive와 같은 방식으로 사용할 수 있다.

PREEMPT_RT는 local_lock을 per-CPU spinlock_t로 mapping하므로 spinlock_t의 모든 의미 변화가 local_lock에도 적용된다. Local_lock은 non-RT에서 per-CPU data structure를 보호하기 위해 preemption 또는 interrupt disable이 적절한 경우에 사용한다. RT에서는 spinlock_t semantics 때문에 preemption이나 interrupt 자체를 막는 용도로 사용할 수 없다.

`local_lock`은 익명의 `preempt_disable()` 또는 IRQ disable 구간에 이름을 부여하여 보호 대상을 문서화하고 lockdep 검증을 가능하게 합니다. 비 RT에서는 대응 선점·IRQ primitive로 치환되고, PREEMPT_RT에서는 CPU별 `spinlock_t`로 구현됩니다.

따라서 `local_lock`을 단순히 인터럽트를 끄는 도구로 해석하면 안 됩니다. 같은 보호 데이터를 만지는 모든 호출자가 동일한 lock 객체를 사용해야 하며, callee는 `lockdep_assert_held()`로 그 명시적 범위를 검사해야 RT와 비 RT에서 같은 규약을 유지할 수 있습니다.

local_lock
==========

I local_lock forniscono nomi agli ambiti di visibilità delle sezioni critiche
protette tramite la disattivazione della prelazione o delle interruzioni.

Sui kernel non-PREEMPT_RT le operazioni local_lock si traducono
nell'abilitazione o disabilitazione della prelazione o le interruzioni.

 ===============================  ======================
 local_lock(&llock)               preempt_disable()
 local_unlock(&llock)             preempt_enable()
 local_lock_irq(&llock)           local_irq_disable()
 local_unlock_irq(&llock)         local_irq_enable()
 local_lock_irqsave(&llock)       local_irq_save()
 local_unlock_irqrestore(&llock)  local_irq_restore()
 ===============================  ======================

Gli ambiti di visibilità con nome hanno due vantaggi rispetto alle primitive di
base:

  - Il nome del blocco permette di fare un'analisi statica, ed è anche chiaro su
    cosa si applichi la protezione cosa che invece non si può fare con le
    classiche primitive in quanto sono opache e senza alcun ambito di
    visibilità.

  - Se viene abilitato lockdep, allora local_lock ottiene un lockmap che
    permette di verificare la bontà della protezione. Per esempio, questo può
    identificare i casi dove una funzione usa preempt_disable() come meccanismo
    di protezione in un contesto d'interruzione (anche software). A parte
    questo, lockdep_assert_held(&llock) funziona come tutte le altre primitive
    di sincronizzazione.

local_lock e PREEMPT_RT
-------------------------

I kernel PREEMPT_RT sostituiscono local_lock con uno spinlock_t per CPU, quindi
ne cambia la semantica:

  - Tutte le modifiche a spinlock_t si applicano anche a local_lock

L'uso di local_lock
-------------------

I local_lock dovrebbero essere usati su kernel non-PREEMPT_RT quando la
disabilitazione della prelazione o delle interruzioni è il modo più adeguato per
gestire l'accesso concorrente a strutture dati per CPU.

Questo meccanismo non è adatto alla protezione da prelazione o interruzione su
kernel PREEMPT_RT dato che verrà convertito in spinlock_t.

raw_spinlock_t, spinlock_t와 task 상태 보존

213-303

raw_spinlock_t는 PREEMPT_RT를 포함한 모든 kernel에서 strict spinning lock이다. Real critical core code, low-level interrupt handling, hardware state에 안전하게 접근하기 위해 preemption 또는 interrupt disable이 반드시 필요한 곳에서만 사용한다. Critical section이 극히 작아 RT-mutex overhead를 피하려는 경우에도 제한적으로 쓸 수 있다.

spinlock_t 의미는 PREEMPT_RT 여부에 따라 달라진다. Non-PREEMPT_RT에서는 raw_spinlock_t에 mapping되어 완전히 같은 semantics를 갖는다.

PREEMPT_RT에서는 spinlock_t가 rt_mutex 기반 별도 구현으로 mapping된다. Preemption을 disable하지 않으며 spin_lock/spin_unlock의 _irq, _irqsave/_irqrestore suffix도 CPU interrupt-disabled state를 바꾸지 않는다.

_bh suffix는 여전히 softirq handler를 disable한다. Non-RT는 preemption을 disable해 이 효과를 얻지만 PREEMPT_RT는 preemption을 유지하면서 per-CPU lock으로 serialization한다. 이 lock은 softirq handler를 disable하고 task preemption에 의한 reentrancy도 막는다.

그 밖의 spinlock_t semantics는 유지된다. Lock을 가진 task는 migrate하지 않는다. Non-RT는 preemption disable로 migration을 막고 PREEMPT_RT는 migration만 disable하여 task가 preempt되더라도 per-CPU variable pointer가 유효하도록 보장한다.

Spinlock 획득 전후의 task state도 모든 kernel configuration에서 보존해야 한다. Non-PREEMPT_RT는 task state를 건드리지 않는다. 하지만 PREEMPT_RT에서는 rt_mutex 기반 spinlock 획득 중 task가 block할 수 있어 state를 바꿔야 한다. Block 전에 current state를 saved_state에 저장하고 lock wakeup이 이를 복원한다.

그림 1. Lock wakeup이 saved_state를 복원하는 순서
Waiting taskLock wakeup
01 task->state = TASK_INTERRUPTIBLE
02 lock() → block()
03 saved_state = TASK_INTERRUPTIBLE
04 state = TASK_UNINTERRUPTIBLE → schedule()
05 lock available
06 task->state = task->saved_state

TASK_INTERRUPTIBLE 상태에서 lock을 기다리면 scheduler block을 위해 TASK_UNINTERRUPTIBLE로 잠시 바꾼다. Lock이 가능해지면 wakeup path가 original saved_state를 되돌린다.

일반 wakeup은 보통 task state를 무조건 RUNNING으로 바꾼다. 그러나 spinlock을 기다리며 block된 task에 그렇게 하면 lock이 가능하기 전에 실행될 수 있어 안 된다. 따라서 non-lock wakeup은 실제 state가 아니라 saved_state를 RUNNING으로 바꾼다. Lock 획득이 끝나면 lock wakeup이 saved_state를 current state에 복원한다.

그림 2. 일반 wakeup을 잃지 않고 lock 대기를 계속하는 순서
Waiting taskWakeup paths
01 state = TASK_INTERRUPTIBLE
02 lock() → block()
03 saved_state = TASK_INTERRUPTIBLE
04 state = TASK_UNINTERRUPTIBLE → schedule()
05 non-lock wakeup: saved_state = TASK_RUNNING
06 lock wakeup: state = saved_state
07 state = TASK_RUNNING

일반 wakeup은 task를 즉시 runnable로 만들지 않고 saved_state에 TASK_RUNNING을 기록한다. Lock wakeup이 뒤에 이 값을 복원하므로 실제 wakeup event가 사라지지 않는다.

이 two-stage state update 덕분에 실제 wakeup event가 lock wait에 가려져 유실되지 않는다.

`raw_spinlock_t`는 모든 커널에서 선점을 막는 실제 회전 잠금이므로 하드웨어 상태와 매우 짧은 저수준 임계 구역에 한정합니다. 반면 PREEMPT_RT의 `spinlock_t`는 rt_mutex 기반이고 선점을 막지 않으며 `_irq`와 `_irqsave` 접미사도 CPU IRQ 상태를 직접 바꾸지 않습니다.

RT의 spinlock 대기는 task를 block시킬 수 있으므로 원래 task 상태를 `saved_state`에 보관합니다. 일반 wakeup은 이 저장 상태를 `TASK_RUNNING`으로 바꾸고, 잠금 wakeup이 실제 state로 복원하여 잠금 대기 중 발생한 wakeup 사건이 유실되지 않게 합니다.

raw_spinlock_t e spinlock_t
===========================

raw_spinlock_t
--------------

I blocco raw_spinlock_t è un blocco ad attesa attiva su tutti i tipi di kernel,
incluso quello PREEMPT_RT. Usate raw_spinlock_t solo in sezioni critiche nel
cuore del codice, nella gestione delle interruzioni di basso livello, e in posti
dove è necessario disabilitare la prelazione o le interruzioni. Per esempio, per
accedere in modo sicuro lo stato dell'hardware. A volte, i raw_spinlock_t
possono essere usati quando la sezione critica è minuscola, per evitare gli
eccessi di un rtmutex.

spinlock_t
----------

Il significato di spinlock_t cambia in base allo stato di PREEMPT_RT.

Sui kernel non-PREEMPT_RT, spinlock_t si traduce in un raw_spinlock_t ed ha
esattamente lo stesso significato.

spinlock_t e PREEMPT_RT
-----------------------

Sui kernel PREEMPT_RT, spinlock_t ha un'implementazione dedicata che si basa
sull'uso di rt_mutex. Questo ne modifica il significato:

 - La prelazione non viene disabilitata.

 - I suffissi relativi alla interruzioni (_irq, _irqsave / _irqrestore) per le
   operazioni spin_lock / spin_unlock non hanno alcun effetto sullo stato delle
   interruzioni della CPU.

 - I suffissi relativi alle interruzioni software (_bh()) disabilitano i
   relativi gestori d'interruzione.

   I kernel non-PREEMPT_RT disabilitano la prelazione per ottenere lo stesso effetto.

   I kernel PREEMPT_RT usano un blocco per CPU per la serializzazione, il che
   permette di tenere attiva la prelazione. Il blocco disabilita i gestori
   d'interruzione software e previene la rientranza vista la prelazione attiva.

A parte quanto appena discusso, i kernel PREEMPT_RT preservano il significato
di tutti gli altri aspetti di spinlock_t:

 - Le attività che trattengono un blocco spinlock_t non migrano su altri
   processori. Disabilitando la prelazione, i kernel non-PREEMPT_RT evitano la
   migrazione. Invece, i kernel PREEMPT_RT disabilitano la migrazione per
   assicurarsi che i puntatori a variabili per CPU rimangano validi anche
   quando un'attività subisce la prelazione.

 - Lo stato di un'attività si mantiene durante le acquisizioni del blocco al
   fine di garantire che le regole basate sullo stato delle attività si possano
   applicare a tutte le configurazioni del kernel. I kernel non-PREEMPT_RT
   lasciano lo stato immutato. Tuttavia, la funzionalità PREEMPT_RT deve
   cambiare lo stato se l'attività si blocca durante l'acquisizione. Dunque,
   salva lo stato attuale prima di bloccarsi ed il rispettivo risveglio lo
   ripristinerà come nell'esempio seguente::

    task->state = TASK_INTERRUPTIBLE
     lock()
       block()
         task->saved_state = task->state
	 task->state = TASK_UNINTERRUPTIBLE
	 schedule()
					lock wakeup
					  task->state = task->saved_state

   Altri tipi di risvegli avrebbero impostato direttamente lo stato a RUNNING,
   ma in questo caso non avrebbe funzionato perché l'attività deve rimanere
   bloccata fintanto che il blocco viene trattenuto. Quindi, lo stato salvato
   viene messo a RUNNING quando il risveglio di un non-blocco cerca di
   risvegliare un'attività bloccata in attesa del rilascio di uno spinlock. Poi,
   quando viene completata l'acquisizione del blocco, il suo risveglio
   ripristinerà lo stato salvato, in questo caso a RUNNING::

    task->state = TASK_INTERRUPTIBLE
     lock()
       block()
         task->saved_state = task->state
	 task->state = TASK_UNINTERRUPTIBLE
	 schedule()
					non lock wakeup
					  task->saved_state = TASK_RUNNING

					lock wakeup
					  task->state = task->saved_state

   Questo garantisce che il vero risveglio non venga perso.

rwlock_t의 구현별 공정성

304-330

rwlock_t는 여러 reader 또는 한 writer를 허용한다. Non-PREEMPT_RT에서는 spinning lock으로 구현되어 spinlock_t suffix rule이 적용되고 fair implementation이 writer starvation을 막는다.

PREEMPT_RT는 rwlock_t를 별도 rt_mutex 기반 구현으로 mapping한다. Spinlock_t의 모든 변화가 rwlock_t에도 적용된다.

Writer는 여러 reader에게 priority를 전달할 수 없으므로 preempt된 low-priority reader가 lock을 계속 보유해 high-priority writer를 starvation시킬 수 있다. 반대로 reader는 writer에게 priority를 전달할 수 있어 preempt된 low-priority writer는 release할 때까지 boost되고 reader starvation을 막는다.

비 RT의 `rwlock_t`는 회전 잠금이며 writer 기아를 막는 공정성을 갖습니다. PREEMPT_RT에서는 rt_mutex 기반으로 바뀌어 `spinlock_t`와 같은 선점·이주 의미를 따르며, 여러 reader와 단일 writer 사이의 우선순위 전달 한계도 `rw_semaphore`와 유사합니다.

rwlock_t
========

Il blocco rwlock_t è un meccanismo che permette più lettori ma un solo scrittore.

Sui kernel non-PREEMPT_RT questo è un blocco ad attesa e per i suoi suffissi si
applicano le stesse regole per spinlock_t. La sua implementazione è imparziale,
quindi previene l'inedia dei processi scrittori.

rwlock_t e PREEMPT_RT
---------------------

Sui kernel PREEMPT_RT rwlock_t ha un'implementazione dedicata che si basa
sull'uso di rt_mutex. Questo ne modifica il significato:

 - Tutte le modifiche fatte a spinlock_t si applicano anche a rwlock_t.

 - Dato che uno scrittore rw_semaphore non può assicurare la propria priorità ai
   suoi lettori, un lettore con priorità più bassa che ha subito la prelazione
   continuerà a trattenere il blocco, quindi porta all'inedia anche gli
   scrittori con priorità più alta. Per contro, dato che i lettori possono
   garantire la propria priorità agli scrittori, uno scrittore a bassa priorità
   che subisce la prelazione vedrà la propria priorità alzata finché non
   rilascerà il blocco, e questo preverrà l'inedia dei processi lettori a causa
   di uno scrittore.

PREEMPT_RT의 local_lock 변환 주의점

331-406

Non-PREEMPT_RT에서 다음 sequence는 local_lock_irq()가 interrupt를 disable하므로 기대대로 동작하며 raw_spin_lock_irq(&lock)와 같다.

local_lock_irq(&local_lock);
raw_spin_lock(&lock);

/* non-RT에서 동등 */
raw_spin_lock_irq(&lock);

PREEMPT_RT에서는 local_lock_irq()가 interrupt나 preemption을 disable하지 않는 per-CPU spinlock_t로 mapping되어 이 sequence가 깨진다. 다음처럼 일반 spin_lock을 nesting하면 RT와 non-RT 모두 올바르다.

local_lock_irq(&local_lock);
spin_lock(&lock);

또 다른 함정은 local_lock마다 고유한 protection scope가 있다는 점이다. 두 caller가 같은 func3()의 protected data에 접근하면서 서로 다른 local_lock_1과 local_lock_2를 사용하면 RT에서 두 caller를 직렬화하지 못한다. local_lock_irqsave()가 interrupt를 실제로 disable하지 않으므로 lockdep_assert_irqs_disabled()도 fail한다.

/* 잘못된 변환: 서로 다른 scope */
func1()
{
  local_lock_irqsave(&local_lock_1, flags);
  func3();
  local_unlock_irqrestore(&local_lock_1, flags);
}

func2()
{
  local_lock_irqsave(&local_lock_2, flags);
  func3();
  local_unlock_irqrestore(&local_lock_2, flags);
}

func3()
{
  lockdep_assert_irqs_disabled();
  access_protected_data();
}

올바른 변환은 두 caller가 같은 local_lock을 사용하고 callee가 lockdep_assert_held()로 그 lock을 확인하는 것이다.

func1()
{
  local_lock_irqsave(&local_lock, flags);
  func3();
  local_unlock_irqrestore(&local_lock, flags);
}

func2()
{
  local_lock_irqsave(&local_lock, flags);
  func3();
  local_unlock_irqrestore(&local_lock, flags);
}

func3()
{
  lockdep_assert_held(&local_lock);
  access_protected_data();
}

비 RT에서 `local_lock_irq()` 뒤 `raw_spin_lock()`을 쓰면 IRQ가 이미 꺼져 있지만, RT의 local lock은 IRQ를 끄지 않으므로 같은 코드는 안전하지 않습니다. 일반 `spin_lock()`을 중첩하거나 실제 IRQ 제어가 필요한 저수준 잠금 API를 명시적으로 선택해야 합니다.

서로 다른 `local_lock_1`과 `local_lock_2`로 같은 보호 함수에 들어가는 변환도 RT에서 직렬화를 제공하지 않습니다. 공통 `local_lock` 하나로 범위를 통합하고 `lockdep_assert_held()`로 확인해야 두 커널 설정에서 동일한 보호 관계가 성립합니다.

Precisazioni su PREEMPT_RT
==========================

local_lock su RT
----------------

Sui kernel PREEMPT_RT Ci sono alcune implicazioni dovute alla conversione di
local_lock in un spinlock_t. Per esempio, su un kernel non-PREEMPT_RT il
seguente codice funzionerà come ci si aspetta::

  local_lock_irq(&local_lock);
  raw_spin_lock(&lock);

ed è equivalente a::

   raw_spin_lock_irq(&lock);

Ma su un kernel PREEMPT_RT questo codice non funzionerà perché local_lock_irq()
si traduce in uno spinlock_t per CPU che non disabilita né le interruzioni né la
prelazione. Il seguente codice funzionerà su entrambe i kernel con o senza
PREEMPT_RT::

  local_lock_irq(&local_lock);
  spin_lock(&lock);

Un altro dettaglio da tenere a mente con local_lock è che ognuno di loro ha un
ambito di protezione ben preciso. Dunque, la seguente sostituzione è errate::


  func1()
  {
    local_irq_save(flags);    -> local_lock_irqsave(&local_lock_1, flags);
    func3();
    local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock_1, flags);
  }

  func2()
  {
    local_irq_save(flags);    -> local_lock_irqsave(&local_lock_2, flags);
    func3();
    local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock_2, flags);
  }

  func3()
  {
    lockdep_assert_irqs_disabled();
    access_protected_data();
  }

Questo funziona correttamente su un kernel non-PREEMPT_RT, ma su un kernel
PREEMPT_RT local_lock_1 e local_lock_2 sono distinti e non possono serializzare
i chiamanti di func3(). L'*assert* di lockdep verrà attivato su un kernel
PREEMPT_RT perché local_lock_irqsave() non disabilita le interruzione a casa
della specifica semantica di spinlock_t in PREEMPT_RT. La corretta sostituzione
è::

  func1()
  {
    local_irq_save(flags);    -> local_lock_irqsave(&local_lock, flags);
    func3();
    local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock, flags);
  }

  func2()
  {
    local_irq_save(flags);    -> local_lock_irqsave(&local_lock, flags);
    func3();
    local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock, flags);
  }

  func3()
  {
    lockdep_assert_held(&local_lock);
    access_protected_data();
  }

IRQ, per-CPU 포인터와 RT 잠금 중첩

407-481

Non-PREEMPT_RT에서 local_irq_disable() 뒤 spin_lock()을 호출하는 sequence는 spin_lock_irq()와 같다. rwlock_t와 _irqsave suffix도 마찬가지다.

local_irq_disable();
spin_lock(&lock);

/* non-RT에서 동등 */
spin_lock_irq(&lock);

PREEMPT_RT에서는 RT-mutex가 fully preemptible context를 요구하므로 첫 sequence가 깨진다. 대신 spin_lock_irq() 또는 spin_lock_irqsave()와 대응 unlock을 사용한다. IRQ disable과 locking을 분리해야 한다면 local_lock으로 task를 CPU에 pin한 뒤 per-CPU interrupt-disabled lock 등을 획득할 수 있지만 반드시 필요한 경우에만 사용한다.

Thread context에서 per-CPU variable을 보호하는 다음 code는 non-RT에서는 맞지만 RT에서는 get_cpu_ptr()가 암묵적으로 preemption을 disable하기 때문에 p->lock을 획득할 수 없다.

struct foo *p = get_cpu_ptr(&var1);

spin_lock(&p->lock);
p->count += this_cpu_read(var2);

두 kernel에서 모두 동작하려면 migration만 disable하고 task는 preemptible하게 유지한다. migrate_disable()은 task를 current CPU에 pin하여 var1과 var2의 per-CPU access가 같은 CPU에 남게 한다.

struct foo *p;

migrate_disable();
p = this_cpu_ptr(&var1);
spin_lock(&p->lock);
p->count += this_cpu_read(var2);

그러나 migrate_disable()은 preempting task의 reentrancy를 막지 못한다. Function이 per-CPU data를 읽은 뒤 preempt될 수 있고 다른 task가 같은 data에 다시 들어올 수 있는 경우에는 local_lock을 사용해야 한다.

func()
{
  struct foo *p;

  local_lock(&foo_lock);
  p = this_cpu_ptr(&var1);
  p->val = func2();
}

Non-RT에서는 local_lock이 preemption을 disable해 reentrancy를 막고 PREEMPT_RT에서는 underlying per-CPU spinlock을 획득해 같은 보호를 제공한다.

PREEMPT_RT의 일반 spinlock은 완전히 선점 가능한 문맥을 요구하므로 `local_irq_disable()`이나 `get_cpu_ptr()`처럼 선점을 먼저 막은 뒤 획득할 수 없습니다. IRQ 상태와 잠금이 결합된 API를 사용하거나 `migrate_disable()`로 CPU 이주만 막아 task는 선점 가능하게 유지해야 합니다.

`migrate_disable()`은 per-CPU 포인터의 CPU 안정성만 보장하고 같은 CPU에서 다른 task가 재진입하는 것은 막지 않습니다. 재진입까지 직렬화해야 한다면 이름 있는 `local_lock`을 사용하여 비 RT에서는 선점 차단, RT에서는 CPU별 실제 잠금으로 같은 불변식을 구현합니다.

spinlock_t e rwlock_t
---------------------

Ci sono alcune conseguenze di cui tener conto dal cambiamento di semantica di
spinlock_t e rwlock_t sui kernel PREEMPT_RT. Per esempio, sui kernel non
PREEMPT_RT il seguente codice funziona come ci si aspetta::

   local_irq_disable();
   spin_lock(&lock);

ed è equivalente a::

   spin_lock_irq(&lock);

Lo stesso vale per rwlock_t e le varianti con _irqsave().

Sui kernel PREEMPT_RT questo codice non funzionerà perché gli rtmutex richiedono
un contesto con la possibilità di prelazione. Al suo posto, usate
spin_lock_irq() o spin_lock_irqsave() e le loro controparti per il rilascio. I
kernel PREEMPT_RT offrono un meccanismo local_lock per i casi in cui la
disabilitazione delle interruzioni ed acquisizione di un blocco devono rimanere
separati. Acquisire un local_lock àncora un processo ad una CPU permettendo cose
come un'acquisizione di un blocco con interruzioni disabilitate per singola CPU.

Il tipico scenario è quando si vuole proteggere una variabile di processore nel
contesto di un thread::


  struct foo *p = get_cpu_ptr(&var1);

  spin_lock(&p->lock);
  p->count += this_cpu_read(var2);

Questo codice è corretto su un kernel non-PREEMPT_RT, ma non lo è su un
PREEMPT_RT. La modifica della semantica di spinlock_t su PREEMPT_RT non permette
di acquisire p->lock perché, implicitamente, get_cpu_ptr() disabilita la
prelazione. La seguente sostituzione funzionerà su entrambe i kernel::

  struct foo *p;

  migrate_disable();
  p = this_cpu_ptr(&var1);
  spin_lock(&p->lock);
  p->count += this_cpu_read(var2);

La funzione migrate_disable() assicura che il processo venga tenuto sulla CPU
corrente, e di conseguenza garantisce che gli accessi per-CPU alle variabili var1 e
var2 rimangano sulla stessa CPU fintanto che il processo rimane prelabile.

La sostituzione con migrate_disable() non funzionerà nel seguente scenario::

  func()
  {
    struct foo *p;

    migrate_disable();
    p = this_cpu_ptr(&var1);
    p->val = func2();

Questo non funziona perché migrate_disable() non protegge dal ritorno da un
processo che aveva avuto il diritto di prelazione. Una sostituzione più adatta
per questo caso è::

  func()
  {
    struct foo *p;

    local_lock(&foo_lock);
    p = this_cpu_ptr(&var1);
    p->val = func2();

Su un kernel non-PREEMPT_RT, questo codice protegge dal rientro disabilitando la
prelazione. Su un kernel PREEMPT_RT si ottiene lo stesso risultato acquisendo lo
spinlock di CPU.

raw 및 bit spinlock의 원자 문맥 제약

482-517

raw_spinlock_t를 획득하면 preemption과 경우에 따라 interrupt도 disable된다. 따라서 critical section은 일반 spinlock_t 또는 rwlock_t를 추가로 획득해서는 안 되고 memory allocation 같은 operation도 피해야 한다.

다음 code는 non-PREEMPT_RT에서는 동작하지만 fully preemptible memory allocator를 truly atomic context에서 호출할 수 없는 PREEMPT_RT에서는 실패한다.

raw_spin_lock(&lock);
p = kmalloc(sizeof(*p), GFP_ATOMIC);

PREEMPT_RT에서 일반 non-raw spinlock은 preemption을 disable하지 않으므로 해당 lock을 가진 상태에서 allocator를 호출하는 것은 허용된다.

spin_lock(&lock);
p = kmalloc(sizeof(*p), GFP_ATOMIC);

Bit 하나에는 RT-mutex를 담을 수 없어 PREEMPT_RT가 bit spinlock을 대체할 수 없다. 따라서 bit spinlock semantics는 그대로이며 raw_spinlock_t의 주의점이 모두 적용된다.

일부 bit spinlock은 사용 위치의 #ifdef code로 PREEMPT_RT에서 일반 spinlock_t로 바꾼다. 반면 spinlock_t 자체의 대체는 사용 위치를 고칠 필요가 없다. Header와 core locking implementation의 conditional이 compiler가 투명하게 substitution하도록 만든다.

`raw_spinlock_t` 보유 구간은 실제 원자 문맥이므로 일반 `spinlock_t`나 `rwlock_t`를 추가로 획득하거나 메모리 할당처럼 잠들 수 있는 연산을 호출할 수 없습니다. 비 RT에서 우연히 작동한 `GFP_ATOMIC` 경로도 PREEMPT_RT의 선점 가능한 allocator에서는 허용되지 않을 수 있습니다.

bit 하나에는 rt_mutex 상태를 담을 수 없으므로 bit spinlock은 PREEMPT_RT에서도 raw 회전 의미를 유지합니다. RT 전환이 필요한 사용처는 별도 `spinlock_t`로 조건부 대체해야 하며 raw 잠금과 같은 중첩 제약을 적용합니다.

raw_spinlock_t su RT
--------------------

Acquisire un raw_spinlock_t disabilita la prelazione e possibilmente anche le
interruzioni, quindi la sezione critica deve evitare di acquisire uno spinlock_t
o rwlock_t. Per esempio, la sezione critica non deve fare allocazioni di
memoria. Su un kernel non-PREEMPT_RT il seguente codice funziona perfettamente::

  raw_spin_lock(&lock);
  p = kmalloc(sizeof(*p), GFP_ATOMIC);

Ma lo stesso codice non funziona su un kernel PREEMPT_RT perché l'allocatore di
memoria può essere oggetto di prelazione e quindi non può essere chiamato in un
contesto atomico. Tuttavia, si può chiamare l'allocatore di memoria quando si
trattiene un blocco *non-raw* perché non disabilitano la prelazione sui kernel
PREEMPT_RT::

  spin_lock(&lock);
  p = kmalloc(sizeof(*p), GFP_ATOMIC);


bit spinlocks
-------------

I kernel PREEMPT_RT non possono sostituire i bit spinlock perché un singolo bit
è troppo piccolo per farci stare un rtmutex. Dunque, la semantica dei bit
spinlock è mantenuta anche sui kernel PREEMPT_RT. Quindi, le precisazioni fatte
per raw_spinlock_t valgono anche qui.

In PREEMPT_RT, alcuni bit spinlock sono sostituiti con normali spinlock_t usando
condizioni di preprocessore in base a dove vengono usati. Per contro, questo non
serve quando si sostituiscono gli spinlock_t. Invece, le condizioni poste sui
file d'intestazione e sul cuore dell'implementazione della sincronizzazione
permettono al compilatore di effettuare la sostituzione in modo trasparente.

잠금 범주 중첩 순서

518-547
  • 같은 category의 lock끼리는 일반 lock ordering rule을 지켜 deadlock을 막는 한 자유롭게 nesting할 수 있다.
  • Sleeping lock은 CPU-local lock 또는 spinning lock 안에 nesting할 수 없다.
  • CPU-local lock과 spinning lock은 sleeping lock 안에 nesting할 수 있다.
  • Spinning lock은 모든 lock type 안에 nesting할 수 있다.

이 제약은 PREEMPT_RT와 non-RT 모두에 적용된다. PREEMPT_RT에서는 spinlock_t와 rwlock_t category가 spinning에서 sleeping으로 바뀌고 local_lock이 per-CPU spinlock_t가 되므로 raw spinlock을 가진 상태에서 이들을 획득할 수 없다.

Nesting levelPREEMPT_RT에서 바깥쪽부터 안쪽 순서
1Sleeping locks
2spinlock_t, rwlock_t, local_lock
3raw_spinlock_t, bit spinlock

PREEMPT_RT 여부와 관계없이 이 제약을 위반하면 lockdep가 경고한다.

같은 범주 안에서는 일반적인 전역 잠금 순서를 지키면 중첩할 수 있습니다. sleeping lock 안에는 CPU 로컬 잠금과 회전 잠금을 둘 수 있지만, CPU 로컬 또는 회전 잠금 안에는 sleeping lock을 둘 수 없습니다.

PREEMPT_RT에서는 `spinlock_t`, `rwlock_t`, `local_lock`이 잠들 수 있는 계층으로 이동하므로 바깥에서 안쪽 순서는 sleeping lock, RT 변환 잠금, `raw_spinlock_t`·bit spinlock이 됩니다. lockdep은 커널 설정과 관계없이 이 범주 위반을 경고합니다.

Regole d'annidamento dei tipi di blocchi
========================================

Le regole principali sono:

  - I tipi di blocco appartenenti alla stessa categoria possono essere annidati
    liberamente a patto che si rispetti l'ordine di blocco al fine di evitare
    stalli.

  - I blocchi con sospensione non possono essere annidati in blocchi del tipo
    CPU locale o ad attesa attiva

  - I blocchi ad attesa attiva e su CPU locale possono essere annidati nei
    blocchi ad attesa con sospensione.

  - I blocchi ad attesa attiva possono essere annidati in qualsiasi altro tipo.

Queste limitazioni si applicano ad entrambe i kernel con o senza PREEMPT_RT.

Il fatto che un kernel PREEMPT_RT cambi i blocchi spinlock_t e rwlock_t dal tipo
ad attesa attiva a quello con sospensione, e che sostituisca local_lock con uno
spinlock_t per CPU, significa che non possono essere acquisiti quando si è in un
blocco raw_spinlock. Ne consegue il seguente ordine d'annidamento:

  1) blocchi ad attesa con sospensione
  2) spinlock_t, rwlock_t, local_lock
  3) raw_spinlock_t e bit spinlocks

Se queste regole verranno violate, allora lockdep se ne accorgerà e questo sia
con o senza PREEMPT_RT.