← Documents Documentation/translations/it_IT/process/volatile-considered-harmful.rst GitHub 원문 ↗

Linux 6.18.37 · Translations

"volatile" keyword를 사용하지 말아야 하는 이유

Kernel shared data에서 volatile 대신 lock, memory barrier와 accessor를 사용해야 하는 이유와 제한적인 예외를 설명합니다.

Source pathDocumentation/translations/it_IT/process/volatile-considered-harmful.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

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

1. 요약·해설

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

요약·해설

volatile-considered-harmful.rst:1-134

Kernel shared data는 `volatile`로 동시 접근을 해결할 수 없습니다. Spinlock, mutex와 memory barrier가 concurrency를 제어하면서 compiler optimization도 필요한 지점에서 제한합니다.

Memory-mapped I/O는 architecture별 accessor를 사용하고 busy wait에는 `cpu_relax()`를 넣습니다. 두 방식 모두 필요한 compiler barrier semantics를 제공하므로 일반 variable에 `volatile`을 붙일 이유가 없습니다.

직접 I/O access를 지원하는 architecture의 accessor, 제거되면 안 되는 inline assembly, `jiffies`, device가 수정하는 coherent memory pointer는 제한적인 예외입니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 .. include:: ../disclaimer-ita.rst
2
3 :Original: :ref:`Documentation/process/volatile-considered-harmful.rst <volatile_considered_harmful>`
4 :Translator: Federico Vaga <[email protected]>
5
6 .. _it_volatile_considered_harmful:
7
8 Perché la parola chiave "volatile" non dovrebbe essere usata
9 ------------------------------------------------------------
10
11 Spesso i programmatori C considerano volatili quelle variabili che potrebbero
12 essere cambiate al di fuori dal thread di esecuzione corrente; come risultato,
13 a volte saranno tentati dall'utilizzare *volatile* nel kernel per le
14 strutture dati condivise. In altre parole, gli è stato insegnato ad usare
15 *volatile* come una variabile atomica di facile utilizzo, ma non è così.
16 L'uso di *volatile* nel kernel non è quasi mai corretto; questo documento ne
17 descrive le ragioni.
18
19 Il punto chiave da capire su *volatile* è che il suo scopo è quello di
20 sopprimere le ottimizzazioni, che non è quasi mai quello che si vuole.
21 Nel kernel si devono proteggere le strutture dati condivise contro accessi
22 concorrenti e indesiderati: questa è un'attività completamente diversa.
23 Il processo di protezione contro gli accessi concorrenti indesiderati eviterà
24 anche la maggior parte dei problemi relativi all'ottimizzazione in modo più
25 efficiente.
26
27 Come *volatile*, le primitive del kernel che rendono sicuro l'accesso ai dati
28 (spinlock, mutex, barriere di sincronizzazione, ecc) sono progettate per
29 prevenire le ottimizzazioni indesiderate. Se vengono usate opportunamente,
30 non ci sarà bisogno di utilizzare *volatile*. Se vi sembra che *volatile* sia
31 comunque necessario, ci dev'essere quasi sicuramente un baco da qualche parte.
32 In un pezzo di codice kernel scritto a dovere, *volatile* può solo servire a
33 rallentare le cose.
34
35 Considerate questo tipico blocco di codice kernel::
36
37 spin_lock(&the_lock);
38 do_something_on(&shared_data);
39 do_something_else_with(&shared_data);
40 spin_unlock(&the_lock);
41
42 Se tutto il codice seguisse le regole di sincronizzazione, il valore di un
43 dato condiviso non potrebbe cambiare inaspettatamente mentre si trattiene un
44 lock. Un qualsiasi altro blocco di codice che vorrà usare quel dato rimarrà
45 in attesa del lock. Gli spinlock agiscono come barriere di sincronizzazione
46 - sono stati esplicitamente scritti per agire così - il che significa che gli
47 accessi al dato condiviso non saranno ottimizzati. Quindi il compilatore
48 potrebbe pensare di sapere cosa ci sarà nel dato condiviso ma la chiamata
49 spin_lock(), che agisce come una barriera di sincronizzazione, gli imporrà di
50 dimenticarsi tutto ciò che sapeva su di esso.
51
52 Se il dato condiviso fosse stato dichiarato come *volatile*, la
53 sincronizzazione rimarrebbe comunque necessaria. Ma verrà impedito al
54 compilatore di ottimizzare gli accessi al dato anche _dentro_ alla sezione
55 critica, dove sappiamo che in realtà nessun altro può accedervi. Mentre si
56 trattiene un lock, il dato condiviso non è *volatile*. Quando si ha a che
57 fare con dei dati condivisi, un'opportuna sincronizzazione rende inutile
58 l'uso di *volatile* - anzi potenzialmente dannoso.
59
60 L'uso di *volatile* fu originalmente pensato per l'accesso ai registri di I/O
61 mappati in memoria. All'interno del kernel, l'accesso ai registri, dovrebbe
62 essere protetto dai lock, ma si potrebbe anche desiderare che il compilatore
63 non "ottimizzi" l'accesso ai registri all'interno di una sezione critica.
64 Ma, all'interno del kernel, l'accesso alla memoria di I/O viene sempre fatto
65 attraverso funzioni d'accesso; accedere alla memoria di I/O direttamente
66 con i puntatori è sconsigliato e non funziona su tutte le architetture.
67 Queste funzioni d'accesso sono scritte per evitare ottimizzazioni indesiderate,
68 quindi, di nuovo, *volatile* è inutile.
69
70 Un'altra situazione dove qualcuno potrebbe essere tentato dall'uso di
71 *volatile*, è nel caso in cui il processore è in un'attesa attiva sul valore
72 di una variabile. Il modo giusto di fare questo tipo di attesa è il seguente::
73
74 while (my_variable != what_i_want)
75 cpu_relax();
76
77 La chiamata cpu_relax() può ridurre il consumo di energia del processore
78 o cedere il passo ad un processore hyperthreaded gemello; funziona anche come
79 una barriera per il compilatore, quindi, ancora una volta, *volatile* non è
80 necessario. Ovviamente, tanto per puntualizzare, le attese attive sono
81 generalmente un atto antisociale.
82
83 Ci sono comunque alcune rare situazioni dove l'uso di *volatile* nel kernel
84 ha senso:
85
86 - Le funzioni d'accesso sopracitate potrebbero usare *volatile* su quelle
87 architetture che supportano l'accesso diretto alla memoria di I/O.
88 In pratica, ogni chiamata ad una funzione d'accesso diventa una piccola
89 sezione critica a se stante, e garantisce che l'accesso avvenga secondo
90 le aspettative del programmatore.
91
92 - I codice *inline assembly* che fa cambiamenti nella memoria, ma che non
93 ha altri effetti espliciti, rischia di essere rimosso da GCC. Aggiungere
94 la parola chiave *volatile* a questo codice ne previene la rimozione.
95
96 - La variabile jiffies è speciale in quanto assume un valore diverso ogni
97 volta che viene letta ma può essere lette senza alcuna sincronizzazione.
98 Quindi jiffies può essere *volatile*, ma l'aggiunta ad altre variabili di
99 questo è sconsigliata. Jiffies è considerata uno "stupido retaggio"
100 (parole di Linus) in questo contesto; correggerla non ne varrebbe la pena e
101 causerebbe più problemi.
102
103 - I puntatori a delle strutture dati in una memoria coerente che potrebbe
104 essere modificata da dispositivi di I/O può, a volte, essere legittimamente
105 *volatile*. Un esempio pratico può essere quello di un adattatore di rete
106 che utilizza un puntatore ad un buffer circolare, questo viene cambiato
107 dall'adattatore per indicare quali descrittori sono stati processati.
108
109 Per la maggior parte del codice, nessuna delle giustificazioni sopracitate può
110 essere considerata. Di conseguenza, l'uso di *volatile* è probabile che venga
111 visto come un baco e porterà a verifiche aggiuntive. Gli sviluppatori tentati
112 dall'uso di *volatile* dovrebbero fermarsi e pensare a cosa vogliono davvero
113 ottenere.
114
115 Le modifiche che rimuovono variabili *volatile* sono generalmente ben accette
116 - purché accompagnate da una giustificazione che dimostri che i problemi di
117 concorrenza siano stati opportunamente considerati.
118
119 Riferimenti
120 ===========
121
122 [1] https://lwn.net/Articles/233481/
123
124 [2] https://lwn.net/Articles/233482/
125
126 Crediti
127 =======
128
129 Impulso e ricerca originale di Randy Dunlap
130
131 Scritto da Jonathan Corbet
132
133 Migliorato dai commenti di Satyam Sharma, Johannes Stezenbach, Jesper
134 Juhl, Heikki Orsila, H. Peter Anvin, Philipp Hahn, e Stefan Richter.
135

