Search

시스템 콜(Project2 : User Program) 속 volatile 키워드

컴파일러에게 “이 변수나 코드를 최적화하지 말고 항상 메모리에서 직접 읽어라”라고 지시하는 역할
과연 시스템 콜을 호출하는 쪽에서는 어떤 흐름을 갖는 걸까?라는 의문점에서 시작해 코드를 살펴보던 중 마주한 volatile 키워드에 대해 정리하고자 합니다. CSAPP에서 분명 마주했었는데, 말을 못하겠어서 정리해보고자 합니다.

등장 위치

lib/user/syscall.c
/* 어셈블리 명령어(syscall)를 사용해 커널에 시스템 콜을 직접 요청. 시스템 콜 * 번호와 최대 6개의 인자를 레지스터에 넣어 커널로 전달. 커널에서 해당 시스템 * 콜을 처리한 뒤 결과값 반환 */ __attribute__((always_inline)) static __inline int64_t syscall( uint64_t num_, uint64_t a1_, uint64_t a2_, uint64_t a3_, uint64_t a4_, uint64_t a5_, uint64_t a6_) { int64_t ret; register uint64_t *num asm("rax") = (uint64_t *)num_; register uint64_t *a1 asm("rdi") = (uint64_t *)a1_; register uint64_t *a2 asm("rsi") = (uint64_t *)a2_; register uint64_t *a3 asm("rdx") = (uint64_t *)a3_; register uint64_t *a4 asm("r10") = (uint64_t *)a4_; register uint64_t *a5 asm("r8") = (uint64_t *)a5_; register uint64_t *a6 asm("r9") = (uint64_t *)a6_; __asm __volatile( "mov %1, %%rax\n" "mov %2, %%rdi\n" "mov %3, %%rsi\n" "mov %4, %%rdx\n" "mov %5, %%r10\n" "mov %6, %%r8\n" "mov %7, %%r9\n" "syscall\n" : "=a"(ret) : "g"(num), "g"(a1), "g"(a2), "g"(a3), "g"(a4), "g"(a5), "g"(a6) : "cc", "memory"); return ret; }
C
복사
어셈블리 코드에서 등장

valatile의 핵심 역할

컴파일러 최적화 방지
컴파일러는 성능 향상을 위해 코드를 자동으로 최적화합니다 :
// volatile이 없는 경우 int x = 10; int y = x; int z = x; // 컴파일러가 "x를 또 읽을 필요 없이 y 값을 복사하자"고 최적화할 수 있음 // volatile이 있는 경우 volatile int x = 10; int y = x; // 메모리에서 x 읽음 int z = x; // 다시 메모리에서 x 읽음 (캐시된 값 사용 안 함)
C
복사

인라인 어셈블리에서 _volatile의 중요성

_volatile이 없는 경우 vs 있는 경우

_volatile이 없는 경우

// volatile 없는 경우 write(1, "Hello", 5); write(1, "World", 5); // 컴파일러가 이렇게 최적화할 수 있음: // "같은 write 시스템콜을 두 번 호출하네? 한 번만 호출하고 결과를 재사용하자!" // → 잘못된 최적화!
C
복사

_volatile이 있는 경우

// volatile 있는 경우 write(1, "Hello", 5); // 반드시 실행 write(1, "World", 5); // 반드시 실행 (최적화로 제거되지 않음)
C
복사

volatile이 필요한 상황들

 하드웨어 레지스터 접근

volatile int *hardware_register = (int *)0x12345678; *hardware_register = 1; // 하드웨어에 명령 전송 *hardware_register = 2; // 또 다른 명령 전송 // volatile 없으면 컴파일러가 첫 번째 쓰기를 제거할 수 있음
C
복사

 멀티스레딩

volatile bool flag = false; // 스레드 1 while (!flag) { // 대기... // volatile 없으면 컴파일러가 flag를 레지스터에 캐시해서 // 다른 스레드가 변경해도 감지 못할 수 있음 } // ------------------------------------------------ // 스레드 2 flag = true; // 스레드 1에게 신호
C
복사

 시스템콜

PintOS의 경우 :
__asm __volatile( "syscall\n" // 🚨 이 명령어는 절대 최적화되면 안 됨! ... );
C
복사
이유는 다음과 같습니다 :
1.
부작용(side effect) : syscall 명령어는 커널 상태를 변경함
2.
순서 중요 : 시스템 콜 순서가 바뀌면 프로그램 동작이 완전히 달라짐
3.
메모리 상태 변경 : 시스템콜이 메모리 내용을 변경할 수도 있음