<aside> 🥕
컴파일러에게 “이 변수나 코드를 최적화하지 말고 항상 메모리에서 직접 읽어라”라고 지시하는 역할
</aside>
과연 시스템 콜을 호출하는 쪽에서는 어떤 흐름을 갖는 걸까?라는 의문점에서 시작해 코드를 살펴보던 중 마주한 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;
}
⭐️ 컴파일러 최적화 방지
컴파일러는 성능 향상을 위해 코드를 자동으로 최적화합니다 :
// 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 읽음 (캐시된 값 사용 안 함)
// volatile 없는 경우
write(1, "Hello", 5);
write(1, "World", 5);
// 컴파일러가 이렇게 최적화할 수 있음:
// "같은 write 시스템콜을 두 번 호출하네? 한 번만 호출하고 결과를 재사용하자!"
// → 잘못된 최적화!
// volatile 있는 경우
write(1, "Hello", 5); // 반드시 실행
write(1, "World", 5); // 반드시 실행 (최적화로 제거되지 않음)
volatile int *hardware_register = (int *)0x12345678;
*hardware_register = 1; // 하드웨어에 명령 전송
*hardware_register = 2; // 또 다른 명령 전송
// volatile 없으면 컴파일러가 첫 번째 쓰기를 제거할 수 있음