[Linux:Kernel] 왜 “volatile” 타입 클래스가 쓰이지 않는 것이 좋을까

이 문서의 저작권은 GPL 라이센스를 따릅니다(This document is released under the GPL license).
Documentation/volatile-considered-harmful.txt
번역: 양정석<dasomoli@gmailREMOVETHIS.com>
왜 “volatile” 타입 클래스가 쓰이지 않는 것이 좋은가
—————————————————
C 프로그래머들은 종종 volatile을 그 변수가 현재 스레드의 실행 밖에서 바뀔 수
있다는 의미로 사용합니다; 그 결과, 그들은 가끔 커널 코드 안에서 공유 자료
구조들을 사용할 때 이를 사용하고자 합니다. 다른 말로 하면, 그들은 간단한
atomic 변수의 일종처럼, 그게 아닌데도, volatile 형식을 다루는 것으로 알고
있습니다. volatile 커널 코드의 용도는 거의 절대 맞지 않습니다; 이 문서는 그
이유를 설명합니다.
volatile에 대한 이해를 위한 핵심은 그 목적이 거의 아무도 절대 진정으로 원하지
않는 최적화를 막기 위해서라는 것입니다. 커널 안에서, 누군가는 공유되는
자료 구조를 매우 다른 태스크의 원치 않는 동시 접근으로부터 보호해야 합니다.
원치 않는 동시성에 대한 보호 처리는 또한 거의 모든 최적화에 관련된 문제를 더
효율적인 방법으로 피할 것입니다.
volatile처럼, 자료로 안전하게 동시 접근을 하는 커널 기본 요소(스핀락(spinlock),
뮤텍스(mutex), 메모리 장벽(memory barrier), 기타 등등)는 원치 않는 최적화를
막도록 설계되었습니다. 그들이 적절히 사용된다면, 그들은 volatile을 사용할
필요가 없을 것입니다. volatile이 여전히 필요하다면, 거기에는 거의 확실히 코드
어딘가에 버그가 있습니다. 적절히 작성된 커널 코드 안에서 volatile은 오직
느리게만 만들 것입니다.
일반적인 커널 코드 블록을 생각해 봅시다:
    spin_lock(&the_lock);
    do_something_on(&shared_data);
    do_something_else_with(&shared_data);
    spin_unlock(&the_lock);
모든 코드가 락킹(locking) 규칙을 따른다면, shared_data의 값은 the_lock이 잡힌
동안, 예기치 않게 바뀔 수 없습니다. 그 자료를 가지고 놀고 싶어 할 어떤 다른
코드는 락 상에서 대기할 것입니다. 스핀락 기본 요소는 자료 접근이 그들을
가로지르는 최적화가 되지 않을 것임을 의미하는 메모리 장벽처럼 – 그들은
명시적으로 그렇게 하도록 작성되었습니다 – 동작합니다. 그래서 컴파일러는 그것이
shared_data 안에 있을 것을 안다고 생각할 수도 있지만. spin_lock() 호출이 메모리
장벽처럼 동작한 이후에는, 컴파일러가 알고 있는 어떠한 것도 잊어버리도록 강제할
것입니다. 거기에는 그 자료로의 접근에 대한 최적화 문제는 없을 것입니다.
shared_data가 volatile로 선언되었어도, 락킹은 여전히 필요할 것입니다.
그러나 컴파일러 역시, 우리가 다른 누구도 그것으로 무언가 할 수 없음을 알 때,
크리티컬 섹션 _안에서는_ shared_data로의 접근에 대한 최적화가 막혔으면 할
것입니다. 락이 잡힌 동안, shared_data는 volatile이 아닙니다. 공유된 자료로
뭔가 할 때, 적절한 락킹은 – 잠재적으로 해로운 – volatile을 불필요하게 만들 것
입니다.
volatile 저장 클래스는 근본적으로 메모리-매핑된(memory-mapped) I/O 레지스터들을
위해 의미가 있습니다. 커널 안에서 레지스터 접근 역시 락으로 보호돼야 합니다만,
누군가는 또한 컴파일러가 크리티컬 섹션 안에서 레지스터 접근을 “최적화하는 것”을
원하지 않습니다. 그러나 커널 안에서, I/O 메모리 접근은 언제나
접근자(accessor) 함수를 통해 이루어집니다; I/O 메모리에 포인터를 통해 직접
접근하는 것은 얼굴이 찡그려지고, 모든 아키텍처에서 작동하지 않습니다.
이들 접근자는 원치 않는 최적화를 막도록 작성되었습니다. 그래서, 역시,
volatile은 불필요합니다.
누군가가 volatile을 사용하고자 하는 또 다른 상황은 프로세서가 한 변수의 값을
위해 바쁜-기다림(busy-waiting)을 할 때입니다. 바쁜 기다림을 수행하는 옳은
방법은:
    while (my_variable != what_i_want)
        cpu_relax();
