x86_64 명령어셋 레퍼런스
x86_64(AMD64/Intel 64) 아키텍처의 명령어를 AT&T 문법 기준으로 종합 정리합니다. CISC 설계 철학과 가변 길이 인코딩, 16개 범용 레지스터와 RFLAGS, SSE/AVX/AVX-512 SIMD 레지스터, 주소 지정 모드, 데이터 전송·산술·논리·분기·스택·시스템·원자적·SIMD 명령어 카테고리 표, REX/VEX/EVEX 인코딩 다이어그램, 커널 핵심 명령어(SYSCALL, SWAPGS, LOCK CMPXCHG, INVLPG) 심화까지 Linux 커널 개발에 필요한 x86_64 ISA 전체를 다룹니다.
핵심 요약
- CISC 아키텍처 — 가변 길이 명령어(1~15바이트), 메모리 피연산자 직접 사용, 풍부한 주소 모드.
- AT&T 문법 — 소스→목적지 순서, % 레지스터 접두사, $ 즉시값 접두사, b/w/l/q 크기 접미사.
- 16개 범용 레지스터 — RAX-RDI(기존 8개) + R8-R15(AMD64 확장), 64/32/16/8-bit 접근 가능.
- SIMD 확장 — SSE(XMM, 128-bit) → AVX(YMM, 256-bit) → AVX-512(ZMM, 512-bit + 마스크 레지스터).
- LOCK 접두사 — 원자적 메모리 연산의 핵심. CMPXCHG, XADD 등과 결합.
단계별 이해
- 레지스터 맵 파악
16개 범용 레지스터의 이름 규칙(RAX/EAX/AX/AL)과 System V ABI 용도를 먼저 익힙니다. - AT&T 문법 숙지
접미사(b/w/l/q), 접두사(%/$), 소스→목적지 순서를 반드시 구분합니다. - 주소 모드 이해
displacement(base,index,scale) SIB 형식이 x86_64 주소 지정의 핵심입니다. - 카테고리별 명령어 학습
데이터 전송 → 산술 → 논리 → 분기 → 시스템 순서로 진행하면 체계적입니다.
아키텍처 개요
x86_64(AMD64)는 2003년 AMD가 IA-32(x86)를 64-bit로 확장한 CISC 아키텍처입니다. 8086(1978) → 80386(1985, 32-bit) → AMD64(2003, 64-bit) 순으로 발전했으며, 하위 호환성을 유지하면서 레지스터와 주소 공간을 확장했습니다.
| 특성 | x86_64 |
|---|---|
| 설계 철학 | CISC (Complex Instruction Set Computer) |
| 명령어 길이 | 가변 (1~15바이트) |
| 엔디언 | 리틀 엔디언 (Little-Endian) |
| 범용 레지스터 | 16개 (64-bit) |
| 주소 공간 | 가상 48-bit (256TB), 물리 최대 52-bit |
| 동작 모드 | Long Mode (64-bit), Compatibility Mode (32-bit), Legacy Mode |
| 페이지 크기 | 4KB, 2MB, 1GB |
| 명령어 접두사 | REX, VEX, EVEX, LOCK, REP, 세그먼트 오버라이드 |
동작 모드 전환 다이어그램
x86_64 프로세서는 전원 인가 후 Real Mode에서 시작하며, 제어 레지스터 비트를 조작해 단계적으로 Long Mode(64-bit)까지 전환합니다. 리눅스 커널 부트 과정에서 이 전환은 arch/x86/boot/compressed/head_64.S에서 수행됩니다.
레지스터 셋
범용 레지스터 (64-bit / 32-bit / 16-bit / 8-bit)
| 64-bit | 32-bit | 16-bit | 8-bit (Low) | 8-bit (High) | System V ABI 용도 |
|---|---|---|---|---|---|
| %rax | %eax | %ax | %al | %ah | 반환값 (1st) |
| %rbx | %ebx | %bx | %bl | %bh | Callee-saved |
| %rcx | %ecx | %cx | %cl | %ch | 4th 인자 |
| %rdx | %edx | %dx | %dl | %dh | 3rd 인자, 반환값 (2nd) |
| %rsi | %esi | %si | %sil | — | 2nd 인자 |
| %rdi | %edi | %di | %dil | — | 1st 인자 |
| %rbp | %ebp | %bp | %bpl | — | 프레임 포인터 (Callee-saved) |
| %rsp | %esp | %sp | %spl | — | 스택 포인터 |
| %r8 | %r8d | %r8w | %r8b | — | 5th 인자 |
| %r9 | %r9d | %r9w | %r9b | — | 6th 인자 |
| %r10 | %r10d | %r10w | %r10b | — | Caller-saved |
| %r11 | %r11d | %r11w | %r11b | — | Caller-saved |
| %r12 | %r12d | %r12w | %r12b | — | Callee-saved |
| %r13 | %r13d | %r13w | %r13b | — | Callee-saved |
| %r14 | %r14d | %r14w | %r14b | — | Callee-saved |
| %r15 | %r15d | %r15w | %r15b | — | Callee-saved |
레지스터 중첩 구조 (RAX 예시)
x86_64 범용 레지스터는 하위 호환성을 위해 중첩 구조를 가집니다. 64-bit RAX 안에 32-bit EAX가, 그 안에 16-bit AX가, AX는 다시 상위 AH와 하위 AL로 나뉩니다.
movl $1, %eax → RAX = 0x00000001. 반면 16-bit/8-bit 쓰기는 상위 비트에 영향을 주지 않습니다.
예: movw $1, %ax → RAX의 상위 48-bit은 그대로 유지됩니다.
R8-R15 레지스터에서는 AH 같은 상위 8-bit 접근이 불가능합니다.
RFLAGS 레지스터 주요 비트
| 비트 | 약어 | 이름 | 설명 |
|---|---|---|---|
| 0 | CF | Carry Flag | 부호 없는 연산 올림/빌림 |
| 2 | PF | Parity Flag | 결과 하위 바이트의 짝수 패리티 |
| 6 | ZF | Zero Flag | 결과가 0이면 세트 |
| 7 | SF | Sign Flag | 결과의 최상위 비트 (부호) |
| 8 | TF | Trap Flag | 단일 스텝 디버깅 |
| 9 | IF | Interrupt Flag | 인터럽트 활성화/비활성화 |
| 10 | DF | Direction Flag | 문자열 명령어 방향 (0=증가, 1=감소) |
| 11 | OF | Overflow Flag | 부호 있는 연산 오버플로 |
특수/시스템 레지스터
| 레지스터 | 설명 |
|---|---|
| %rip | 명령어 포인터 (Instruction Pointer) |
| %cs, %ds, %es, %fs, %gs, %ss | 세그먼트 레지스터 (Long Mode에서 FS/GS만 유효) |
| CR0 | 제어 레지스터: PE(보호모드), PG(페이징), WP(쓰기 보호) 등 |
| CR2 | Page Fault 선형 주소 |
| CR3 | 페이지 디렉토리 베이스 (PML4 물리 주소) |
| CR4 | 확장 기능: PAE, PSE, OSXSAVE, PCIDE, SMEP, SMAP 등 |
| MSR EFER | Extended Feature Enable: LME(Long Mode Enable), SCE(SYSCALL Enable), NXE |
| MSR LSTAR | SYSCALL 진입 주소 (64-bit) |
| MSR STAR | SYSCALL/SYSRET CS/SS 세그먼트 |
| MSR FMASK | SYSCALL 시 RFLAGS 마스크 |
SIMD 레지스터
| 확장 | 레지스터 | 크기 | 개수 |
|---|---|---|---|
| SSE | XMM0-XMM15 | 128-bit | 16 |
| AVX | YMM0-YMM15 | 256-bit | 16 |
| AVX-512 | ZMM0-ZMM31 | 512-bit | 32 |
| AVX-512 마스크 | k0-k7 | 64-bit | 8 (k0은 암묵적 all-ones) |
| SSE 제어 | MXCSR | 32-bit | 1 (라운딩 모드, 예외 마스크) |
SIMD 레지스터 중첩 구조
SIMD 레지스터는 SSE → AVX → AVX-512 확장에 따라 중첩 구조를 가집니다. ZMM의 하위 256-bit은 YMM이고, YMM의 하위 128-bit은 XMM입니다.
kernel_fpu_begin() / kernel_fpu_end()로 FPU 상태를 저장/복원해야 합니다.
이는 주로 암호화(aesni-intel), CRC 계산, RAID XOR 등에서 활용됩니다.
주소 지정 모드
x86_64 AT&T 문법에서 메모리 피연산자는 displacement(base, index, scale) 형식을 사용합니다.
| 모드 | AT&T 문법 | 계산 | 예제 |
|---|---|---|---|
| 즉시값 | $imm | 상수 값 | movq $42, %rax |
| 레지스터 | %reg | 레지스터 값 | movq %rbx, %rax |
| 직접 메모리 | addr | [addr] | movq 0x1000, %rax |
| 간접 | (%reg) | [reg] | movq (%rdi), %rax |
| 오프셋 | disp(%reg) | [reg + disp] | movq 8(%rbp), %rax |
| 인덱스 | (%base,%idx) | [base + idx] | movq (%rax,%rcx), %rdx |
| SIB | disp(%base,%idx,s) | [base + idx*s + disp] | movq 16(%rdi,%rsi,8), %rax |
| RIP 상대 | symbol(%rip) | [RIP + offset] | movq var(%rip), %rax |
b(byte, 8-bit), w(word, 16-bit), l(long, 32-bit), q(quad, 64-bit). 예: movb, movw, movl, movq.
ModR/M 및 SIB 바이트 구조
x86_64 메모리 피연산자의 인코딩에서 ModR/M 바이트는 주소 모드와 레지스터를 지정하고, SIB(Scale-Index-Base) 바이트는 복합 주소 계산(base + index * scale + displacement)을 인코딩합니다. ModR/M의 R/M 필드가 100(RSP 인코딩)이면 SIB 바이트가 뒤따릅니다.
Mod=00, R/M=101 조합이 RIP 상대 주소를 의미합니다 (32-bit에서는 절대 주소).
movq var(%rip), %rax에서 어셈블러는 현재 RIP부터 var까지의 오프셋을 32-bit displacement로 인코딩합니다.
이 방식은 Position-Independent Code(PIC)에 필수적이며, 커널의 KASLR(Kernel Address Space Layout Randomization)에서도 활용됩니다.
데이터 전송 명령어
| 명령어 | AT&T 문법 | 설명 | 동작 |
|---|---|---|---|
| MOV | movq %rax, %rbx | 데이터 이동 | dst ← src |
| MOVSX | movslq %eax, %rbx | 부호 확장 이동 | dst ← sign_extend(src) |
| MOVZX | movzbl %al, %eax | 제로 확장 이동 | dst ← zero_extend(src) |
| LEA | leaq 8(%rdi,%rsi,4), %rax | 유효 주소 계산 | dst ← effective_address (메모리 접근 없음) |
| XCHG | xchgq %rax, %rbx | 값 교환 (메모리 시 암묵적 LOCK) | tmp ← dst; dst ← src; src ← tmp |
| BSWAP | bswap %eax | 바이트 순서 반전 | 엔디언 변환 |
| CMOVcc | cmovzq %rbx, %rax | 조건부 이동 | 조건 충족 시 dst ← src |
| MOVS | movsq | 문자열 복사 | [RDI] ← [RSI]; RSI/RDI 갱신 |
| LODS | lodsq | 문자열 로드 | RAX ← [RSI]; RSI 갱신 |
| STOS | stosq | 문자열 저장 | [RDI] ← RAX; RDI 갱신 |
| PUSH | pushq %rax | 스택 푸시 | RSP -= 8; [RSP] ← src |
| POP | popq %rax | 스택 팝 | dst ← [RSP]; RSP += 8 |
- MOV — 메모리에서 값을 읽어서 레지스터에 저장합니다.
movq (%rdi), %rax는 RDI가 가리키는 주소의 8바이트 값을 RAX에 로드합니다. - LEA — 메모리를 접근하지 않고 주소 자체를 계산합니다.
leaq 8(%rdi,%rsi,4), %rax는 RDI + RSI*4 + 8 결과를 RAX에 저장하며, 플래그를 변경하지 않습니다. 산술 연산의 대용으로 자주 사용됩니다. - MOVSX (movslq) — 작은 크기의 부호 있는 값을 큰 레지스터로 부호 확장합니다. 커널에서
int(32-bit) →long(64-bit) 변환에 필수적입니다. - MOVZX (movzbl) — 작은 크기의 부호 없는 값을 제로 확장합니다.
u8→u64변환 등에 사용됩니다.
/* 커널에서 자주 보이는 패턴 */
/* 1. LEA로 구조체 멤버 주소 계산 (메모리 접근 없음) */
/* leaq offset(%rdi), %rax — container_of() 매크로 결과 */
/* 2. MOVSX: syscall 번호(int) → 테이블 인덱스(long) */
/* movslq %eax, %rax — 32-bit 부호 있는 값을 64-bit로 확장 */
/* 3. MOVZX: 바이트 읽기 → 레지스터 제로 확장 */
/* movzbl (%rdi), %eax — 1바이트 로드, EAX 제로 확장 (RAX 상위도 0) */
/* 4. CMOVcc: 조건부 이동으로 분기 제거 */
int clamp_min(int val, int min_val) {
return val < min_val ? min_val : val;
/* 컴파일 결과:
cmpl %esi, %edi // val - min_val
cmovll %esi, %edi // val < min_val이면 edi = min_val
movl %edi, %eax // return
*/
}
산술 명령어
| 명령어 | AT&T 문법 | 설명 | 플래그 영향 |
|---|---|---|---|
| ADD | addq %rax, %rbx | 덧셈 | CF, ZF, SF, OF, PF |
| SUB | subq %rax, %rbx | 뺄셈 | CF, ZF, SF, OF, PF |
| ADC | adcq %rax, %rbx | 올림 포함 덧셈 | CF, ZF, SF, OF, PF |
| SBB | sbbq %rax, %rbx | 빌림 포함 뺄셈 | CF, ZF, SF, OF, PF |
| INC | incq %rax | 1 증가 (CF 미변경) | ZF, SF, OF, PF |
| DEC | decq %rax | 1 감소 (CF 미변경) | ZF, SF, OF, PF |
| NEG | negq %rax | 2의 보수 부정 | CF, ZF, SF, OF, PF |
| MUL | mulq %rbx | 부호 없는 곱셈 | CF, OF (RDX:RAX ← RAX * src) |
| IMUL | imulq %rbx, %rax | 부호 있는 곱셈 (2/3 오퍼랜드) | CF, OF |
| DIV | divq %rbx | 부호 없는 나눗셈 | RAX ← 몫, RDX ← 나머지 |
| IDIV | idivq %rbx | 부호 있는 나눗셈 | RAX ← 몫, RDX ← 나머지 |
| CQO | cqo | RAX 부호를 RDX로 확장 | — |
| CDQ | cdq | EAX 부호를 EDX로 확장 | — |
leaq (%rdi,%rdi,1), %rax→rax = rdi * 2(SHL보다 유연)leaq (%rdi,%rdi,2), %rax→rax = rdi * 3leaq (%rdi,%rdi,4), %rax→rax = rdi * 5leaq (%rdi,%rdi,8), %rax→rax = rdi * 9leaq 7(%rdi,%rdi,8), %rax→rax = rdi * 9 + 7leaq (,%rdi,8), %rax→rax = rdi * 8(shift 대체)
/* GCC가 자주 생성하는 LEA 패턴 */
/* x * 3 */
leaq (%rdi,%rdi,2), %rax /* rax = rdi + rdi*2 = rdi*3 */
/* x * 5 + 1 */
leaq 1(%rdi,%rdi,4), %rax /* rax = rdi + rdi*4 + 1 = rdi*5 + 1 */
/* x * 12 (두 단계) */
leaq (%rdi,%rdi,2), %rax /* rax = rdi*3 */
shlq $2, %rax /* rax = rdi*3*4 = rdi*12 */
/* 주의: LEA는 RFLAGS를 변경하지 않으므로 */
/* CMP/TEST 뒤의 조건 분기를 방해하지 않음 */
cmpq $0, %rsi
leaq 1(%rdi), %rdi /* rdi++, 플래그 보존 (INC는 ZF 변경) */
je .Lzero /* 위의 CMP 결과로 분기 */
논리/시프트/비트 조작 명령어
| 명령어 | AT&T 문법 | 설명 | 플래그 영향 |
|---|---|---|---|
| AND | andq %rax, %rbx | 비트 AND | ZF, SF, PF (CF=OF=0) |
| OR | orq %rax, %rbx | 비트 OR | ZF, SF, PF (CF=OF=0) |
| XOR | xorq %rax, %rax | 비트 XOR (셀프 XOR = 0) | ZF, SF, PF (CF=OF=0) |
| NOT | notq %rax | 비트 반전 | — |
| TEST | testq %rax, %rbx | 비트 AND (결과 저장 안 함) | ZF, SF, PF (CF=OF=0) |
| SHL/SAL | shlq $3, %rax | 좌측 시프트 | CF(마지막 밀려난 비트), ZF, SF |
| SHR | shrq $1, %rax | 논리 우측 시프트 | CF, ZF, SF |
| SAR | sarq $1, %rax | 산술 우측 시프트 (부호 보존) | CF, ZF, SF |
| ROL/ROR | rolq $4, %rax | 좌측/우측 순환 시프트 | CF, OF |
| BT | btq $5, %rax | 비트 테스트 | CF ← 지정 비트 |
| BTS/BTR/BTC | btsq $5, %rax | 비트 테스트 후 세트/리셋/토글 | CF ← 이전 비트 |
| BSF | bsfq %rbx, %rax | 최하위 세트 비트 검색 | ZF (src=0이면 세트) |
| BSR | bsrq %rbx, %rax | 최상위 세트 비트 검색 | ZF |
| POPCNT | popcntq %rbx, %rax | 세트 비트 수 카운트 | ZF (CF=SF=OF=PF=0) |
| LZCNT | lzcntq %rbx, %rax | 선행 제로 비트 수 | CF, ZF |
| TZCNT | tzcntq %rbx, %rax | 후행 제로 비트 수 | CF, ZF |
| PEXT | pextq %rcx, %rbx, %rax | 병렬 비트 추출 (BMI2) | — |
| PDEP | pdepq %rcx, %rbx, %rax | 병렬 비트 배치 (BMI2) | — |
비교/분기 명령어
| 명령어 | AT&T 문법 | 설명 | 조건 |
|---|---|---|---|
| CMP | cmpq %rax, %rbx | rbx - rax (결과 저장 안 함) | CF, ZF, SF, OF 세트 |
| TEST | testq %rax, %rax | rax & rax (zero 검사) | ZF, SF, PF 세트 |
| JE/JZ | je label | 같으면 분기 | ZF=1 |
| JNE/JNZ | jne label | 다르면 분기 | ZF=0 |
| JG/JNLE | jg label | 부호 있는 > | ZF=0 && SF=OF |
| JGE/JNL | jge label | 부호 있는 >= | SF=OF |
| JL/JNGE | jl label | 부호 있는 < | SF≠OF |
| JLE/JNG | jle label | 부호 있는 <= | ZF=1 || SF≠OF |
| JA/JNBE | ja label | 부호 없는 > | CF=0 && ZF=0 |
| JAE/JNB | jae label | 부호 없는 >= | CF=0 |
| JB/JNAE | jb label | 부호 없는 < | CF=1 |
| JBE/JNA | jbe label | 부호 없는 <= | CF=1 || ZF=1 |
| JS/JNS | js label | 부호 비트 | SF=1 / SF=0 |
| JO/JNO | jo label | 오버플로 | OF=1 / OF=0 |
| JMP | jmp label | 무조건 분기 | — |
| LOOP | loop label | RCX-- 후 0이 아니면 분기 | — |
분기 예측과 커널 최적화
현대 x86_64 프로세서는 분기 예측 유닛(BPU)을 사용해 조건 분기의 결과를 투기적으로 예측합니다. 예측이 맞으면 파이프라인이 지연 없이 계속 진행되지만, 예측 실패(misprediction) 시 10~20 사이클의 페널티가 발생합니다.
__builtin_expect()를 활용해 분기 예측 힌트를 제공합니다.
이 매크로는 코드 레이아웃을 변경하여 예상되는 경로(hot path)를 직선으로 배치하고, 예외 경로(cold path)를 별도 위치로 분리합니다.
likely(cond)→__builtin_expect(!!(cond), 1)— 조건이 참일 가능성이 높음unlikely(cond)→__builtin_expect(!!(cond), 0)— 조건이 거짓일 가능성이 높음 (에러 경로 등)
/* include/linux/compiler.h */
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
/* 사용 예시 — 페이지 폴트 핸들러 */
if (unlikely(fault_signal_pending(fault, regs))) {
/* 시그널 처리 (cold path — 거의 실행되지 않음) */
return;
}
/* 정상 처리 (hot path — 대부분 여기로 진행) */
/* unlikely() 적용 시 GCC가 생성하는 코드 레이아웃 */
/* hot path가 직선(fall-through)이 되도록 배치 */
.Lhot_path:
testq %rax, %rax
jne .Lcold_path /* unlikely: 분기 안 하는 것이 일반적 */
/* ... 정상 처리 (fall-through) ... */
ret
/* cold path는 함수 끝으로 분리 (I-cache 효율) */
.section .text.unlikely
.Lcold_path:
/* ... 에러 처리 ... */
jmp .Lerror_handler
LFENCE 삽입, retpoline(간접 분기 보호), IBRS/IBPB(Indirect Branch Prediction Barrier) 등의 완화 기법을 사용합니다.
array_index_nospec() 매크로는 LFENCE를 사용해 투기적 배열 접근을 방지합니다.
스택/함수 호출 명령어
| 명령어 | AT&T 문법 | 설명 | 동작 |
|---|---|---|---|
| PUSH | pushq %rax | 스택 푸시 | RSP -= 8; [RSP] ← src |
| POP | popq %rax | 스택 팝 | dst ← [RSP]; RSP += 8 |
| CALL | call func | 함수 호출 | PUSH RIP; RIP ← target |
| RET | ret | 함수 복귀 | POP RIP |
| ENTER | enter $N, $0 | 스택 프레임 설정 | PUSH RBP; RBP ← RSP; RSP -= N |
| LEAVE | leave | 스택 프레임 해제 | RSP ← RBP; POP RBP |
- 정수 인자: RDI, RSI, RDX, RCX, R8, R9 (순서대로 6개)
- 반환값: RAX (+ RDX 128-bit)
- Callee-saved: RBX, RBP, R12-R15
- Caller-saved: RAX, RCX, RDX, RSI, RDI, R8-R11
- 스택 16-byte 정렬 필수 (CALL 직전)
- Red Zone: RSP 아래 128바이트 (리프 함수용, 커널에서는 비활성화)
시스템/특권 명령어
| 명령어 | 설명 | 특권 수준 |
|---|---|---|
| SYSCALL | 시스템 콜 진입 (MSR LSTAR → RIP) | Ring 3 → Ring 0 |
| SYSRET | 시스템 콜 복귀 (RCX → RIP) | Ring 0 → Ring 3 |
| INT n | 소프트웨어 인터럽트 (IDT[n]) | Ring 3 |
| IRET/IRETQ | 인터럽트/예외 복귀 | Ring 0 |
| CLI | 인터럽트 비활성화 (IF=0) | Ring 0 |
| STI | 인터럽트 활성화 (IF=1) | Ring 0 |
| HLT | 프로세서 정지 (인터럽트 대기) | Ring 0 |
| LGDT/SGDT | GDT 로드/저장 | Ring 0 / Ring 3 |
| LIDT/SIDT | IDT 로드/저장 | Ring 0 / Ring 3 |
| LLDT/LTR | LDT/TSS 로드 | Ring 0 |
| SWAPGS | GS 베이스와 MSR_KERNEL_GS_BASE 교환 | Ring 0 |
| WRMSR/RDMSR | MSR 쓰기/읽기 (ECX=번호) | Ring 0 |
| RDTSC | 타임스탬프 카운터 읽기 | Ring 3 (CR4.TSD로 제한 가능) |
| RDTSCP | RDTSC + 프로세서 ID (IA32_TSC_AUX) | Ring 3 |
| CPUID | CPU 정보 쿼리 | Ring 3 |
| INVLPG | TLB 엔트리 무효화 | Ring 0 |
| MOV CRn | 제어 레지스터 접근 | Ring 0 |
| MOV DRn | 디버그 레지스터 접근 | Ring 0 |
| IN/OUT | I/O 포트 입출력 | Ring 0 (또는 IOPL) |
| WBINVD | 캐시 라이트백 + 무효화 | Ring 0 |
| CLFLUSH | 캐시 라인 플러시 | Ring 3 |
| CLFLUSHOPT | 최적화된 캐시 라인 플러시 | Ring 3 |
| CLWB | 캐시 라인 라이트백 (무효화 없음) | Ring 3 |
| MFENCE | 메모리 펜스 (전체 직렬화) | Ring 3 |
| LFENCE | 로드 펜스 + 명령어 직렬화 | Ring 3 |
| SFENCE | 스토어 펜스 | Ring 3 |
| PAUSE | 스핀-웨이트 루프 힌트 | Ring 3 |
SYSCALL/SYSRET 메커니즘 상세
SYSCALL은 인터럽트(INT 0x80)보다 훨씬 빠른 시스템 콜 진입 방법입니다. 스택 전환이나 IDT 조회 없이 MSR에 미리 설정된 값으로 즉시 커널 코드로 점프합니다. 이 과정에서 하드웨어가 자동으로 수행하는 레지스터 조작을 이해하는 것이 중요합니다.
원자적/동기화 명령어
LOCK 접두사는 다음 명령어의 메모리 연산을 원자적으로 만듭니다. 캐시 라인 락 또는 버스 락을 통해 멀티코어 환경에서 원자성을 보장합니다.
| 명령어 | AT&T 문법 | 설명 |
|---|---|---|
| LOCK ADD | lock addq $1, (%rdi) | 원자적 덧셈 |
| LOCK SUB | lock subq $1, (%rdi) | 원자적 뺄셈 |
| LOCK INC/DEC | lock incq (%rdi) | 원자적 증가/감소 |
| LOCK AND/OR/XOR | lock andq %rax, (%rdi) | 원자적 비트 연산 |
| LOCK BTS/BTR/BTC | lock btsq $5, (%rdi) | 원자적 비트 테스트-세트/리셋/토글 |
| LOCK XADD | lock xaddq %rax, (%rdi) | 원자적 교환-덧셈 (old → rax) |
| LOCK CMPXCHG | lock cmpxchgq %rcx, (%rdi) | 원자적 비교-교환 (CAS) |
| LOCK CMPXCHG16B | lock cmpxchg16b (%rdi) | 128-bit CAS (RDX:RAX vs [mem]) |
| XCHG | xchgq %rax, (%rdi) | 교환 (메모리 시 암묵적 LOCK) |
LOCK CMPXCHG 패턴 (Compare-And-Swap)
/* atomic_cmpxchg: [rdi]가 rsi이면 rdx로 교체 */
atomic_cmpxchg:
movq %rsi, %rax /* RAX ← expected (old) */
lock cmpxchgq %rdx, (%rdi) /* if [RDI]==RAX then [RDI]←RDX */
/* RAX ← [RDI] (이전 값) */
ret /* RAX에 이전 값 반환 */
- 캐시 라인 락 (Cache Line Lock) — 대상 메모리가 단일 캐시 라인 내에 정렬되어 있으면, MESI 프로토콜의 Exclusive/Modified 상태를 이용해 해당 캐시 라인만 잠급니다. 다른 코어는 이 캐시 라인에 대한 Invalidate 요청이 완료될 때까지 대기합니다. 이 방식이 대부분의 경우에 사용되며 성능이 좋습니다.
- 버스 락 (Bus Lock) — 대상이 캐시 라인 경계를 걸쳐 있거나(split lock), 캐시 불가능 메모리인 경우 #LOCK 시그널로 전체 메모리 버스를 잠급니다. 이는 시스템 전체 성능에 심각한 영향을 줍니다.
CONFIG_SPLIT_LOCK_DETECT 옵션으로 split lock을 감지하고 경고합니다.
XCHG 기반 스핀락 구현
XCHG는 메모리 피연산자와 사용될 때 암묵적으로 LOCK이 적용됩니다. 이 특성을 이용한 스핀락은 가장 기본적인 커널 동기화 방식입니다.
/* 단순 스핀락 구현 (Test-and-Set) */
/* RDI = lock 주소, lock=0 (해제), lock=1 (획득) */
spin_lock:
movl $1, %eax
.Lretry:
xchgl %eax, (%rdi) /* 원자적 교환 (암묵적 LOCK) */
testl %eax, %eax /* 이전 값이 0이었으면 → 락 획득 성공 */
jnz .Lspin /* 아니면 스핀 */
ret
.Lspin:
pause /* 스핀 루프 힌트 (전력 절감 + 파이프라인) */
cmpl $0, (%rdi) /* Test: 락이 해제되었는지 읽기만 (LOCK 없이) */
jne .Lspin /* 아직 잠겨있으면 계속 스핀 */
jmp .Lretry /* 해제된 것 같으면 Set 재시도 */
spin_unlock:
movl $0, (%rdi) /* 단순 쓰기 (x86 스토어 순서 보장) */
ret
.Lspin 루프가 일반 cmpl(LOCK 없음)로 먼저 확인하는 이유는
LOCK 접두사가 캐시 라인을 Exclusive로 가져오기 때문입니다. 여러 코어가 동시에 XCHG를 시도하면 캐시 라인이 계속 바운싱(bouncing)됩니다.
먼저 Shared 상태에서 읽기만 수행하면 캐시 효율이 크게 개선됩니다.
XADD 기반 참조 카운팅
LOCK XADD는 원자적으로 값을 더하면서 이전 값을 반환합니다. 이는 참조 카운팅에서 이전 카운트 확인이 필요할 때 핵심적으로 사용됩니다.
/* 커널 refcount 감소 (arch/x86/include/asm/refcount.h 기반) */
static inline bool refcount_dec_and_test(refcount_t *r)
{
int val = -1; /* 감소할 값 */
asm volatile(LOCK_PREFIX "xaddl %0, %1"
: "+r"(val), "+m"(r->refs.counter)
:: "memory");
/* val = 이전 값, counter는 이미 감소됨 */
/* 이전 값이 1이었으면 → 감소 후 0 → 마지막 참조 해제 */
return val == 1;
}
/* 사용 패턴 */
if (refcount_dec_and_test(&obj->refcnt)) {
/* 참조 카운트 0 → 객체 해제 */
kfree(obj);
}
SIMD/벡터 명령어
SSE 명령어 (128-bit XMM)
| 명령어 | 설명 | 동작 |
|---|---|---|
| MOVAPS/MOVUPS | 정렬/비정렬 팩 단정도 이동 | XMM ← 128-bit 메모리/XMM |
| MOVAPD/MOVUPD | 정렬/비정렬 팩 배정도 이동 | XMM ← 128-bit 메모리/XMM |
| ADDPS/ADDPD | 팩 단정도/배정도 덧셈 | 각 요소 병렬 덧셈 |
| SUBPS/SUBPD | 팩 단정도/배정도 뺄셈 | 각 요소 병렬 뺄셈 |
| MULPS/MULPD | 팩 단정도/배정도 곱셈 | 각 요소 병렬 곱셈 |
| DIVPS/DIVPD | 팩 단정도/배정도 나눗셈 | 각 요소 병렬 나눗셈 |
| MINPS/MAXPS | 팩 최솟값/최댓값 | 각 요소 min/max |
| SQRTPS | 팩 제곱근 | 각 요소 sqrt |
| CMPPS | 팩 비교 | 조건 충족 시 0xFFFFFFFF, 아니면 0 |
| SHUFPS | 팩 셔플 | 즉시값으로 요소 재배치 |
| UNPCKLPS/UNPCKHPS | 하위/상위 인터리브 | 두 벡터의 요소 교차 배치 |
| PAND/POR/PXOR | 팩 정수 비트 논리 | 128-bit 논리 연산 |
| PADDB/W/D/Q | 팩 정수 덧셈 | 바이트/워드/더블워드/쿼드워드 요소별 |
| PMULLW/PMULLD | 팩 정수 곱셈 (하위) | 워드/더블워드 요소별 곱셈 |
| PCMPEQB/PCMPGTB | 팩 바이트 비교 (같음/큼) | 요소별 비교 → 마스크 |
| PSHUFD | 더블워드 셔플 | 즉시값으로 4개 요소 재배치 |
| PSHUFB | 바이트 셔플 (SSSE3) | 마스크 벡터로 바이트 재배치 |
AVX 명령어 (256-bit YMM)
| 명령어 | 설명 |
|---|---|
| VMOVAPS/VMOVUPS | 256-bit 정렬/비정렬 이동 |
| VADDPS/VADDPD | 3-오퍼랜드 팩 덧셈: dst ← src1 + src2 |
| VFMADD132PS/213/231 | FMA (Fused Multiply-Add): a*b+c 단일 명령 |
| VBROADCASTSS | 스칼라를 모든 요소에 복제 |
| VPERM2F128 | 128-bit 레인 순열 |
| VEXTRACTF128 | YMM에서 128-bit 레인 추출 |
| VINSERTF128 | YMM에 128-bit 레인 삽입 |
| VZEROALL/VZEROUPPER | YMM 상위 제로화 (SSE↔AVX 전환 시) |
AVX-512 명령어 (512-bit ZMM)
| 명령어 | 설명 |
|---|---|
| VMOVAPS zmm{k1}{z} | 마스크 적용 512-bit 이동 ({z}=제로 마스킹) |
| VADDPS zmm, zmm, zmm{k1} | 마스크 적용 512-bit 덧셈 |
| VADDPS zmm, zmm, m512/m32bcst | 브로드캐스트 로드 후 덧셈 |
| VPCMPD k1{k2}, zmm, zmm, imm | 팩 정수 비교 → 마스크 레지스터 |
| VPCOMPRESSD | 마스크 기반 압축 저장 |
| VPEXPANDD | 마스크 기반 확장 로드 |
| VPERMD/VPERMQ | 크로스-레인 순열 |
| VCONFLICTD | 충돌 감지 (히스토그램/scatter용) |
커널 핵심 명령어 심화
SYSCALL/SYSRET 진입 경로
x86_64 Linux에서 시스템 콜은 SYSCALL 명령어로 진입합니다. MSR_LSTAR에 저장된 entry_SYSCALL_64 주소로 점프합니다.
/* arch/x86/entry/entry_64.S — entry_SYSCALL_64 핵심 경로 */
entry_SYSCALL_64:
swapgs /* GS를 커널 per-cpu로 교환 */
movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
/* pt_regs 구조체 저장 */
pushq $__USER_DS /* SS */
pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* RSP */
pushq %r11 /* RFLAGS (SYSCALL이 R11에 저장) */
pushq $__USER_CS /* CS */
pushq %rcx /* RIP (SYSCALL이 RCX에 저장) */
pushq %rax /* syscall 번호 */
/* ... 나머지 레지스터 저장, 시스콜 디스패치 ... */
컨텍스트 전환 시 레지스터 저장/복원
시스템 콜이나 인터럽트로 커널에 진입할 때, 하드웨어와 소프트웨어가 역할을 분담하여 사용자 레지스터를 저장합니다. SYSCALL은 최소한의 레지스터만 하드웨어가 저장하고, 나머지는 커널 진입 코드(entry_SYSCALL_64)가 수동으로 저장합니다.
SWAPGS와 per-cpu 데이터 접근 패턴
SWAPGS는 GS 베이스 레지스터와 MSR_KERNEL_GS_BASE를 교환합니다. 커널 진입 시 per-cpu 데이터 접근을 위해 사용되며, 복귀 시 다시 교환합니다. GS 베이스를 통해 현재 CPU의 per-cpu 영역에 빠르게 접근할 수 있습니다.
/* SWAPGS + per-cpu 접근 패턴 */
/* 1. 커널 진입 시: GS를 커널 per-cpu 영역으로 교환 */
/* SWAPGS → GS.base = 현재 CPU의 per-cpu 오프셋 */
/* 2. per-cpu 변수 접근 (GS 세그먼트 오버라이드) */
/* movq %gs:offset, %rax — per-cpu 변수 읽기 */
/* arch/x86/include/asm/percpu.h — 간략화 */
#define this_cpu_read_8(pcp) ({ \
u64 val; \
asm volatile("movq %%gs:%1, %0" \
: "=r"(val) \
: "m"(pcp)); \
val; \
})
/* 사용 예: 현재 CPU의 current task 가져오기 */
static inline struct task_struct *get_current(void)
{
return this_cpu_read_8(current_task);
/* 컴파일 결과: movq %gs:current_task, %rax */
}
/* 3. 커널 복귀 시: SWAPGS로 GS를 사용자 공간 값으로 복원 */
/* SWAPGS → GS.base = 사용자 프로세스의 GS (TLS 등) */
testb $3, CS(%rsp) — CS의 RPL(Ring Privilege Level) 비트가 3(유저)이면 SWAPGS 필요.
LOCK CMPXCHG — 커널 atomic_cmpxchg
/* arch/x86/include/asm/cmpxchg.h — 간략화 */
static inline u64 __cmpxchg(volatile void *ptr, u64 old, u64 new)
{
u64 prev;
asm volatile(LOCK_PREFIX "cmpxchgq %2, %1"
: "=a"(prev), "+m"(*(volatile u64 *)ptr)
: "r"(new), "0"(old)
: "memory");
return prev;
}
INVLPG — TLB 무효화
/* 단일 페이지 TLB 엔트리 무효화 */
static inline void __invlpg(unsigned long addr)
{
asm volatile("invlpg (%0)" :: "r"(addr) : "memory");
}
WRMSR/RDMSR — MSR 접근
static inline void wrmsr(u32 msr, u32 low, u32 high)
{
asm volatile("wrmsr"
:: "c"(msr), "a"(low), "d"(high)
: "memory");
}
명령어 인코딩
x86_64 명령어는 가변 길이로, 최대 15바이트까지 가능합니다. 레거시 접두사, REX, VEX, EVEX 네 가지 인코딩 체계가 있습니다.
인코딩 실전 예제
실제 명령어가 어떻게 바이트로 인코딩되는지 두 가지 예제를 통해 살펴봅니다.
movq $42, %rax는 실제로 movl $42, %eax(5바이트)로 대체 가능합니다 — 32-bit 쓰기가 자동으로 상위 32-bit를 제로화하므로 결과는 동일하지만 5바이트 절약됩니다.
xorl %eax, %eax(2바이트)는 movq $0, %rax(10바이트)보다 효율적인 레지스터 제로화 방법입니다.
또한 xorl은 현대 프로세서에서 제로화 관용어(zeroing idiom)로 인식되어 실행 유닛을 사용하지 않고 레지스터 파일에서 직접 처리됩니다.
최근 아키텍처 확장 (v6.14+)
Intel APX — Advanced Performance Extensions (v6.16+)
커널 6.16에서 Intel APX(Advanced Performance Extensions) 지원이 추가되었습니다. APX는 x86-64의 주요 한계를 해소하는 ISA 확장으로, 다음과 같은 핵심 기능을 제공합니다:
| 기능 | 설명 | 효과 |
|---|---|---|
| 32개 GPR | 기존 16개 → 32개 범용 레지스터 (R16~R31) | 레지스터 스필/리로드 감소, 함수 호출 오버헤드 감소 |
| REX2 접두사 | 새로운 2바이트 REX 접두사 (확장 레지스터 인코딩) | R16~R31 접근을 위한 인코딩 지원 |
| NDD (New Data Destination) | 3-오퍼랜드 형식 (ADD r1, r2, r3) | 소스 보존 연산 — MOV+연산 패턴 제거 |
| NF (No Flags) | 플래그 레지스터 미수정 옵션 | 플래그 의존성 제거, 비순차 실행 개선 |
4096 CPU 코어 지원 (v6.14+)
커널 6.14에서 x86_64의 최대 CPU 코어 수 제한이 2048에서 4096으로 확장되었습니다 (CONFIG_NR_CPUS 최댓값 증가). 이는 AMD EPYC/Intel Xeon의 고코어 서버와 멀티소켓 시스템의 확장성을 지원합니다.
시스템 레지스터 심화
x86-64의 시스템 레지스터는 CPU 모드 전환, 페이징, 디버깅, 성능 모니터링 등 커널 동작의 핵심을 제어합니다. Control Register(CR0-CR4), Debug Register(DR0-DR7), MSR(Model-Specific Register)의 비트 필드를 정확히 이해해야 커널 초기화 코드와 예외 처리를 분석할 수 있습니다.
디버그 레지스터 (DR0-DR7)
| 레지스터 | 용도 | 커널 사용 |
|---|---|---|
DR0-DR3 | 하드웨어 브레이크포인트 주소 (4개) | ptrace(PTRACE_POKEUSER) — perf hw_breakpoint |
DR6 | 디버그 상태 (어떤 BP가 트리거됐는지) | #DB 예외 핸들러에서 읽기 |
DR7 | 디버그 제어 (활성화, 조건, 길이) | bit 0-7: BP 활성화, bit 16-31: 조건/길이 |
/* DR7 비트 필드 */
/* bit 0,2,4,6 (L0-L3): 로컬 BP 활성화 */
/* bit 1,3,5,7 (G0-G3): 글로벌 BP 활성화 */
/* bit 16-17 (R/W0): DR0 조건 (00=실행, 01=쓰기, 10=I/O, 11=읽기/쓰기) */
/* bit 18-19 (LEN0): DR0 길이 (00=1B, 01=2B, 10=8B, 11=4B) */
/* bit 20-23: DR1 조건/길이, bit 24-27: DR2, bit 28-31: DR3 */
/* 커널 hw_breakpoint 사용 예 */
/* perf_event_create_kernel_counter() → arch_install_hw_breakpoint() */
/* → set_debugreg(addr, 0) // DR0에 주소 설정 */
/* → set_debugreg(ctrl, 7) // DR7에 조건 설정 */
성능 모니터링 카운터 (PMC)
| MSR | 이름 | 용도 |
|---|---|---|
0x186-0x189 | IA32_PERFEVTSELx | 이벤트 선택 (EventSelect, UMask, USR, OS, EN) |
0xC1-0xC4 | IA32_PMCx | 카운터 값 (48비트) |
0x38D | IA32_FIXED_CTR_CTRL | 고정 카운터 제어 |
0x309-0x30B | IA32_FIXED_CTRx | 고정 카운터 (inst_retired, cpu_clk, ref_clk) |
0x38F | IA32_PERF_GLOBAL_CTRL | 전체 카운터 활성화 |
0x390 | IA32_PERF_GLOBAL_STATUS | 오버플로 상태 (NMI 트리거) |
/* PERFEVTSELx 비트 필드 */
/* bit 0-7 : EventSelect (이벤트 코드) */
/* bit 8-15 : UMask (이벤트 세분류) */
/* bit 16 : USR (유저 모드 카운트) */
/* bit 17 : OS (커널 모드 카운트) */
/* bit 20 : INT (오버플로 시 인터럽트/NMI) */
/* bit 22 : EN (카운터 활성화) */
/* bit 23 : INV (조건 반전) */
/* bit 24-31 : CMASK (카운트 마스크) */
/* 커널 perf 서브시스템 사용 흐름 */
/* perf_event_open() → x86_pmu_hw_config() */
/* → wrmsrl(MSR_P6_EVNTSEL0, config) → wrmsrl(MSR_P6_PERFCTR0, 0) */
/* 카운터 오버플로 → NMI → intel_pmu_handle_irq() → 샘플 저장 */
가상화 확장 (VMX) 레지스터
| 구분 | 설명 | 커널 사용 |
|---|---|---|
| VMCS | Virtual Machine Control Structure (4KB) | KVM: vmcs_write*() |
| VMXON/VMXOFF | VMX 모드 진입/탈출 | kvm_cpu_vmxon() |
| VMLAUNCH/VMRESUME | 게스트 실행 시작/재개 | vmx_vcpu_run() |
| VMPTRLD/VMPTRST | 현재 VMCS 로드/저장 | vCPU 스위칭 시 |
| VMREAD/VMWRITE | VMCS 필드 읽기/쓰기 | 게스트 레지스터, 제어 필드 |
| EPT | Extended Page Table (2단계 주소 변환) | 게스트 물리→호스트 물리 |
| VPID | Virtual Processor ID (TLB 태깅) | VM 전환 시 TLB 보존 |
MTRR과 PAT: IA32_MTRR* MSR은 물리 메모리 범위별 캐시 정책(UC/WC/WT/WB/WP)을 설정합니다. IA32_PAT는 페이지 테이블 엔트리의 PWT/PCD/PAT 비트 조합으로 8가지 메모리 타입 중 하나를 선택합니다. 커널은 memtype_reserve()로 디바이스 메모리의 MTRR/PAT 정합성을 관리합니다.
SIMD/벡터 확장 심화
x86-64의 SIMD 확장은 SSE → AVX → AVX-512 → AMX로 발전해왔습니다. 커널은 암호화(AES-NI), 체크섬(CRC32c), RAID(XOR/P+Q), 문자열 연산에서 SIMD를 활용합니다. 각 세대의 레지스터 폭, 명령어 형식, 커널 사용 패턴을 이해해야 합니다.
| 세대 | 레지스터 | 폭 | 레지스터 수 | 인코딩 | 커널 주요 용도 |
|---|---|---|---|---|---|
| SSE | XMM | 128비트 | 16 | 레거시 (66/F2/F3) | AES-NI, CRC32c |
| AVX | YMM | 256비트 | 16 | VEX (C4/C5) | RAID6, SHA |
| AVX-512 | ZMM+k | 512비트 | 32+8 | EVEX (62) | RAID6, ChaCha20 |
| AMX | TMM | 8KB 타일 | 8 | VEX | 유저공간 AI/ML |
AVX-512 주파수 감소: AVX-512 사용 시 일부 CPU(Skylake 서버 등)에서 코어 주파수가 낮아집니다(License Level 1→2→3). 커널의 RAID6는 이를 고려하여 raid6_avx512x2_gen_syndrome()이 항상 최적은 아닐 수 있으며, lib/raid6/algos.c에서 부팅 시 벤치마크로 최적 구현을 선택합니다.
관련 문서
- 어셈블리 종합 — GCC 인라인 어셈블리, AT&T/Intel 문법, 호출 규약
- SIMD 명령과 커널 개발 — 커널 공간 SIMD 사용, FPU 컨텍스트
- CPUID 명령어 심화 — CPU 기능 탐지, Leaf 구조
- GNU Assembler (as) — GAS 지시자, 섹션, 매크로
- ARM64 명령어셋 (ISA) — ARM64 RISC 아키텍처 비교
- RISC-V 명령어셋 (ISA) — RISC-V 모듈형 ISA
- MIPS 명령어셋 (ISA) — MIPS 전통 RISC