3. 한국어 전문 번역

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

"volatile"의 목적과 kernel concurrency

1-34

C 프로그래머는 흔히 volatile을 현재 실행 중인 스레드의 바깥에서 변수가 변경될 수 있다는 뜻으로 받아들인다. 그 결과 공유 자료 구조를 사용하는 커널 코드에도 volatile을 쓰고 싶은 유혹을 받곤 한다. 다시 말해 volatile 형식을 손쉬운 atomic 변수처럼 취급해 왔지만, volatile은 atomic 변수가 아니다. 커널 코드에서 volatile을 사용하는 것은 거의 언제나 올바르지 않다. 이 문서는 그 이유를 설명한다.

volatile과 관련해 이해해야 할 핵심은 그 목적이 최적화를 억제하는 데 있다는 점이다. 그러나 최적화 억제는 실제로 원하는 일이 거의 아니다. 커널에서는 공유 자료 구조를 원치 않는 동시 접근으로부터 보호해야 하며, 이는 최적화를 막는 일과는 매우 다른 문제다. 원치 않는 동시 접근을 막는 과정은 최적화 때문에 생길 수 있는 거의 모든 문제도 더 효율적인 방식으로 방지한다.

volatile과 마찬가지로 spinlock, mutex, memory barrier 등 자료에 대한 동시 접근을 안전하게 만드는 커널 primitive도 원치 않는 최적화를 막도록 설계되어 있다. 이 primitive를 올바르게 사용한다면 volatile을 함께 사용할 필요가 없다. 그래도 volatile이 필요하다면 코드 어딘가에 버그가 있을 가능성이 거의 확실하다. 올바르게 작성된 커널 코드에서 volatile은 실행 속도를 늦추는 역할밖에 하지 못한다.

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

