ARM64 (AArch64) 명령어셋 레퍼런스

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 모델, 레지스터-레지스터 연산.
  • 31개 범용 레지스터 — X0-X30(64-bit)/W0-W30(32-bit), XZR=제로, SP=스택, LR=X30.
  • 예외 레벨 — EL0(유저), EL1(커널/OS), EL2(하이퍼바이저), EL3(시큐어 모니터).
  • NEON/SVE — NEON: V0-V31(128-bit 고정), SVE: Z0-Z31(128~2048-bit 가변) + P0-P15 프레디킷.
  • 독점 접근 — LDXR/STXR(독점 로드/저장)로 원자적 CAS 구현, ARMv8.1 LSE로 CAS/SWP 단일 명령어.

단계별 이해

  1. 레지스터 구조 파악
    X0-X30(64-bit), W0-W30(32-bit 하위), XZR/WZR(제로), SP의 관계를 먼저 익힙니다.
  2. Load/Store 모델 이해
    ARM64에서 메모리 접근은 LDR/STR만 사용합니다. 산술은 레지스터 간만 가능합니다.
  3. 예외 레벨과 시스템 레지스터
    EL0-EL3 구조와 MSR/MRS로 접근하는 시스템 레지스터를 이해합니다.
  4. 조건 코드와 분기
    PSTATE 플래그(N,Z,C,V)와 B.cond 조건부 분기 패턴을 학습합니다.

아키텍처 개요

ARM64(AArch64)는 ARMv8-A에서 도입된 64-bit RISC 아키텍처입니다. ARMv1(1985) → ARMv7(32-bit A32/T32) → ARMv8(64-bit A64, 2011) → ARMv9(2021, SVE2/MTE/CCA)로 발전했습니다.

특성ARM64 (AArch64)
설계 철학RISC (Reduced Instruction Set Computer)
명령어 길이고정 32-bit (4바이트)
엔디언바이-엔디언 (기본 리틀 엔디언)
범용 레지스터31개 (64-bit) + ZR + SP
주소 공간가상 48-bit (256TB) 또는 52-bit (ARMv8.2-LVA)
예외 레벨EL0(유저), EL1(OS), EL2(하이퍼바이저), EL3(시큐어)
실행 상태AArch64(A64), AArch32(A32/T32) 전환 가능
페이지 크기4KB, 16KB, 64KB
ARM64 예외 레벨 (Exception Level) 계층 구조 EL3 — Secure Monitor ATF (ARM Trusted Firmware) EL2 — Hypervisor KVM / Xen (Stage-2 주소 변환) EL1 — OS / Kernel Linux Kernel (TTBR0/TTBR1, VBAR_EL1) EL0 — User Application 유저 프로세스 (가장 낮은 특권) SVC #0 HVC #0 SMC #0 ERET ERET ERET SVC: Supervisor Call (EL0→EL1) HVC: Hypervisor Call (EL1→EL2) SMC: Secure Monitor Call (→EL3) ERET: Exception Return — ELR_ELn→PC, SPSR_ELn→PSTATE (하위 레벨로 복귀)
예외 레벨 전환: ARM64는 네 개의 예외 레벨(EL0~EL3)을 지원합니다. SVC/HVC/SMC 명령으로 상위 레벨에 진입하며, 하드웨어가 자동으로 ELR_ELn(복귀 주소)과 SPSR_ELn(PSTATE 백업)을 저장합니다. ERET로 하위 레벨로 복귀할 때 이 값들이 복원됩니다. 각 EL은 독립적인 SP를 갖고, 시스템 레지스터에 _ELn 접미사를 붙여 레벨별로 분리됩니다.

레지스터 셋

범용 레지스터