cpu_relax() 호출은 CPU 전력 소비를 낮출 수 있고, 하이퍼 스레드 된 다른
프로세서에 양보할 수 있습니다; 또한 컴파일러 장벽처럼 서비스하는 일이
일어납니다. 그래서, 역시, volatile은 불필요합니다. 물론, 바쁜 기다림은
일반적으로 시작하는 반사회적 행동입니다.
volatile이 커널 안에서 이해되는 적은 수의 드문 상황들이 여전히 있습니다:
  – 직접 I/O 메모리 접근이 동작하는 아키텍처에서는 위에서 설명된 접근자
    함수들이 volatile을 사용할 수 있습니다. 기본적으로, 각 접근자 호출은
    그 자체로 작은 크리티컬 섹션이 되고, 접근이 프로그래머가 의도한 대로
    일어남을 보장합니다.
  – 메모리를 변경하는, 그렇지만 다른 보이는 부수 효과(side effect)나, GCC에 의해
    제거되는 위험 없는 인라인 어셈블리 코드. volatile 키워드를 asm 문장에
    추가하는 것은 이 제거를 막을 것입니다.
  – jiffies 변수는 그것이 참조되는 순간마다 다른 값을 가지지만, 어떤 특별한
    락킹도 없이 읽힐 수 있어서 특별합니다. 그래서 jiffies는 volatile일 수
    있지만, 이 타입의 다른 변수들의 추가는 강하게 얼굴이 찌푸려집니다.
    jiffies는 이런 점에서 (리누스의 말로) “멍청한 유산” 이슈로 생각됩니다; 이를
    고치는 것은 그 가치보다 더 많은 문제를 일으킬 수 있습니다.
  – 가끔 합법적으로 volatile이 될 수 있는 I/O 장치에 의해 수정될 수 있는
    일관적인(coherent) 메모리 안의 자료 구조로의 포인터. 네트워크 어댑터가 어떤
    기술자(descriptor)가 처리되었는지를 나타내기 위해 포인터를 수정하는 데서,
    그 어댑터에 의해 사용되는 링 버퍼가 이런 종류 상황의 한 예제입니다.
코드 대부분에서, volatile의 적용을 위한 위의 타당한 이유가 없습니다. 그 결과,
volatile의 사용은 버그처럼 보이고, 코드에 추가적인 정밀 조사를 가져오도록 할
가능성이 큽니다. volatile을 사용하고자 하는 개발자들은 발걸음을 돌리고 그들이
정말로 하고자 하는 바에 대해서 생각해 봐야 할 것입니다.
volatile 변수들의 제거 패치는 – 그들이 동시성 이슈들을 적절히 생각했음을
보여주는 타당한 이유로 다가오는 만큼 – 일반적으로 환영받습니다.
참고
—-
[1] http://lwn.net/Articles/233481/
[2] http://lwn.net/Articles/233482/
언급해야 할 이름
—————-
원래의 자극과 연구는 Randy Dunlap에 의해
작성은 Jonathan Corbet에 의해
의견을 통한 개선은 Satyam Sharma, Johannes Stezenbach, Jesper
Juhl, Heikki Orsila, H. Peter Anvin, Philipp Hahn, and Stefan
Richter로부터.
한국어 번역은 양정석이.