:Original: :ref:`Documentation/process/volatile-considered-harmful.rst <volatile_considered_harmful>`
:Translator: Federico Vaga <[email protected]>

.. _it_volatile_considered_harmful:

Perché la parola chiave "volatile" non dovrebbe essere usata
------------------------------------------------------------

Spesso i programmatori C considerano volatili quelle variabili che potrebbero
essere cambiate al di fuori dal thread di esecuzione corrente; come risultato,
a volte saranno tentati dall'utilizzare *volatile* nel kernel per le
strutture dati condivise.  In altre parole, gli è stato insegnato ad usare
*volatile* come una variabile atomica di facile utilizzo, ma non è così.
L'uso di *volatile* nel kernel non è quasi mai corretto; questo documento ne
descrive le ragioni.

Il punto chiave da capire su *volatile* è che il suo scopo è quello di
sopprimere le ottimizzazioni, che non è quasi mai quello che si vuole.
Nel kernel si devono proteggere le strutture dati condivise contro accessi
concorrenti e indesiderati: questa è un'attività completamente diversa.
Il processo di protezione contro gli accessi concorrenti indesiderati eviterà
anche la maggior parte dei problemi relativi all'ottimizzazione in modo più
efficiente.

Come *volatile*, le primitive del kernel che rendono sicuro l'accesso ai dati
(spinlock, mutex, barriere di sincronizzazione, ecc) sono progettate per
prevenire le ottimizzazioni indesiderate.  Se vengono usate opportunamente,
non ci sarà bisogno di utilizzare *volatile*.  Se vi sembra che *volatile* sia
comunque necessario, ci dev'essere quasi sicuramente un baco da qualche parte.
In un pezzo di codice kernel scritto a dovere, *volatile* può solo servire a
rallentare le cose.

Lock으로 보호되는 shared data

35-59

다음과 같은 전형적인 커널 코드 블록을 생각해 보자.

spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);

모든 코드가 locking 규칙을 따른다면 the_lock을 보유하는 동안 shared_data의 값이 예기치 않게 바뀔 수 없다. 이 자료를 사용하려는 다른 코드는 lock을 기다리게 된다. Spinlock primitive는 memory barrier로 동작하며, 실제로 그렇게 동작하도록 명시적으로 작성되어 있다. 따라서 자료 접근이 spinlock 경계를 넘어가도록 최적화되지 않는다.

컴파일러는 shared_data에 어떤 값이 들어 있을지 알고 있다고 판단할 수 있다. 하지만 memory barrier 역할을 하는 spin_lock() 호출은 컴파일러가 이전에 알고 있던 내용을 잊도록 강제한다. 그러므로 해당 자료에 접근할 때 최적화로 인한 문제가 발생하지 않는다.

shared_data를 volatile로 선언하더라도 locking은 여전히 필요하다. 그와 동시에, 아무도 shared_data를 함께 사용하지 못한다는 사실을 알고 있는 critical section 안에서도 컴파일러가 shared_data 접근을 최적화할 수 없게 된다. Lock을 보유하는 동안 shared_data는 volatile한 자료가 아니다. 공유 자료를 다룰 때 올바른 locking은 volatile을 불필요하게 만들며, volatile을 잠재적으로 해롭게 만든다.