64-bit32-bitAAPCS64 용도
X0-X7W0-W7인자/반환값 (X0=1st 반환, X0-X7=인자)
X8W8간접 결과 레지스터 (XR)
X9-X15W9-W15Caller-saved (임시)
X16W16IP0 (Intra-Procedure scratch)
X17W17IP1 (Intra-Procedure scratch)
X18W18플랫폼 레지스터 (Linux: shadow call stack)
X19-X28W19-W28Callee-saved
X29W29프레임 포인터 (FP)
X30W30링크 레지스터 (LR)
SPWSP스택 포인터 (EL별 독립)
XZRWZR제로 레지스터 (읽기=0, 쓰기=버림)
X 레지스터 (64-bit) / W 레지스터 (32-bit) 관계 63 32 31 0 X0 (64-bit) 상위 32-bit [63:32] W0 (하위 32-bit) [31:0] W 레지스터 쓰기 시 제로 확장: MOV W0, #0x1234 → X0 = 0x0000_0000_0000_1234 (상위 32비트가 자동으로 0이 됨) X 레지스터 쓰기: MOV X0, #0xFFFF_FFFF_0000_1234 → X0 전체 64비트가 설정됨, W0 읽기 시 하위 0x0000_1234만 반환
W 레지스터 쓰기의 제로 확장: ARM64에서 W 레지스터에 값을 쓰면 상위 32비트가 자동으로 0으로 클리어됩니다. 이는 x86-64에서 32-bit 레지스터 쓰기(EAX 등)가 상위 32비트를 클리어하는 것과 동일한 설계입니다. 반면 16-bit/8-bit 하위 접근은 존재하지 않으며, 이를 통해 부분 레지스터 갱신 문제(partial register stall)가 원천적으로 방지됩니다.

PSTATE 조건 플래그

플래그이름설명
NNegative결과의 최상위 비트 (부호)
ZZero결과가 0이면 세트
CCarry부호 없는 올림/빌림
VOverflow부호 있는 오버플로

시스템 레지스터 (주요)

레지스터설명
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/FPV0-V31128-bitBn(8), Hn(16), Sn(32), Dn(64), Qn(128)
SVEZ0-Z31128~2048-bit (가변)구현에 따라 128-bit 단위로 확장
SVE 프레디킷P0-P15가변벡터 요소별 마스크
SVE FFRFFR가변First Fault Register
FP 제어FPCR/FPSR32-bit라운딩 모드, 예외 상태
NEON 레지스터 뷰 및 벡터 레인 구조 스칼라 접근 뷰 (V0 레지스터) 127 0 Q0 128-bit D0 64-bit [63:0] S0 32-bit [31:0] H0 16-bit B0 8b 벡터 레인 뷰 (128-bit 벡터) V0.2D D[1] (64-bit) D[0] (64-bit) V0.4S S[3] S[2] S[1] S[0] V0.8H H[7] H[6] H[5] H[4] H[3] H[2] H[1] H[0] V0.16B B15 B14 B13 B12 B11 B10 B9 B8 B7 B6 B5 B4 B3 B2 B1 B0 V0.2D = 2x64-bit double V0.4S = 4x32-bit single V0.8H = 8x16-bit half V0.16B = 16x8-bit byte 접미사 의미: B=8-bit, H=16-bit, S=32-bit, D=64-bit, Q=128-bit (Q/D/S/H/B는 스칼라 접근 시 레지스터 이름에 사용)
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 += immldr 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 복사에도 유용합니다.

데이터 전송 명령어

