ARM64(AArch64) 아키텍처의 명령어를 GAS 문법 기준으로 종합 정리합니다. RISC 설계 철학과 고정 32-bit 명령어 길이, 31개 범용 레지스터와 예외 레벨(EL0-EL3), NEON(128-bit)/SVE(가변 길이) 벡터 레지스터, 주소 지정 모드, 데이터 전송·산술·논리·분기·스택·시스템·원자적·SIMD 명령어 카테고리 표, A64 인코딩 다이어그램, 커널 핵심 명령어(SVC, ERET, LDXR/STXR, DMB/DSB/ISB) 심화까지 Linux 커널 개발에 필요한 ARM64 ISA 전체를 다룹니다.
전제 조건:어셈블리 종합을 먼저 읽으세요.
ARM64 명령어 레퍼런스는 RISC 레지스터-레지스터 모델, 조건 코드, Load/Store 아키텍처에 대한 기본 이해가 필요합니다.
일상 비유: ARM64는 레고 블록과 같습니다.
모든 블록(명령어)이 같은 크기(32-bit)로 통일되어 있어 조립(디코딩)이 빠르고 효율적입니다. 복잡한 작업은 여러 단순 블록을 조합해서 구성합니다.
핵심 요약
RISC 아키텍처 — 고정 32-bit 명령어, Load/Store 모델, 레지스터-레지스터 연산.
예외 레벨 전환: ARM64는 네 개의 예외 레벨(EL0~EL3)을 지원합니다.
SVC/HVC/SMC 명령으로 상위 레벨에 진입하며, 하드웨어가 자동으로 ELR_ELn(복귀 주소)과 SPSR_ELn(PSTATE 백업)을 저장합니다.
ERET로 하위 레벨로 복귀할 때 이 값들이 복원됩니다. 각 EL은 독립적인 SP를 갖고, 시스템 레지스터에 _ELn 접미사를 붙여 레벨별로 분리됩니다.
레지스터 셋
범용 레지스터
64-bit
32-bit
AAPCS64 용도
X0-X7
W0-W7
인자/반환값 (X0=1st 반환, X0-X7=인자)
X8
W8
간접 결과 레지스터 (XR)
X9-X15
W9-W15
Caller-saved (임시)
X16
W16
IP0 (Intra-Procedure scratch)
X17
W17
IP1 (Intra-Procedure scratch)
X18
W18
플랫폼 레지스터 (Linux: shadow call stack)
X19-X28
W19-W28
Callee-saved
X29
W29
프레임 포인터 (FP)
X30
W30
링크 레지스터 (LR)
SP
WSP
스택 포인터 (EL별 독립)
XZR
WZR
제로 레지스터 (읽기=0, 쓰기=버림)
W 레지스터 쓰기의 제로 확장: ARM64에서 W 레지스터에 값을 쓰면 상위 32비트가 자동으로 0으로 클리어됩니다. 이는 x86-64에서 32-bit 레지스터 쓰기(EAX 등)가 상위 32비트를 클리어하는 것과 동일한 설계입니다. 반면 16-bit/8-bit 하위 접근은 존재하지 않으며, 이를 통해 부분 레지스터 갱신 문제(partial register stall)가 원천적으로 방지됩니다.
PSTATE 조건 플래그
플래그
이름
설명
N
Negative
결과의 최상위 비트 (부호)
Z
Zero
결과가 0이면 세트
C
Carry
부호 없는 올림/빌림
V
Overflow
부호 있는 오버플로
시스템 레지스터 (주요)
레지스터
설명
SCTLR_EL1
시스템 제어 (MMU, 캐시, 정렬 체크 등)
TCR_EL1
변환 제어 (페이지 크기, 주소 범위)
TTBR0_EL1 / TTBR1_EL1
페이지 테이블 베이스 (유저/커널)
ESR_EL1
예외 신드롬 (예외 원인 정보)
FAR_EL1
폴트 주소
VBAR_EL1
예외 벡터 베이스 주소
ELR_EL1
예외 링크 레지스터 (복귀 주소)
SPSR_EL1
저장된 프로세서 상태 (PSTATE 백업)
DAIF
인터럽트 마스크 (Debug, SError, IRQ, FIQ)
CurrentEL
현재 예외 레벨
SIMD/벡터 레지스터
확장
레지스터
크기
접근 방식
NEON/FP
V0-V31
128-bit
Bn(8), Hn(16), Sn(32), Dn(64), Qn(128)
SVE
Z0-Z31
128~2048-bit (가변)
구현에 따라 128-bit 단위로 확장
SVE 프레디킷
P0-P15
가변
벡터 요소별 마스크
SVE FFR
FFR
가변
First Fault Register
FP 제어
FPCR/FPSR
32-bit
라운딩 모드, 예외 상태
NEON vs SVE: NEON은 고정 128-bit 벡터로 모든 ARM64 구현에서 지원되며, 암호화(AES, SHA), 미디어 코덱 등에 널리 사용됩니다.
SVE(Scalable Vector Extension)는 128~2048-bit 가변 길이를 지원하며, VL(Vector Length)이 실행 시 결정됩니다.
프레디킷 레지스터(P0-P15)를 통해 벡터 요소별 조건부 실행이 가능하여 루프 끝단 처리가 우아합니다.
Linux 커널에서 NEON/SVE를 사용하려면 반드시 kernel_neon_begin() / kernel_neon_end()로 FPU 상태를 보호해야 합니다.
주소 지정 모드
모드
문법
설명
예제
즉시값
#imm
상수 값
mov x0, #42
레지스터
Xn/Wn
레지스터 값
mov x0, x1
베이스
[Xn]
[Xn]
ldr x0, [x1]
오프셋
[Xn, #imm]
[Xn + imm]
ldr x0, [sp, #8]
Pre-index
[Xn, #imm]!
Xn += imm; [Xn]
ldr x0, [sp, #-16]!
Post-index
[Xn], #imm
[Xn]; Xn += imm
ldr x0, [sp], #16
레지스터 오프셋
[Xn, Xm]
[Xn + Xm]
ldr x0, [x1, x2]
확장 오프셋
[Xn, Xm, LSL #n]
[Xn + Xm << n]
ldr x0, [x1, x2, lsl #3]
PC 상대
label
[PC + offset]
adr x0, label
리터럴
=value
리터럴 풀에서 로드
ldr x0, =0x12345678
Pre-index vs Post-index 비교:
Pre-index [Xn, #imm]! — 먼저 베이스 갱신, 그 다음 접근. 스택 할당(push)에 적합:
stp x29, x30, [sp, #-16]! → SP를 먼저 16 감소, 그 위치에 저장
Post-index [Xn], #imm — 먼저 접근, 그 다음 베이스 갱신. 스택 해제(pop)에 적합:
ldp x29, x30, [sp], #16 → 현재 SP에서 로드, 그 후 SP를 16 증가
ADRP + ADD 패턴 (PC-relative 주소 생성): ARM64에서 즉시값으로 표현할 수 있는 주소 범위가 제한적이므로, 커널 코드에서 심볼 주소를 구할 때 두 명령어의 조합을 사용합니다.
ADRP는 심볼이 있는 4KB 페이지 주소(상위 비트)를 PC 상대로 계산하고, ADD로 페이지 내 오프셋(하위 12-bit)을 더합니다.
이 패턴은 ±4GB 범위의 임의 주소를 단 두 명령어로 생성할 수 있어 매우 효율적입니다.
/* ADRP + ADD: 심볼의 전체 주소 계산 */adrp x0, my_global /* x0 = my_global이 있는 4KB 페이지 주소 */add x0, x0, :lo12:my_global /* x0 += 페이지 내 오프셋 (하위 12비트) *//* 이제 x0 = &my_global 전체 주소 *//* ADRP + LDR: 심볼에서 직접 값 로드 */adrp x1, my_global
ldr x1, [x1, :lo12:my_global] /* my_global 값 직접 로드 */
LDP/STP (Load/Store Pair) 이점:
단일 명령어로 두 레지스터를 동시에 로드/저장하여 코드 밀도가 2배 향상됩니다.
128-bit 메모리 버스를 완전히 활용하며, 캐시 라인 접근 효율이 높습니다.
함수 프롤로그/에필로그에서 FP(X29)와 LR(X30)을 한 번에 저장/복원하는 관용 패턴으로 사용됩니다.
SP 기반 LDP/STP는 원자적 128-bit 접근을 보장하여 struct 복사에도 유용합니다.
데이터 전송 명령어
명령어
문법
설명
MOV
mov x0, x1
레지스터 → 레지스터 이동
MOVZ
movz x0, #0x1234, lsl #16
즉시값 이동 + 나머지 제로화
MOVN
movn x0, #0
즉시값 반전 이동
MOVK
movk x0, #0x5678
16-bit 즉시값 삽입 (나머지 보존)
LDR
ldr x0, [x1, #8]
메모리 → 레지스터 (64-bit)
LDRB/LDRH
ldrb w0, [x1]
바이트/하프워드 제로 확장 로드
LDRSB/LDRSH/LDRSW
ldrsw x0, [x1]
부호 확장 로드
STR
str x0, [x1, #8]
레지스터 → 메모리 (64-bit)
LDP
ldp x0, x1, [sp]
쌍 레지스터 로드 (128-bit)
STP
stp x29, x30, [sp, #-16]!
쌍 레지스터 저장
ADRP
adrp x0, symbol
PC 상대 4KB 페이지 주소
ADR
adr x0, label
PC 상대 주소 (±1MB)
LDAR
ldar x0, [x1]
Acquire 시맨틱 로드
STLR
stlr x0, [x1]
Release 시맨틱 저장
PRFM
prfm pldl1keep, [x0]
메모리 프리페치
LDAR/STLR — Acquire/Release 시맨틱과 Linux 메모리 모델:
LDAR (Load-Acquire): 이 로드 이후의 모든 메모리 접근이 이 로드 이전에 재배치되지 않음을 보장합니다. Linux의 smp_load_acquire()에 매핑됩니다.
STLR (Store-Release): 이 저장 이전의 모든 메모리 접근이 이 저장 이후로 재배치되지 않음을 보장합니다. Linux의 smp_store_release()에 매핑됩니다.
DMB 배리어보다 경량이며, 특정 주소에 대한 순서만 강제하므로 성능 영향이 적습니다.
Acquire-Release 쌍은 단방향 배리어로, 전체 배리어(DMB)보다 파이프라인 효율이 높습니다.
ADRP + LDR 패턴: 전역 변수 접근
/* C 코드: extern unsigned long jiffies; val = jiffies; *//* 컴파일러가 생성하는 ADRP + LDR 패턴 */adrp x0, jiffies /* x0 = jiffies의 4KB 페이지 주소 */ldr x0, [x0, :lo12:jiffies] /* x0 = *jiffies (페이지 내 오프셋으로 로드) *//* GOT(Global Offset Table) 경유 패턴 (모듈 코드) */adrp x0, :got:jiffies /* GOT 엔트리의 페이지 주소 */ldr x0, [x0, :got_lo12:jiffies] /* GOT에서 실제 주소 로드 */ldr x0, [x0] /* 실제 값 로드 */
Acquire/Release vs 전체 배리어 비교:LDAR/STLR은 주소별 순서 보장만 하므로, 관련 없는 메모리 접근은 여전히 재배치될 수 있습니다.
전체 순서가 필요한 경우(예: smp_mb())에는 DMB ISH를 사용해야 합니다.
Linux 커널에서 일반적인 READ_ONCE()/WRITE_ONCE()는 배리어를 포함하지 않으며, 컴파일러 재배치만 방지합니다.
CCMP (Conditional Compare) — 다중 조건 분기 최적화:CCMP는 이전 조건이 참일 때만 비교를 수행하고, 거짓이면 즉시값으로 NZCV 플래그를 설정합니다.
이를 통해 if (a == 1 && b == 2) 같은 다중 조건을 분기 없이 단일 조건 체인으로 평가할 수 있습니다.
x86에서는 CMP + JNE + CMP + JNE로 2개의 분기가 필요하지만, ARM64에서는 CMP + CCMP + B.cond로 1개의 분기만 사용합니다.
분기 예측 실패 패널티를 줄이고, 파이프라인 효율을 높이는 ARM64의 핵심 최적화 기법입니다.
OR 조건(||)도 nzcv 즉시값을 적절히 설정하여 구현할 수 있습니다.
/* C 코드: if (x == 1 && y == 2) goto target; *//* x86-64 방식 (분기 2개) *//* cmp x0, #1 *//* b.ne skip *//* cmp x1, #2 *//* b.eq target *//* skip: *//* ARM64 CCMP 방식 (분기 1개) */cmp x0, #1/* x == 1? 플래그 세트 */ccmp x1, #2, #0, eq /* 이전이 EQ이면 y==2 비교, 아니면 NZCV=0(NE) */b.eq target /* 두 조건 모두 참이면 분기 *//* C 코드: if (x == 1 || y == 2) goto target; */cmp x0, #1/* x == 1? */ccmp x1, #2, #4, ne /* 이전이 NE이면 y==2 비교, EQ이면 NZCV=4(Z=1,EQ) */b.eq target /* 둘 중 하나라도 참이면 분기 */
주의: LDXR과 STXR 사이에는 다른 메모리 접근을 최소화해야 합니다. 과도한 명령어는 독점 상태를 불필요하게 해제시킬 수 있습니다.
CLREX 명령으로 명시적으로 독점 상태를 해제할 수 있으며, 컨텍스트 스위치 시 커널이 이를 실행합니다.
Linux 커널 대체 패치 (Alternative Patching):arch/arm64/include/asm/atomic_ll_sc.h에 LDXR/STXR 기본 구현이, arch/arm64/include/asm/atomic_lse.h에 LSE 구현이 있습니다.
부팅 시 cpufeature 프레임워크가 ARMv8.1 LSE 지원을 감지하면, .altinstructions 섹션을 통해
LL/SC 루프 코드를 LSE 단일 명령어(CAS, SWP, LDADD 등)로 바이너리 패치합니다.
이를 통해 단일 커널 이미지로 ARMv8.0과 ARMv8.1+ 하드웨어를 모두 최적으로 지원합니다.
SIMD/벡터 명령어
NEON 명령어 (128-bit)
명령어
설명
LD1 {v0.4s}, [x0]
벡터 로드 (4×32-bit)
ST1 {v0.4s}, [x0]
벡터 저장
ADD v0.4s, v1.4s, v2.4s
벡터 정수 덧셈
FADD v0.4s, v1.4s, v2.4s
벡터 부동소수점 덧셈
FMUL v0.4s, v1.4s, v2.4s
벡터 부동소수점 곱셈
MUL v0.4s, v1.4s, v2.4s
벡터 정수 곱셈
AND v0.16b, v1.16b, v2.16b
벡터 비트 AND
CMHI v0.4s, v1.4s, v2.4s
벡터 부호없는 > 비교
CMEQ v0.4s, v1.4s, v2.4s
벡터 같음 비교
TBL v0.16b, {v1.16b}, v2.16b
테이블 룩업
ZIP1/ZIP2 v0.4s, v1.4s, v2.4s
벡터 인터리브
UZP1/UZP2
벡터 디인터리브
TRN1/TRN2
벡터 전치
DUP v0.4s, w0
스칼라를 모든 요소에 복제
INS v0.s[0], w0
스칼라 요소 삽입
UMOV w0, v0.s[0]
벡터 요소 추출
ADDV s0, v0.4s
벡터 리덕션 합계
SVE 명령어 (가변 길이)
명령어
설명
LD1 {z0.s}, p0/z, [x0]
프레디킷 마스크 벡터 로드
ST1 {z0.s}, p0, [x0]
프레디킷 마스크 벡터 저장
ADD z0.s, p0/m, z0.s, z1.s
프레디킷 벡터 덧셈
MUL z0.s, p0/m, z0.s, z1.s
프레디킷 벡터 곱셈
WHILELT p0.s, x0, x1
루프 프레디킷 생성 (x0 < x1)
PTRUE p0.s
모든 요소 활성 프레디킷
INDEX z0.s, #0, #1
인덱스 벡터 생성 (0,1,2,3,...)
COMPACT z0.s, p0, z1.s
활성 요소 압축
CNTW x0
벡터 워드 요소 수 반환
커널 핵심 명령어 심화
SVC #0 el0_svc 진입 경로
/* 유저 공간에서 시스템 콜: SVC #0 *//* ELR_EL1 ← PC+4, SPSR_EL1 ← PSTATE, PSTATE.DAIF 마스크 *//* PC ← VBAR_EL1 + 0x400 (EL0 64-bit Synchronous) *//* arch/arm64/kernel/entry.S — 예외 벡터 테이블 */
.align 11/* 2048-byte 정렬 */vectors:
/* Current EL with SP0 */
ventry el1_sync_invalid /* Synchronous */
ventry el1_irq_invalid /* IRQ */
ventry el1_fiq_invalid /* FIQ */
ventry el1_error_invalid /* SError *//* Current EL with SPx */
ventry el1h_sync /* Synchronous */
ventry el1h_irq /* IRQ *//* ... *//* Lower EL (EL0) using AArch64 */
ventry el0_sync /* Synchronous (SVC 포함) */
ventry el0_irq /* IRQ *//* ... */
MSR/MRS 시스템 레지스터 접근
/* SCTLR_EL1 읽기/쓰기 */mrs x0, sctlr_el1 /* 읽기 */orr x0, x0, #(1 << 0) /* MMU 활성화 비트 세트 */msr sctlr_el1, x0 /* 쓰기 */isb/* 명령어 동기화 *//* 페이지 테이블 베이스 설정 */msr ttbr0_el1, x0 /* 유저 페이지 테이블 */msr ttbr1_el1, x1 /* 커널 페이지 테이블 */tlbi vmalle1 /* 전체 TLB 무효화 */dsb ish /* 데이터 동기화 */isb/* 명령어 동기화 */
ERET 예외 복귀
/* EL1에서 EL0으로 복귀 */msr elr_el1, x0 /* 복귀 주소 설정 */msr spsr_el1, x1 /* 프로세서 상태 복원 */eret/* 예외 복귀: PC←ELR, PSTATE←SPSR */
KPTI (Kernel Page Table Isolation) — TTBR0/TTBR1 전환:
Meltdown 유사 공격을 방어하기 위해 Linux 커널은 유저 공간 복귀 시 TTBR0_EL1을 전환하여 커널 매핑을 제거합니다.
TTBR1_EL1: 커널 주소 공간 (0xFFFF...) — 항상 커널 페이지 테이블을 가리킵니다.
TTBR0_EL1: 유저 주소 공간 (0x0000...) — EL0 복귀 시 커널 매핑이 제거된 별도 페이지 테이블로 전환됩니다.
ADDS X0, X1, X2이면 S=1이므로: 0xAB020020 (bit[29]이 1로 변경)
SUB X0, X1, X2이면 op=1이므로: 0xCB020020 (bit[30]이 1로 변경)
ADD W0, W1, W2이면 sf=0이므로: 0x0B020020 (bit[31]이 0으로 변경)
보안 확장 (PAC/BTI/MTE)
ARMv8.3+ 이후 도입된 보안 확장은 커널과 유저 공간 모두에서 메모리 안전성과 제어 흐름 무결성을 하드웨어 수준으로 강화합니다. PAC(Pointer Authentication), BTI(Branch Target Identification), MTE(Memory Tagging Extension)는 각각 ROP/JOP 공격, 간접 분기 하이재킹, use-after-free/buffer-overflow를 방어합니다.
/* PAC 커널 함수 프롤로그/에필로그 (GCC 자동 생성) */
my_function:
paciasp /* LR에 PAC 서명 (SP를 컨텍스트로) */
stp x29, x30, [sp, #-16]!
mov x29, sp
/* ... 함수 본문 ... */
ldp x29, x30, [sp], #16
autiasp /* PAC 검증 (실패 시 fault → SIGILL) */
ret
/* BTI 보호 함수 진입점 */
my_indirect_target:
bti c /* BL로만 도달 가능한 타겟 표시 */
stp x29, x30, [sp, #-16]!
/* ... *//* MTE 커널 사용 (KASAN HW 모드) *//* CONFIG_KASAN_HW_TAGS=y → 슬랩 할당 시 자동 태깅 *//* kmalloc() → kasan_set_tag() → IRG + STG *//* kfree() → 태그 변경 → 이후 접근 시 Tag Check Fault */
캐시 & TLB 관리 명령어
ARM64는 x86과 달리 소프트웨어가 명시적으로 캐시 유지보수와 TLB 무효화를 수행해야 합니다. 커널의 DMA 매핑, 페이지 테이블 수정, 코드 패칭 등에서 정확한 캐시/TLB 관리가 필수입니다.
명령어
대상
동작
커널 사용처
DC CIVAC
데이터 캐시
Clean + Invalidate by VA
DMA, 비일관 I/O
DC CVAC
데이터 캐시
Clean by VA (PoC)
DMA CPU→디바이스
DC CVAU
데이터 캐시
Clean by VA (PoU)
코드 패칭 전
DC ZVA
데이터 캐시
Zero by VA
clear_page() 최적화
IC IVAU
명령어 캐시
Invalidate by VA (PoU)
코드 패칭 후
IC IALLU
명령어 캐시
Invalidate All (Inner Share)
모듈 로딩
TLBI VMALLE1IS
TLB
EL1 전체 무효화 (IS)
flush_tlb_all()
TLBI VAE1IS
TLB
VA+ASID 기반 무효화 (IS)
flush_tlb_page()
TLBI ASIDE1IS
TLB
ASID 전체 무효화 (IS)
flush_tlb_mm()
⚠️
DSB/ISB 필수: ARM64에서 캐시/TLB 유지보수 명령어는 비동기적으로 실행될 수 있습니다. DSB(Data Synchronization Barrier)로 완료를 보장하고, 코드 패칭 시 ISB(Instruction Synchronization Barrier)로 파이프라인을 flush해야 합니다. x86의 invlpg는 동기적이므로 이 문제가 없지만, ARM64에서는 누락하면 stale 캐시/TLB로 인한 크래시가 발생합니다.