Considerate questo tipico blocco di codice kernel::

    spin_lock(&the_lock);
    do_something_on(&shared_data);
    do_something_else_with(&shared_data);
    spin_unlock(&the_lock);

Se tutto il codice seguisse le regole di sincronizzazione, il valore di un
dato condiviso non potrebbe cambiare inaspettatamente mentre si trattiene un
lock.  Un qualsiasi altro blocco di codice che vorrà usare quel dato rimarrà
in attesa del lock.  Gli spinlock agiscono come barriere di sincronizzazione
- sono stati esplicitamente scritti per agire così - il che significa che gli
accessi al dato condiviso non saranno ottimizzati.  Quindi il compilatore
potrebbe pensare di sapere cosa ci sarà nel dato condiviso ma la chiamata
spin_lock(), che agisce come una barriera di sincronizzazione, gli imporrà di
dimenticarsi tutto ciò che sapeva su di esso.

Se il dato condiviso fosse stato dichiarato come *volatile*, la
sincronizzazione rimarrebbe comunque necessaria.  Ma verrà impedito al
compilatore di ottimizzare gli accessi al dato anche _dentro_ alla sezione
critica, dove sappiamo che in realtà nessun altro può accedervi.  Mentre si
trattiene un lock, il dato condiviso non è *volatile*.  Quando si ha a che
fare con dei dati condivisi, un'opportuna sincronizzazione rende inutile
l'uso di *volatile* - anzi potenzialmente dannoso.

Memory-mapped I/O와 accessor

60-69

volatile storage class는 원래 memory-mapped I/O register를 위해 마련되었다. 커널에서도 register 접근은 lock으로 보호해야 한다. 그러나 critical section 안에서 컴파일러가 register 접근을 최적화해 버리는 것 역시 원하지 않는다.

커널에서는 I/O memory에 항상 accessor function을 통해 접근한다. Pointer를 이용해 I/O memory에 직접 접근하는 방식은 권장되지 않으며 모든 architecture에서 동작하지도 않는다. 이 accessor들은 원치 않는 최적화를 방지하도록 작성되어 있다. 따라서 이 경우에도 volatile은 필요하지 않다.

L'uso di *volatile* fu originalmente pensato per l'accesso ai registri di I/O
mappati in memoria.  All'interno del kernel, l'accesso ai registri, dovrebbe
essere protetto dai lock, ma si potrebbe anche desiderare che il compilatore
non "ottimizzi" l'accesso ai registri all'interno di una sezione critica.
Ma, all'interno del kernel, l'accesso alla memoria di I/O viene sempre fatto
attraverso funzioni d'accesso; accedere alla memoria di I/O direttamente
con i puntatori è sconsigliato e non funziona su tutte le architetture.
Queste funzioni d'accesso sono scritte per evitare ottimizzazioni indesiderate,
quindi, di nuovo, *volatile* è inutile.

Busy wait와 cpu_relax()

70-82

Processor가 어떤 변수의 값을 busy-wait 방식으로 기다릴 때도 volatile을 사용하고 싶을 수 있다. Busy wait를 올바르게 수행하는 방법은 다음과 같다.

while (my_variable != what_i_want)
    cpu_relax();

cpu_relax() 호출은 CPU의 전력 소비를 줄이거나 hyperthreading으로 짝을 이룬 다른 processor에 실행 기회를 줄 수 있다. 또한 compiler barrier 역할도 하므로 이 경우에도 volatile은 필요하지 않다. 물론 busy-wait 자체가 일반적으로 다른 실행 주체에 비협조적인 동작이라는 점도 고려해야 한다.

Un'altra situazione dove qualcuno potrebbe essere tentato dall'uso di
*volatile*, è nel caso in cui il processore è in un'attesa attiva sul valore
di una variabile.  Il modo giusto di fare questo tipo di attesa è il seguente::

    while (my_variable != what_i_want)
        cpu_relax();

La chiamata cpu_relax() può ridurre il consumo di energia del processore
o cedere il passo ad un processore hyperthreaded gemello; funziona anche come
una barriera per il compilatore, quindi, ancora una volta, *volatile* non è
necessario.  Ovviamente, tanto per puntualizzare, le attese attive sono
generalmente un atto antisociale.

volatile이 의미를 갖는 드문 경우

83-118

커널에서도 volatile이 타당한 경우가 조금은 남아 있다.

  • 앞에서 언급한 accessor function은 I/O memory 직접 접근이 동작하는 architecture에서 volatile을 사용할 수 있다. 본질적으로 accessor 호출 하나하나가 작은 critical section이 되어 프로그래머가 의도한 대로 접근이 일어나도록 보장한다.
  • Memory를 변경하지만 그 밖에 눈에 보이는 side effect가 없는 inline assembly 코드는 GCC에 의해 삭제될 위험이 있다. asm statement에 volatile keyword를 추가하면 이러한 제거를 막는다.
  • jiffies 변수는 참조할 때마다 다른 값을 가질 수 있지만 특별한 locking 없이 읽을 수 있다는 점에서 특수하다. 따라서 jiffies는 volatile일 수 있다. 그러나 같은 형식의 변수를 새로 추가하는 것은 강하게 권장되지 않는다. 이 점에서 jiffies는 Linus의 표현을 빌리면 '어리석은 legacy' 문제이며, 이를 고치는 수고가 얻는 이익보다 더 크다.
  • I/O device가 변경할 수 있는 coherent memory의 자료 구조를 가리키는 pointer는 때때로 정당하게 volatile일 수 있다. Network adapter가 처리한 descriptor를 표시하기 위해 pointer를 변경하는 ring buffer가 이런 상황의 한 예다.

대부분의 코드에는 위의 어떤 volatile 사용 근거도 적용되지 않는다. 따라서 volatile 사용은 버그로 간주될 가능성이 크며 해당 코드는 추가적인 검토를 받게 된다. Volatile을 사용하고 싶은 개발자는 한 걸음 물러서서 자신이 실제로 달성하려는 일이 무엇인지 생각해야 한다.

Volatile 변수를 제거하는 patch는 일반적으로 환영받는다. 단, 동시성 문제를 올바르게 검토했음을 보여 주는 근거가 patch에 함께 제시되어야 한다.

Ci sono comunque alcune rare situazioni dove l'uso di *volatile* nel kernel
ha senso:

  - Le funzioni d'accesso sopracitate potrebbero usare *volatile* su quelle
    architetture che supportano l'accesso diretto alla memoria di I/O.
    In pratica, ogni chiamata ad una funzione d'accesso diventa una piccola
    sezione critica a se stante, e garantisce che l'accesso avvenga secondo
    le aspettative del programmatore.

  - I codice *inline assembly* che fa cambiamenti nella memoria, ma che non
    ha altri effetti espliciti, rischia di essere rimosso da GCC.  Aggiungere
    la parola chiave *volatile* a questo codice ne previene la rimozione.

  - La variabile jiffies è speciale in quanto assume un valore diverso ogni
    volta che viene letta ma può essere lette senza alcuna sincronizzazione.
    Quindi jiffies può essere *volatile*, ma l'aggiunta ad altre variabili di
    questo è sconsigliata.  Jiffies è considerata uno "stupido retaggio"
    (parole di Linus) in questo contesto; correggerla non ne varrebbe la pena e
    causerebbe più problemi.

  - I puntatori a delle strutture dati in una memoria coerente che potrebbe
    essere modificata da dispositivi di I/O può, a volte, essere legittimamente
    *volatile*.  Un esempio pratico può essere quello di un adattatore di rete
    che utilizza un puntatore ad un buffer circolare, questo viene cambiato
    dall'adattatore per indicare quali descrittori sono stati processati.

Per la maggior parte del codice, nessuna delle giustificazioni sopracitate può
essere considerata.  Di conseguenza, l'uso di *volatile* è probabile che venga
visto come un baco e porterà a verifiche aggiuntive.  Gli sviluppatori tentati
dall'uso di *volatile* dovrebbero fermarsi e pensare a cosa vogliono davvero
ottenere.

Le modifiche che rimuovono variabili *volatile* sono generalmente ben accette
- purché accompagnate da una giustificazione che dimostri che i problemi di
concorrenza siano stati opportunamente considerati.

참고문헌과 기여자

119-134

[1] https://lwn.net/Articles/233481/

[2] https://lwn.net/Articles/233482/

최초의 문제 제기와 조사는 Randy Dunlap이 수행했다. Jonathan Corbet이 문서를 작성했다.

Satyam Sharma, Johannes Stezenbach, Jesper Juhl, Heikki Orsila, H. Peter Anvin, Philipp Hahn, Stefan Richter의 의견을 반영해 문서를 개선했다.

Riferimenti
===========

[1] https://lwn.net/Articles/233481/

[2] https://lwn.net/Articles/233482/

Crediti
=======

Impulso e ricerca originale di Randy Dunlap

Scritto da Jonathan Corbet

Migliorato dai commenti di Satyam Sharma, Johannes Stezenbach, Jesper
Juhl, Heikki Orsila, H. Peter Anvin, Philipp Hahn, e Stefan Richter.