명령어문법설명
MOVmov x0, x1레지스터 → 레지스터 이동
MOVZmovz x0, #0x1234, lsl #16즉시값 이동 + 나머지 제로화
MOVNmovn x0, #0즉시값 반전 이동
MOVKmovk x0, #0x567816-bit 즉시값 삽입 (나머지 보존)
LDRldr x0, [x1, #8]메모리 → 레지스터 (64-bit)
LDRB/LDRHldrb w0, [x1]바이트/하프워드 제로 확장 로드
LDRSB/LDRSH/LDRSWldrsw x0, [x1]부호 확장 로드
STRstr x0, [x1, #8]레지스터 → 메모리 (64-bit)
LDPldp x0, x1, [sp]쌍 레지스터 로드 (128-bit)
STPstp x29, x30, [sp, #-16]!쌍 레지스터 저장
ADRPadrp x0, symbolPC 상대 4KB 페이지 주소
ADRadr x0, labelPC 상대 주소 (±1MB)
LDARldar x0, [x1]Acquire 시맨틱 로드
STLRstlr x0, [x1]Release 시맨틱 저장
PRFMprfm 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()는 배리어를 포함하지 않으며, 컴파일러 재배치만 방지합니다.

산술 명령어

명령어문법설명플래그
ADDadd x0, x1, x2덧셈
ADDSadds x0, x1, x2덧셈 + 플래그 세트N, Z, C, V
SUBsub x0, x1, #16뺄셈
SUBSsubs x0, x1, x2뺄셈 + 플래그 세트N, Z, C, V
ADCadc x0, x1, x2캐리 포함 덧셈
SBCsbc x0, x1, x2캐리 포함 뺄셈
NEGneg x0, x1부정 (0 - src)
MULmul x0, x1, x2곱셈 (하위 64-bit)
MADDmadd x0, x1, x2, x3곱셈-덧셈: x1*x2 + x3
MSUBmsub x0, x1, x2, x3곱셈-뺄셈: x3 - x1*x2
SMULLsmull x0, w1, w2부호 있는 32→64 곱셈
UMULLumull x0, w1, w2부호 없는 32→64 곱셈
SMULHsmulh x0, x1, x264×64→128 상위 64-bit (부호)
UMULHumulh x0, x1, x264×64→128 상위 64-bit (부호 없음)
SDIVsdiv x0, x1, x2부호 있는 나눗셈
UDIVudiv x0, x1, x2부호 없는 나눗셈

논리/시프트/비트 조작 명령어

명령어문법설명
ANDand x0, x1, x2비트 AND
ANDSands x0, x1, x2비트 AND + 플래그 세트
ORRorr x0, x1, x2비트 OR
ORNorn x0, x1, x2비트 OR NOT
EOReor x0, x1, x2비트 XOR
EONeon x0, x1, x2비트 XOR NOT
BICbic x0, x1, x2비트 클리어: x1 AND NOT x2
TSTtst x0, #0xFFAND 테스트 (결과 저장 안 함)
MVNmvn x0, x1비트 반전 (NOT)
LSLlsl x0, x1, #4논리 좌측 시프트
LSRlsr x0, x1, #4논리 우측 시프트
ASRasr x0, x1, #4산술 우측 시프트
RORror x0, x1, #4순환 우측 시프트
CLZclz x0, x1선행 제로 카운트
CLScls x0, x1선행 부호 비트 카운트
RBITrbit x0, x1비트 순서 반전
REVrev x0, x1바이트 순서 반전 (64-bit)
REV16rev16 x0, x1하프워드 내 바이트 반전
REV32rev32 x0, x1워드 내 바이트 반전
EXTRextr x0, x1, x2, #n두 레지스터에서 비트 추출
BFIbfi x0, x1, #lsb, #width비트 필드 삽입
BFXILbfxil x0, x1, #lsb, #width비트 필드 추출 + 삽입
UBFIZ/SBFIZubfiz x0, x1, #lsb, #w부호 없는/있는 비트 필드 제로/부호 확장 삽입

비교/분기 명령어

명령어문법설명
CMPcmp x0, x1SUBS + 결과 버림 (플래그만 세트)
CMNcmn x0, x1ADDS + 결과 버림
TSTtst x0, x1ANDS + 결과 버림
CCMPccmp x0, x1, #nzcv, eq조건부 비교 (조건 미충족 시 nzcv 세트)
CSELcsel x0, x1, x2, eq조건부 선택: eq이면 x1, 아니면 x2
CSINCcsinc x0, x1, x2, ne조건부 선택/증가
CSETcset x0, eq조건 충족 시 1, 아니면 0
Bb label무조건 분기 (±128MB)
B.condb.eq label조건부 분기 (±1MB)
BLbl func함수 호출 (X30 ← PC+4)
BRbr x0간접 분기
BLRblr x0간접 호출 (X30 ← PC+4)
RETret복귀 (BR X30)
CBZcbz x0, label제로이면 분기
CBNZcbnz x0, label비제로이면 분기
TBZtbz x0, #5, label특정 비트가 0이면 분기
TBNZtbnz x0, #5, label특정 비트가 1이면 분기
조건 코드: EQ(같음), NE(다름), GT(부호있는 >), GE(부호있는 >=), LT(부호있는 <), LE(부호있는 <=), HI(부호없는 >), HS(부호없는 >=), LO(부호없는 <), LS(부호없는 <=), MI(음수), PL(양수/0), VS(오버플로), VC(무오버플로).
CCMP (Conditional Compare) — 다중 조건 분기 최적화: CCMP는 이전 조건이 참일 때만 비교를 수행하고, 거짓이면 즉시값으로 NZCV 플래그를 설정합니다. 이를 통해 if (a == 1 && b == 2) 같은 다중 조건을 분기 없이 단일 조건 체인으로 평가할 수 있습니다.
  • x86에서는 CMP + JNE + CMP + JNE로 2개의 분기가 필요하지만, ARM64에서는 CMP + CCMP + B.cond1개의 분기만 사용합니다.
  • 분기 예측 실패 패널티를 줄이고, 파이프라인 효율을 높이는 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                /* 둘 중 하나라도 참이면 분기 */

스택/함수 호출 명령어

ARM64에는 PUSH/POP 명령어가 없습니다. STP/LDP로 레지스터 쌍을 저장/복원합니다.

/* 함수 프롤로그/에필로그 패턴 */
my_func:
    stp     x29, x30, [sp, #-16]!    /* FP, LR 저장 (pre-index) */
    mov     x29, sp                    /* 프레임 포인터 설정 */
    /* ... 함수 본문 ... */
    ldp     x29, x30, [sp], #16       /* FP, LR 복원 (post-index) */
    ret                                /* X30(LR)으로 복귀 */
AAPCS64 호출 규약:
  • 인자: X0-X7 (최대 8개 정수/포인터)
  • 반환값: X0 (+ X1 128-bit)
  • Callee-saved: X19-X28, FP(X29), LR(X30)
  • Caller-saved: X0-X18 (X16/X17은 링커 사용)
  • SP는 항상 16-byte 정렬

시스템/특권 명령어

명령어설명예외 레벨
SVC #imm시스템 콜 (EL0 → EL1)EL0
HVC #imm하이퍼바이저 콜 (EL1 → EL2)EL1
SMC #imm시큐어 모니터 콜 (→ EL3)EL1
ERET예외 복귀 (ELR/SPSR 복원)EL1+
MSR시스템 레지스터 쓰기레지스터별
MRS시스템 레지스터 읽기레지스터별
NOPNo operation모든 레벨
WFI인터럽트 대기 (저전력)EL0+ (트랩 가능)
WFE이벤트 대기EL0+
SEV/SEVL이벤트 전송 (로컬)EL0+
ISB명령어 동기화 배리어모든 레벨
DSB데이터 동기화 배리어모든 레벨
DMB데이터 메모리 배리어모든 레벨
TLBITLB 무효화EL1+
IC명령어 캐시 유지레벨별
DC데이터 캐시 유지레벨별
AT주소 변환EL1+
CLREX독점 모니터 초기화모든 레벨
YIELD양보 힌트 (SMT)모든 레벨
BRK #imm디버그 브레이크포인트모든 레벨
예외 벡터 테이블 레이아웃 (VBAR_ELn 기준) 4 그룹 x 4 엔트리 = 16 벡터, 각 128 바이트 (0x80), 전체 2048 바이트 (0x800) 오프셋 Synchronous IRQ FIQ SError Current EL with SP_EL0 (SP0) +0x000 Sync (0x80) IRQ (0x80) FIQ (0x80) SError (0x80) Current EL with SP_ELx (SPx) — 커널 예외 처리 +0x200 el1h_sync el1h_irq el1h_fiq el1h_error Lower EL using AArch64 — 유저 → 커널 (시스템 콜) +0x400 el0_sync (SVC) el0_irq el0_fiq el0_error Lower EL using AArch32 — 32-bit 호환 모드 +0x600 el0_32_sync el0_32_irq el0_32_fiq el0_32_error VBAR_EL1 + 0x000: SP0 그룹 | +0x200: SPx 그룹 | +0x400: AArch64 하위 EL | +0x600: AArch32 하위 EL 각 엔트리 = 128바이트 (최대 32개 명령어). ventry 매크로가 정렬 + 분기를 생성합니다. 메모리 배리어 비교: DMB vs DSB vs ISB DMB (Data Memory Barrier) - 메모리 접근 순서만 보장 - 명령어 실행은 계속 진행 - smp_mb() / smp_rmb() 용도: 일반 메모리 순서 보장 DSB (Data Sync Barrier) - 메모리 접근 완료까지 대기 - 후속 명령어 실행 차단 - DMB보다 강력, 비용 높음 용도: TLBI/IC 후, 장치 설정 ISB (Instruction Sync Barrier) - 파이프라인 완전 플러시 - 시스템 레지스터 변경 반영 - 가장 비용이 높은 배리어 용도: MMU/캐시 설정 후 강도 증가: DMB → DSB → ISB (비용도 함께 증가) ISH=Inner Shareable(SMP), OSH=Outer Shareable(클러스터간), SY=System(전체)
배리어 실전 사용 시나리오:
  • DMB ISH — 스핀락 획득/해제, smp_mb() 구현. SMP 시스템에서 코어 간 메모리 순서 보장에 가장 많이 사용됩니다.
  • DSB ISH + ISB — SCTLR_EL1 변경(MMU on/off), 페이지 테이블 변경 후 TLBI + DSB ISH + ISB 시퀀스. 시스템 레지스터 변경이 후속 명령어에 반영되어야 할 때 필수입니다.
  • DSB NSH — Non-Shareable 영역에 한정된 배리어. 단일 코어 장치 레지스터 접근 시 사용합니다.
  • DMB ISHLD / DMB ISHST — 각각 로드-로드, 스토어-스토어 순서만 보장하는 경량 배리어로, smp_rmb()/smp_wmb()에 매핑됩니다.

원자적/동기화 명령어

독점 접근 (ARMv8.0)

명령어문법설명
LDXRldxr x0, [x1]독점 로드
STXRstxr w2, x0, [x1]독점 저장 (w2=성공 여부: 0=성공)
LDAXRldaxr x0, [x1]독점 로드 + Acquire
STLXRstlxr w2, x0, [x1]독점 저장 + Release

LSE 원자적 명령어 (ARMv8.1)

명령어설명
CAS/CASA/CASL/CASAL비교-교환 (Compare And Swap)
CASP쌍 레지스터 비교-교환
SWP/SWPA/SWPL/SWPAL원자적 교환
LDADD/LDCLR/LDEOR/LDSET원자적 RMW (Add/Clear/XOR/Set)
LDSMAX/LDSMIN/LDUMAX/LDUMIN원자적 RMW (Max/Min)

메모리 배리어

명령어설명
DMB ISHInner Shareable 전체 배리어
DMB ISHLDInner Shareable 로드 배리어
DMB ISHSTInner Shareable 스토어 배리어
DSB ISH데이터 동기화 (배리어 완료 보장)
ISB명령어 파이프라인 플러시

LDXR/STXR CAS 루프 패턴

/* compare_and_swap(addr, expected, desired) */
cas_loop:
    ldaxr   x3, [x0]            /* 독점 Acquire 로드 */
    cmp     x3, x1               /* expected와 비교 */
    b.ne    1f                   /* 다르면 실패 */
    stlxr   w4, x2, [x0]         /* 독점 Release 저장 */
    cbnz    w4, cas_loop          /* 저장 실패 시 재시도 */
1:
    mov     x0, x3               /* 이전 값 반환 */
    ret
LDXR/STXR (ARMv8.0) vs LSE CAS (ARMv8.1) 성능 비교:
  • LDXR/STXR (독점 접근 루프):
    • 모든 ARMv8.0+ 프로세서에서 지원 (범용성 최고)
    • 높은 경합(contention) 상황에서 STXR 실패 → 재시도 루프가 반복되어 성능 급격히 저하
    • 캐시 라인 바운스(bouncing)로 인한 코히런시 트래픽 증가
    • Linux 커널: CONFIG_ARM64_LSE_ATOMICS가 비활성이면 이 방식 사용
  • LSE CAS/SWP/LDADD (ARMv8.1+):
    • 단일 명령어로 원자적 RMW 수행 — 재시도 루프 불필요
    • 마이크로아키텍처 레벨에서 캐시 컨트롤러가 직접 처리하여 경합 시에도 2~10배 성능 향상
    • CAS: casa x0, x1, [x2] (Acquire), CASAL: Acquire+Release 양쪽
    • Linux 커널: 부팅 시 LSE 지원 여부를 감지하여 대체 패치(alternative patching)로 런타임 전환

스핀락 구현 예제 (LDAXR/STLXR)

/* arch_spin_lock — 간소화된 스핀락 구현 */
/* x0 = &lock (0=unlocked, 1=locked) */
arch_spin_lock:
    mov     w2, #1               /* 락 값 준비 */
    sevl                             /* 이벤트 레지스터 세트 (첫 WFE 통과) */
1:  wfe                              /* 이벤트 대기 (전력 절약) */
2:  ldaxr   w1, [x0]             /* Acquire 독점 로드 */
    cbnz    w1, 1b               /* 이미 잠김 → WFE로 대기 */
    stxr    w1, w2, [x0]          /* 독점 저장 시도 */
    cbnz    w1, 2b               /* 저장 실패 → 재시도 (WFE 건너뜀) */
    ret                              /* 락 획득 성공 */

arch_spin_unlock:
    stlr    wzr, [x0]             /* Release 저장: 0(unlock) 기록 */
    ret                              /* SEV는 STLR의 exclusive monitor clear로 자동 전파 */
독점 모니터 (Exclusive Monitor) 메커니즘:
  • LDXR은 해당 주소를 "독점 상태"로 마킹합니다. 이후 다른 코어가 같은 캐시 라인에 쓰면 독점 상태가 해제됩니다.
  • STXR은 독점 상태가 유지된 경우에만 저장에 성공(W 결과=0)합니다. 해제되었으면 실패(W 결과=1)합니다.
  • 로컬 모니터(코어별)와 글로벌 모니터(시스템 레벨) 두 단계가 존재합니다.
  • 주의: 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 복귀 시 커널 매핑이 제거된 별도 페이지 테이블로 전환됩니다.
  • 예외 진입 시(EL0→EL1): trampoline 코드가 TTBR0을 전체 커널 매핑 테이블로 복원합니다.
  • 예외 복귀 시(EL1→EL0): TTBR0을 최소 매핑 테이블로 전환한 뒤 ERET합니다.
/* KPTI: 유저 복귀 시 TTBR0 전환 (간소화) */
/* arch/arm64/kernel/entry.S 기반 */
    mrs     x1, ttbr1_el1           /* 커널 TTBR1 읽기 */
    bfi     x1, x0, #48, #16     /* ASID 삽입 */
    msr     ttbr0_el1, x1           /* TTBR0 = 유저 페이지 테이블 */
    isb                              /* 파이프라인 동기화 */
    eret                             /* EL0으로 복귀 */

예외 진입 시 레지스터 저장 (kernel_entry 매크로)

/* arch/arm64/kernel/entry.S — kernel_entry 매크로 (간소화) */
/* EL0 → EL1 예외 진입 시 전체 레지스터 컨텍스트 저장 */
    sub     sp, sp, #288           /* pt_regs 구조체 크기만큼 스택 할당 */
    stp     x0, x1, [sp, #16 * 0]  /* X0-X1 저장 */
    stp     x2, x3, [sp, #16 * 1]  /* X2-X3 저장 */
    /* ... X4-X27 저장 (STP 쌍으로) ... */
    stp     x28, x29, [sp, #16 * 14] /* X28-X29 저장 */

    mrs     x22, elr_el1            /* 예외 복귀 주소 */
    mrs     x23, spsr_el1           /* 저장된 프로세서 상태 */
    mrs     x20, sp_el0             /* 유저 SP (EL0 사용 SP) */

    stp     x30, x20, [sp, #16 * 15] /* LR, SP_EL0 저장 */
    stp     x22, x23, [sp, #16 * 16] /* ELR_EL1, SPSR_EL1 저장 */

    /* 이제 C 함수(el0_sync_handler 등)를 호출할 수 있음 */
    mov     x0, sp                   /* pt_regs 포인터를 인자로 전달 */
    bl      el0_svc_handler          /* 시스템 콜 핸들러 호출 */

Linux 커널 GCC 인라인 어셈블리 예제 (ARM64)

/* arch/arm64/include/asm/sysreg.h 기반 */
#define read_sysreg(r) ({                               \
    u64 __val;                                         \
    asm volatile("mrs %0, " __stringify(r) : "=r"(__val)); \
    __val;                                              \
})

#define write_sysreg(v, r) do {                          \
    u64 __val = (u64)(v);                               \
    asm volatile("msr " __stringify(r) ", %x0"           \
                 : : "rZ"(__val));                       \
} while (0)

/* 사용 예: 인터럽트 비활성화 */
static inline void arch_local_irq_disable(void)
{
    asm volatile(
        "msr    daifset, #3"    /* IRQ + FIQ 마스크 */
        :
        :
        : "memory"
    );
}

/* atomic_add: 인라인 어셈블리로 원자적 덧셈 */
static inline void arch_atomic_add(int i, atomic_t *v)
{
    unsigned long tmp;
    int result;

    asm volatile(
        "1: ldxr    %w0, %2\n"       /* 독점 로드 */
        "   add     %w0, %w0, %w3\n" /* 덧셈 */
        "   stxr    %w1, %w0, %2\n"  /* 독점 저장 */
        "   cbnz    %w1, 1b"         /* 실패 시 재시도 */
        : "=&r"(result), "=&r"(tmp), "+Q"(v->counter)
        : "Ir"(i)
    );
}
인라인 어셈블리 제약 조건:
  • "=&r" — Early clobber 출력: 입력이 완전히 소비되기 전에 출력이 기록될 수 있음을 알림
  • "+Q" — 메모리 읽기+쓰기 오퍼랜드 (주소가 단일 레지스터로 표현 가능)
  • "Ir" — 즉시값 또는 레지스터 (ADD의 즉시값 범위 내)
  • "rZ" — 레지스터 또는 제로 레지스터 (XZR/WZR 활용)
  • "memory" clobber — 컴파일러가 배리어 앞뒤로 메모리 접근을 재배치하지 않도록 방지

명령어 인코딩

ARM64(A64) 모든 명령어는 고정 32-bit입니다. 상위 비트(op0 필드)로 명령어 그룹을 결정합니다.

A64 고정 32-bit 명령어 인코딩 31 28 25 24 0 op0 [28:25] 명령어 그룹별 인코딩 필드 op0 값 명령어 그룹 x00x Reserved / Unallocated 100x Data Processing — Immediate 101x Branches, Exception, System x1x0 Loads and Stores x101 Data Processing — Register x111 Data Processing — SIMD & FP

인코딩 예제: ADD X0, X1, X2

ADD X0, X1, X2는 "Data Processing — Register" 그룹(op0=x101)에 속합니다. 구체적으로 Add (shifted register) 형식으로 인코딩됩니다.

ADD X0, X1, X2 인코딩 (Data Processing — Shifted Register) 31 30 29 28:24 23:22 21 20:16 15:10 9:5 4:0 sf op S 01011 shift Rm imm6 Rn Rd 1 0 0 01011 00 00010 000000 00001 00000 각 필드 해석: sf = 1 64-bit 연산 (X 레지스터). 0이면 32-bit (W 레지스터) op = 0 ADD 연산. 1이면 SUB S = 0 플래그 미갱신. 1이면 ADDS (NZCV 플래그 세트) 01011 고정 opcode (Data Processing — Register 그룹 식별) shift = 00 LSL (00=LSL, 01=LSR, 10=ASR). Rm에 적용할 시프트 타입 Rm = 00010 소스 레지스터 2 = X2 (레지스터 번호 2) imm6 = 000000 시프트 양 = 0 (시프트 없음). ADD X0, X1, X2, LSL #3이면 000011 Rn = 00001 소스 레지스터 1 = X1 (레지스터 번호 1) Rd = 00000 대상 레지스터 = X0 (레지스터 번호 0)
최종 바이너리 인코딩: ADD X0, X1, X2 = 1 0 0 01011 00 0 00010 000000 00001 000002 = 0x8B020020
  • 확인: echo '8B020020' | xxd -r -p | aarch64-linux-gnu-objdump -b binary -m aarch64 -D -
  • 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를 방어합니다.

ARM64 보안 확장 (ARMv8.3+) PAC (Pointer Authentication) ARMv8.3 — ROP/JOP 방어 PACIA X30, SP → LR 상위 비트에 PAC 삽입 AUTIA X30, SP → PAC 검증, 실패 시 fault 키: APIAKey, APIBKey, APDAKey, APDBKey, APGAKey (EL1 관리) 커널: CONFIG_ARM64_PTR_AUTH 함수 프롤로그/에필로그 자동 삽입 QARMA/PACGA: 64→VA비트 해시 BTI (Branch Target ID) ARMv8.5 — JOP/COP 방어 BTI C → BL(call)의 타겟에만 허용 BTI J → BR(jump)의 타겟에만 허용 BTI JC → BL 또는 BR 모두 허용 SCTLR_EL1.BT1 활성화 커널: CONFIG_ARM64_BTI_KERNEL x86 CET-IBT의 ARM64 대응 MTE (Memory Tagging Ext) ARMv8.5 — UAF/오버플로 탐지 IRG X0, X0 → 랜덤 4비트 태그 생성 STG X0, [X0] → 메모리에 태그 저장 (16B 단위) LDR X1, [X0] → 포인터 태그 ≠ 메모리 태그 시 fault 포인터 bit 56-59에 4비트 태그 커널: CONFIG_ARM64_MTE KASAN HW 모드로 커널 메모리 검사 커널 사용 현황 비교 기능 PAC BTI MTE 대상 위협 ROP (리턴 주소 변조) JOP (간접 분기 하이재킹) UAF, 힙 오버플로 메커니즘 포인터 암호 서명 분기 타겟 표시 명령어 메모리 색상 태깅 성능 비용 ~1% (서명/검증 명령어) ~0% (NOP 크기 명령어) ~3-5% (태그 메모리 접근) 커널 설정 ARM64_PTR_AUTH ARM64_BTI_KERNEL ARM64_MTE + KASAN_HW_TAGS GCC 옵션 -mbranch-protection=pac-ret -mbranch-protection=bti -fsanitize=memtag-stack x86 대응 CET-SS (Shadow Stack) CET-IBT (endbr64) 없음 (ASAN 소프트웨어) 최소 ARMv8 v8.3 (Apple M1+) v8.5 (A78+, X1+) v8.5 (Pixel 8+, A715+)
/* 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 관리가 필수입니다.

ARM64 캐시 & TLB 관리 명령어 데이터 캐시 (DC) 명령어 DC CIVAC, Xt Clean+Invalidate by VA (PoC) DC CVAC, Xt Clean by VA (PoC) — 쓰기 반영 DC CVAU, Xt Clean by VA (PoU) — I$ 일관성 DC IVAC, Xt Invalidate by VA (PoC) DC ZVA, Xt Zero by VA — 캐시 라인 제로 초기화 PoC: Point of Coherency (모든 관찰자) PoU: Point of Unification (I$/D$ 통합점) DMA: dma_map_single() → DC CIVAC clear_page(): DC ZVA 사용 (빠른 제로화) 명령어 캐시 (IC) & TLB (TLBI) IC IALLU 전체 I$ 무효화 (Inner Share) IC IVAU, Xt VA 기반 I$ 무효화 (PoU) TLBI 명령어 TLBI VMALLE1 EL1 전체 TLB 무효화 TLBI VAE1, Xt VA+ASID 기반 무효화 TLBI ASIDE1, Xt ASID 기반 전체 무효화 TLBI VALE1IS, Xt 마지막 레벨+Inner Share IS 접미사: Inner Shareable (모든 코어) 캐시 관리 시퀀스 예시 DMA 버퍼 전송 (CPU→디바이스) 1. DC CVAC — 더티 데이터를 메모리로 flush 2. DSB SY — flush 완료 보장 3. DMA 전송 시작 코드 패칭 (alternatives) 1. 새 명령어 쓰기 (데이터 접근) 2. DC CVAU — D$ clean (PoU까지) 3. DSB ISH — 완료 대기 4. IC IVAU — I$ 무효화 5. DSB ISH + ISB — 파이프라인 동기화 페이지 테이블 수정 1. PTE 수정 (WRITE_ONCE) 2. DSB ISHST — store 완료 보장 3. TLBI VAE1IS — TLB 무효화 4. DSB ISH — TLBI 완료 대기 5. ISB — 파이프라인 flush (선택)
명령어대상동작커널 사용처
DC CIVAC데이터 캐시Clean + Invalidate by VADMA, 비일관 I/O
DC CVAC데이터 캐시Clean by VA (PoC)DMA CPU→디바이스
DC CVAU데이터 캐시Clean by VA (PoU)코드 패칭 전
DC ZVA데이터 캐시Zero by VAclear_page() 최적화
IC IVAU명령어 캐시Invalidate by VA (PoU)코드 패칭 후
IC IALLU명령어 캐시Invalidate All (Inner Share)모듈 로딩
TLBI VMALLE1ISTLBEL1 전체 무효화 (IS)flush_tlb_all()
TLBI VAE1ISTLBVA+ASID 기반 무효화 (IS)flush_tlb_page()
TLBI ASIDE1ISTLBASID 전체 무효화 (IS)flush_tlb_mm()
⚠️

DSB/ISB 필수: ARM64에서 캐시/TLB 유지보수 명령어는 비동기적으로 실행될 수 있습니다. DSB(Data Synchronization Barrier)로 완료를 보장하고, 코드 패칭 시 ISB(Instruction Synchronization Barrier)로 파이프라인을 flush해야 합니다. x86의 invlpg는 동기적이므로 이 문제가 없지만, ARM64에서는 누락하면 stale 캐시/TLB로 인한 크래시가 발생합니다.

다음 학습: