MIPS 명령어셋 레퍼런스

MIPS 아키텍처의 명령어를 GAS 문법 기준으로 종합 정리합니다. 전통 RISC 설계 철학과 고정 32-bit 명령어, 32개 범용 레지스터(Register)와 HI/LO 특수 레지스터, 지연(Latency) 슬롯 메커니즘, MIPS32/64 Release 2와 Release 6의 차이, R/I/J 3가지 인코딩 타입, 데이터 전송·산술·논리·분기·스택·시스템·원자적(Atomic)·MSA SIMD 명령어 카테고리 표, 인코딩 다이어그램, 커널 핵심 명령어(SYSCALL, ERET, LL/SC, MFC0/MTC0, TLBWI) 고급까지 Linux 커널 개발에 필요한 MIPS ISA 전체를 다룹니다.

전제 조건: 어셈블리(Assembly) 종합을 먼저 읽으세요. MIPS 명령어 레퍼런스는 RISC Load/Store 모델, 지연 슬롯 개념, CP0 레지스터에 대한 기본 이해가 필요합니다.
일상 비유: MIPS는 교과서적 RISC 설계의 대표입니다. 컴퓨터 구조 교육의 표준으로 사용될 만큼 깔끔한 명령어 세트를 가지고 있으며, 파이프라인(Pipeline) 설계의 교본과 같습니다. 지연 슬롯은 파이프라인 효율을 위한 독특한 설계 결정입니다.

핵심 요약

  • 전통 RISC — 고정 32-bit 명령어, Load/Store 아키텍처, 균일한 레지스터 파일.
  • 32개 범용 레지스터 — $0=zero(항상 0), $31=ra, $29=sp, $4-$7=a0-a3(인자).
  • 지연 슬롯 — 분기/점프 다음 명령어가 항상 실행됨 (R6에서 compact branch로 개선).
  • HI/LO 레지스터 — 곱셈/나눗셈 결과 저장 (R6에서 제거, GPR 직접 사용).
  • R2 → R6 진화 — Release 6에서 지연 슬롯 없는 분기, HI/LO 제거, 새 인코딩 도입.

단계별 이해

  1. 레지스터 구조 파악
    $0-$31의 ABI 이름($zero, $at, $v0-$v1, $a0-$a3, $t0-$t9, $s0-$s7 등)을 먼저 익힙니다.
  2. 지연 슬롯 이해
    분기/점프 직후 명령어가 항상 실행되는 MIPS 고유 특성을 반드시 이해합니다.
  3. R/I/J 인코딩
    3가지 고정 인코딩 포맷의 필드 배치를 학습합니다.
  4. CP0와 특권 명령어
    MFC0/MTC0으로 접근하는 CP0 레지스터와 SYSCALL/ERET를 학습합니다.

아키텍처 개요

MIPS(Microprocessor without Interlocked Pipelined Stages)는 1981년 Stanford에서 John Hennessy가 설계한 RISC 아키텍처입니다. MIPS Computer Systems(1984) → SGI(1992) → Imagination Technologies(2012) → Wave Computing(2018) → MIPS(2021)를 거쳤습니다.

특성MIPS
설계 철학전통 RISC (교과서적 파이프라인 설계)
명령어 길이고정 32-bit
엔디언바이-엔디언 (Big 또는 Little)
범용 레지스터32개 ($0=hardwired zero)
특수 레지스터HI/LO (곱셈/나눗셈, R2), PC
지연 슬롯분기/점프 다음 1개 명령어 항상 실행 (R6: compact branch 도입)
주요 릴리스MIPS32/64 R2 (2003), R5 (2012), R6 (2014)
동작 모드커널 모드, 슈퍼바이저 모드, 유저 모드
주소 공간(Address Space)32-bit (MIPS32) 또는 64-bit (MIPS64)
R2 vs R6: Release 6(2014)에서 대폭 변경되었습니다. HI/LO 제거(곱셈/나눗셈 결과 GPR 직접), 지연 슬롯 없는 compact branch 도입, 일부 명령어 재인코딩. Linux 커널은 R2와 R6 모두 지원합니다.

MIPS 클래식 5단계 파이프라인

MIPS 클래식 5단계 파이프라인 (Classic 5-Stage Pipeline) IF Instruction Fetch ID Instruction Decode EX Execute MEM Memory Access WB Write Back 분기 명령어와 지연 슬롯 실행 타이밍 Cycle 1 Cycle 2 Cycle 3 Cycle 4 Cycle 5 BEQ (분기) IF ID+비교 EX 지연 슬롯 IF ID EX MEM 분기 타겟 IF ID EX BEQ가 ID에서 분기 결정 → 이미 다음 명령어(지연 슬롯)가 IF에 진입 설계 결정: 분기 결과를 ID 단계에서 결정해도 다음 명령어는 이미 IF에 진입했으므로 취소하는 대신 항상 실행(지연 슬롯). 이 설계는 파이프라인 버블을 제거하지만 프로그래머/컴파일러에게 지연 슬롯 관리 부담을 전가합니다.
파이프라인 해저드와 지연 슬롯: MIPS의 이름(Microprocessor without Interlocked Pipelined Stages)이 암시하듯, 초기 MIPS는 하드웨어 인터록 없이 파이프라인 해저드를 소프트웨어가 관리하도록 설계되었습니다. 분기 명령어가 ID 단계에서 결과를 결정하지만, 그 시점에 다음 명령어는 이미 IF 단계에 진입해 있습니다. 하드웨어로 이를 취소(flush)하는 대신, 항상 실행하도록 하여 1 사이클의 파이프라인 버블을 제거한 것이 지연 슬롯의 핵심 설계 결정입니다. 현대 프로세서에서는 이 결정이 오히려 복잡성을 증가시키기 때문에, R6에서 지연 슬롯 없는 compact branch가 도입되었습니다.

MIPS 가상 주소 공간 (32-bit)

MIPS32 가상 주소 공간 레이아웃 주소 범위 세그먼트 속성 0xE0000000 kseg3 0xFFFFFFFF 커널 매핑됨 (512MB) — TLB 사용 캐시 속성 TLB 엔트리에 의해 결정 0xC0000000 kseg2 / ksseg 0xDFFFFFFF 커널/슈퍼바이저 매핑됨 (512MB) — TLB 사용 vmalloc, ioremap 영역 0xA0000000 kseg1 0xBFFFFFFF 커널 비매핑 비캐시 (512MB) — TLB 미사용 MMIO, 부트 코드 (물리 주소 = VA - 0xA0000000) 0x80000000 kseg0 0x9FFFFFFF 커널 비매핑 캐시 (512MB) — TLB 미사용 커널 코드/데이터 (물리 주소 = VA - 0x80000000) 0x00000000 kuseg 0x7FFFFFFF 유저 매핑됨 (2GB) — TLB 사용 유저 프로세스 코드, 데이터, 스택, 힙 kseg0/kseg1은 물리 메모리 하위 512MB에 직접 매핑 (PA = VA & 0x1FFFFFFF). 부트 시 TLB 초기화 전에 사용 가능.

레지스터 셋

범용 레지스터 ($0-$31)

번호ABI 이름용도보존
$0$zero하드와이어 제로 (항상 0)
$1$at어셈블러 임시 (Assembler Temporary)
$2-$3$v0-$v1함수 반환값Caller
$4-$7$a0-$a3함수 인자 (o32 ABI)Caller
$8-$15$t0-$t7임시 레지스터Caller
$16-$23$s0-$s7Saved 레지스터Callee
$24-$25$t8-$t9임시 레지스터Caller
$26-$27$k0-$k1커널 전용 (예외 핸들러용)
$28$gp전역 포인터 (Global Pointer)Caller
$29$sp스택 포인터Callee
$30$fp ($s8)프레임 포인터 / Saved 레지스터Callee
$31$ra복귀 주소 (Return Address)Caller
$k0/$k1이 커널 전용인 이유: MIPS에서 예외가 발생하면 프로세서는 예외 벡터로 즉시 점프하지만, 레지스터를 자동으로 저장하지 않습니다. 예외 핸들러(Handler)는 레지스터를 메모리에 저장하기 위해 최소 2개의 "스크래치" 레지스터가 필요합니다. $k0/$k1은 이 목적으로 예약되어 있으며, 예외가 언제든 발생할 수 있으므로 이 레지스터의 값은 예고 없이 덮어쓰여질 수 있습니다. 따라서 유저 코드나 일반 커널 코드에서 $k0/$k1을 사용하면 안 됩니다. Linux 커널의 TLB refill 핸들러에서 $k0으로 Context 레지스터를, $k1으로 PTE 값을 임시 저장하는 패턴이 대표적입니다.
MIPS 범용 레지스터 용도별 그룹 고정/특수 $0 (zero) 항상 0 $1 (at) 어셈블러 임시 $28 (gp) 전역 포인터 $29 (sp) 스택 포인터 $31 (ra) 복귀 주소 Caller-saved (임시) $2-$3 (v0-v1) 반환값 $4-$7 (a0-a3) 인자 $8-$15 (t0-t7) 임시 $24-$25 (t8-t9) 임시 Callee-saved (보존) $16-$23 (s0-s7) $30 (fp/s8) 함수 호출 전후 값 유지 피호출자가 저장/복원 책임 커널 예약 $26 (k0) 예외 임시 $27 (k1) 예외 임시 예외 발생 시 언제든 덮어쓰여질 수 있음 HI / LO 특수 레지스터 (R2 전용, R6에서 제거) MULT/DIV 결과 저장. MFHI/MFLO로 읽기, MTHI/MTLO로 쓰기. R6에서는 MUL/DIV가 GPR 직접 사용. PC (프로그램 카운터) 직접 접근 불가. JAL이 PC+8을 $ra에 저장. 분기는 PC 상대 오프셋 사용. o32 ABI: 인자 4개($a0-$a3), 임시 10개($t0-$t9), 보존 8개($s0-$s7), 반환 2개($v0-$v1) n64 ABI: 인자 8개($a0-$a7=$t0-$t3 재사용), 임시 6개($t4-$t9 축소), 보존/반환 동일

특수 레지스터

레지스터설명
HI / LO곱셈 상위/하위, 나눗셈 나머지/몫 (R2, R6에서 제거)
PC프로그램 카운터 (직접 접근 불가)

CP0 (Coprocessor 0) 레지스터 — 주요

번호이름설명
$8BadVAddr가장 최근 주소 오류의 가상 주소(Virtual Address)
$9Count카운터 (타이머(Timer))
$11Compare카운터 비교값 (타이머 인터럽트(Interrupt))
$12Status프로세서 상태 (IE, EXL, ERL, KSU, IM 등)
$13Cause예외 원인 (ExcCode, IP 등)
$14EPC예외 PC (복귀 주소)
$15PRId프로세서 ID
$16Config구성 레지스터
$0IndexTLB 인덱스
$1RandomTLB 랜덤 인덱스
$2-$3EntryLo0/1TLB 엔트리 하위 (짝수/홀수 페이지(Page))
$4ContextTLB 미스 컨텍스트
$5PageMaskTLB 페이지 마스크
$6Wired고정 TLB 엔트리 수
$10EntryHiTLB 엔트리 상위 (VPN2, ASID)
$15.1EBase예외 베이스 주소 (R2+)
$30ErrorEPC에러 예외 PC

CP0 Status 레지스터 비트 필드 ($12)

CP0 Status 레지스터 ($12) 비트 레이아웃 비트 31 비트 16 CU3 CU2 CU1 CU0 RP FR RE MX PX BEV TS SR NMI IM[7:0] (인터럽트 마스크) 비트 7 비트 0 KX SX UX KSU[1:0] (모드) ERL EXL IE CU[3:0]: 코프로세서 사용 가능 (CU1=FPU) | BEV: 부트 예외 벡터 (1=ROM 벡터) | IM[7:0]: 개별 인터럽트 마스크 KSU: 00=커널, 01=슈퍼바이저, 10=유저 | ERL: 에러 레벨 (1=에러 예외 처리 중) | EXL: 예외 레벨 (1=예외 처리 중) IE: 전역 인터럽트 활성화 | FR: FPU 64-bit 레지스터 | KX/SX/UX: 64-bit 주소 모드 (MIPS64) 인터럽트 전달 조건: IE=1 AND EXL=0 AND ERL=0 AND 해당 IM 비트=1

CP0 Cause 레지스터 비트 필드 ($13)

CP0 Cause 레지스터 ($13) 비트 레이아웃 비트 31 비트 0 BD TI CE[1:0] DC PCI IV WP FDCI IP[7:0] (인터럽트 펜딩) 0 ExcCode[4:0] BD: 지연 슬롯에서 예외 (1이면 EPC-4가 분기 명령어) | TI: 타이머 인터럽트 | CE: 코프로세서 에러 번호 IV: 인터럽트 전용 벡터 사용 | IP[7:2]: 하드웨어 인터럽트 | IP[1:0]: 소프트웨어 인터럽트 (쓰기 가능) 주요 ExcCode 값: 0=Int(인터럽트) | 1=Mod(TLB수정) | 2=TLBL(TLB로드) | 3=TLBS(TLB저장) | 4=AdEL(주소에러로드) | 5=AdES(주소에러저장) 8=Sys(SYSCALL) | 9=Bp(BREAK) | 10=RI(예약 명령어) | 11=CpU(코프로세서 불가) | 13=Ov(오버플로)

MIPS64 n64 ABI 차이

n64 ABI: $a0-$a7 (8개 인자 레지스터), $v0-$v1 반환. o32 ABI는 $a0-$a3 (4개)만 사용하고 나머지는 스택으로 전달.

주소 지정 모드

MIPS는 매우 제한적인 주소 모드를 사용합니다: 베이스 + 16-bit 부호 확장 오프셋(Offset)만 지원합니다.

패턴문법설명예제
베이스+오프셋offset(base)[base + sign_ext(offset)]lw $t0, 8($sp)
32-bit 즉시값lui + ori상위 16 + 하위 16 조합lui $t0, %hi(sym); ori $t0, $t0, %lo(sym)
의사 명령어 lili $t0, imm어셈블러가 lui+ori로 확장li $t0, 0x12345678
의사 명령어 lala $t0, symbol주소 로드la $t0, my_var
지연 슬롯 (Branch Delay Slot): MIPS R2에서 모든 분기/점프 명령어 다음 1개 명령어는 분기 결과와 무관하게 항상 실행됩니다. 이는 파이프라인 효율을 위한 설계입니다. NOP으로 채우거나 유용한 명령어를 배치합니다. R6의 compact branch(BC, BALC 등)는 지연 슬롯이 없습니다.
지연 슬롯 구체적 예제: 아래 코드에서 addiu는 분기 결과와 무관하게 항상 실행됩니다:
    beq     $t0, $t1, target       /* $t0 == $t1이면 target으로 분기 */
    addiu   $v0, $v0, 1            /* ← 지연 슬롯: 항상 실행됨! */
    /* 여기는 분기하지 않았을 때만 실행 */
    ...
target:
    /* $v0는 이미 +1된 상태 (지연 슬롯이 실행되었으므로) */
주의: 지연 슬롯에서 분기 조건의 소스 레지스터를 수정하면 안 됩니다. 또한 지연 슬롯에 또 다른 분기 명령어를 넣는 것은 미정의 동작입니다.
.set noreorder / .set reorder 지시자: 커널 어셈블리에서는 지연 슬롯을 직접 관리하기 위해 GAS 지시자를 사용합니다:
  • .set noreorder — 어셈블러가 명령어 재배치(Relocation)/NOP 삽입을 하지 않음. 프로그래머가 지연 슬롯을 직접 채워야 함. 커널 핵심 경로(예외 핸들러, TLB refill)에서 사용.
  • .set reorder — 어셈블러가 필요 시 NOP을 자동 삽입하고 명령어 순서를 최적화. 일반 C 호출 함수에서 사용.
    .set    noreorder
    beq     $t0, $zero, done
    addiu   $v0, $v0, 1            /* 유용한 명령어를 지연 슬롯에 배치 */
    .set    reorder
Linux 커널의 arch/mips/ 어셈블리 코드는 대부분 .set noreorder 블록 내에서 작성됩니다.

데이터 전송 명령어

명령어문법설명비고
LB / LBUlb $t0, 0($a0)바이트 로드 (부호/제로 확장)
LH / LHUlh $t0, 0($a0)하프워드 로드
LWlw $t0, 0($a0)워드 로드
LWUlwu $t0, 0($a0)워드 제로 확장 로드MIPS64
LDld $t0, 0($a0)더블워드 로드MIPS64
SB / SH / SW / SDsw $t0, 0($a0)바이트/하프/워드/더블 저장
LUIlui $t0, 0x1234상위 16-bit 즉시값 로드
LWL / LWRlwl $t0, 3($a0)비정렬 워드 로드 (좌/우)R2 전용
MFC0 / MTC0mfc0 $t0, $12CP0 레지스터 읽기/쓰기특권
MFHI / MFLOmfhi $t0HI/LO 레지스터 읽기R2
MTHI / MTLOmthi $t0HI/LO 레지스터 쓰기R2
MFC1 / MTC1mfc1 $t0, $f0FPU 레지스터 ↔ GPR
LWC1 / LDC1lwc1 $f0, 0($a0)FPU 메모리 로드
SWC1 / SDC1swc1 $f0, 0($a0)FPU 메모리 저장
비정렬 메모리 접근 (Unaligned Access): MIPS R2에서 LW/SW는 4바이트 정렬을 요구합니다. 비정렬 주소에서 워드를 읽으려면 LWL(Load Word Left)과 LWR(Load Word Right)을 쌍으로 사용해야 합니다. R6에서는 이 문제가 해결되어 LW/SW가 비정렬 접근을 하드웨어적으로 지원하며, LWL/LWR은 제거되었습니다.
/* R2: 비정렬 32-bit 로드 (LWL/LWR 패턴) */
/* $a0에 비정렬 주소가 있다고 가정 (예: 0x10003) */
/* Big-Endian 기준 */
    lwl     $t0, 0($a0)            /* 주소의 워드 좌측 바이트들 로드 */
    lwr     $t0, 3($a0)            /* 주소의 워드 우측 바이트들 로드 */
    /* 이제 $t0에 4바이트가 올바르게 로드됨 */

/* R2: 비정렬 32-bit 저장 (SWL/SWR 패턴) */
    swl     $t0, 0($a0)            /* 워드 좌측 바이트들 저장 */
    swr     $t0, 3($a0)            /* 워드 우측 바이트들 저장 */

/* R6: 단순화됨 — LW/SW가 비정렬 지원 */
    lw      $t0, 0($a0)            /* R6에서는 비정렬이어도 정상 동작 */
커널에서의 비정렬 접근: Linux 커널의 arch/mips/kernel/unaligned.c는 비정렬 접근 예외(AdEL/AdES)를 에뮬레이션합니다. R2 커널 코드에서 비정렬 접근이 필요한 경우 get_unaligned() / put_unaligned() 매크로(Macro)를 사용하며, 이 매크로는 내부적으로 LWL/LWR 패턴을 사용합니다.

산술 명령어

명령어문법설명비고
ADD / ADDUaddu $t0, $t1, $t2덧셈 (ADD=오버플로 트랩, ADDU=무시)
ADDI / ADDIUaddiu $t0, $t1, 42즉시값 덧셈
SUB / SUBUsubu $t0, $t1, $t2뺄셈
MULT / MULTUmult $t0, $t1곱셈 → HI:LOR2
DIV / DIVUdiv $t0, $t1나눗셈 → LO=몫, HI=나머지R2
MUL (R6)mul $t0, $t1, $t2곱셈 → GPR (하위)R6
MUH / MULU / MUHU (R6)muh $t0, $t1, $t2곱셈 상위 / 부호없는R6
DIV / MOD (R6)div $t0, $t1, $t2나눗셈 / 나머지 → GPRR6
MADD / MADDUmadd $t0, $t1곱셈-누적: HI:LO += t0*t1R2
CLO / CLZclz $t0, $t1선행 1/0 카운트R2+
DADD / DADDU / DSUBdaddu $t0, $t1, $t264-bit 덧셈/뺄셈MIPS64
DMULT / DDIVdmult $t0, $t164-bit 곱셈/나눗셈MIPS64
R2 vs R6 곱셈/나눗셈 비교:
  • R2 방식: MULT rs, rt → 결과가 HI:LO에 저장됨 → MFHI/MFLO로 읽어야 함. 3개 명령어 필요.
  • R6 방식: MUL rd, rs, rt (하위 32-bit) / MUH rd, rs, rt (상위 32-bit) → GPR에 직접 저장. 1개 명령어로 완료.
  • R6에서 HI/LO 레지스터와 MFHI/MFLO/MTHI/MTLO/MADD/MSUB 모두 제거되었습니다.
/* R2: 곱셈 패턴 — result = $a0 * $a1 */
    mult    $a0, $a1               /* HI:LO = $a0 * $a1 */
    mflo    $v0                    /* $v0 = 결과 하위 32-bit (곱의 하위) */
    mfhi    $v1                    /* $v1 = 결과 상위 32-bit */

/* R2: 나눗셈 패턴 — quotient = $a0 / $a1, remainder = $a0 % $a1 */
    div     $zero, $a0, $a1        /* LO=몫, HI=나머지 */
    mflo    $v0                    /* $v0 = 몫 */
    mfhi    $v1                    /* $v1 = 나머지 */

/* R6: 곱셈 — 결과가 GPR에 직접 */
    mul     $v0, $a0, $a1          /* $v0 = ($a0 * $a1) 하위 32-bit */
    muh     $v1, $a0, $a1          /* $v1 = ($a0 * $a1) 상위 32-bit */

/* R6: 나눗셈 — 몫과 나머지 각각 별도 명령어 */
    div     $v0, $a0, $a1          /* $v0 = $a0 / $a1 (몫) */
    mod     $v1, $a0, $a1          /* $v1 = $a0 % $a1 (나머지) */

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

명령어문법설명비고
AND / ANDIand $t0, $t1, $t2비트 AND
OR / ORIor $t0, $t1, $t2비트 OR
XOR / XORIxor $t0, $t1, $t2비트 XOR
NORnor $t0, $t1, $t2비트 NOR (NOT = nor $t0,$t1,$zero)
SLLsll $t0, $t1, 4논리 좌측 시프트 (즉시값)
SRLsrl $t0, $t1, 4논리 우측 시프트
SRAsra $t0, $t1, 4산술 우측 시프트
SLLV / SRLV / SRAVsllv $t0, $t1, $t2변수 시프트 (레지스터 양)
INSins $t0, $t1, pos, size비트 필드 삽입R2
EXText $t0, $t1, pos, size비트 필드 추출R2
WSBHwsbh $t0, $t1하프워드 내 바이트 스왑(Swap)R2
SEB / SEHseb $t0, $t1바이트/하프워드 부호 확장R2
ROTR / ROTRVrotr $t0, $t1, 8우측 순환 시프트R2
BITSWAP (R6)bitswap $t0, $t1바이트 내 비트 반전R6
ALIGN (R6)align $t0, $t1, $t2, 2바이트 정렬 추출R6
DSLL / DSRL / DSRAdsll $t0, $t1, 464-bit 시프트MIPS64
DSLL32 / DSRL32 / DSRA32dsll32 $t0, $t1, 464-bit 시프트 (32+ 위치)MIPS64

비교/분기 명령어

명령어문법설명지연 슬롯
SLT / SLTIslt $t0, $t1, $t2부호 있는 < 비교 (1/0)
SLTU / SLTIUsltu $t0, $t1, $t2부호 없는 < 비교
BEQbeq $t0, $t1, label같으면 분기있음 (R2)
BNEbne $t0, $t1, label다르면 분기있음 (R2)
BGTZ / BLEZbgtz $t0, label>0 / <=0 분기있음 (R2)
BLTZ / BGEZbltz $t0, label<0 / >=0 분기있음 (R2)
BLTZAL / BGEZALbltzal $t0, label조건부 분기 + 링크있음 (R2)
Jj label무조건 점프 (26-bit 타겟)있음 (R2)
JALjal func함수 호출 ($ra ← PC+8)있음 (R2)
JRjr $ra간접 점프 (레지스터)있음 (R2)
JALRjalr $ra, $t0간접 호출있음 (R2)
BC (R6)bc label무조건 분기 (compact)없음
BALC (R6)balc label무조건 호출 (compact)없음
BEQZC / BNEZC (R6)beqzc $t0, label제로/비제로 compact 분기없음
BEQC / BNEC (R6)beqc $t0, $t1, label비교 compact 분기없음
MOVZ / MOVNmovz $t0, $t1, $t2조건부 이동 (R2)
SELEQZ / SELNEZ (R6)seleqz $t0, $t1, $t2조건부 선택 (R6 대체)
Branch Likely 명령어 (R2, R6에서 제거): BEQL, BNEL, BGTZL, BLEZL 등의 "branch likely" 변형은 분기가 taken일 때만 지연 슬롯을 실행합니다 (not-taken이면 지연 슬롯 명령어를 무효화(Invalidation)). 이는 컴파일러가 taken 경로에 유용한 명령어를 배치하기 쉽게 해주었지만, 하드웨어 구현이 복잡해져 R6에서 완전히 제거되었습니다. Linux 커널에서는 R2 TLB 핸들러 등 성능 크리티컬한 코드에서 여전히 사용됩니다.
지연 슬롯 최적화 예제: NOP 대신 유용한 명령어를 지연 슬롯에 배치하여 성능을 향상시킵니다:
/* 비최적화: NOP 낭비 */
    addiu   $v0, $v0, 1
    bne     $v0, $a0, loop
    nop                             /* 낭비되는 사이클 */

/* 최적화: 유용한 명령어를 지연 슬롯에 이동 */
    bne     $v0, $a0, loop
    addiu   $v0, $v0, 1            /* 지연 슬롯에서 실행 — 1 사이클 절약 */

/* 함수 호출 최적화: 인자 설정을 지연 슬롯에 */
    jal     some_func
    move    $a0, $s0               /* 지연 슬롯: 인자를 $a0에 설정 */

스택/함수 호출 명령어

MIPS에는 PUSH/POP 명령어가 없습니다. addiu $spsw/lw로 수동 관리합니다.

/* 함수 프롤로그/에필로그 (o32 ABI) */
my_func:
    addiu   $sp, $sp, -32         /* 스택 프레임 할당 */
    sw      $ra, 28($sp)           /* 복귀 주소 저장 */
    sw      $fp, 24($sp)           /* 프레임 포인터 저장 */
    move    $fp, $sp               /* 프레임 포인터 설정 */
    /* ... 함수 본문 ... */
    move    $sp, $fp               /* 스택 복원 */
    lw      $fp, 24($sp)           /* 프레임 포인터 복원 */
    lw      $ra, 28($sp)           /* 복귀 주소 복원 */
    addiu   $sp, $sp, 32           /* 스택 프레임 해제 */
    jr      $ra                    /* 복귀 (지연 슬롯) */
    nop                             /* 지연 슬롯 */
o32 ABI 호출 규약(Calling Convention):
  • 인자: $a0-$a3 (4개, 나머지 스택)
  • 반환값: $v0 (+ $v1)
  • Callee-saved: $s0-$s7, $fp, $sp
  • Caller-saved: $t0-$t9, $a0-$a3, $v0-$v1, $ra
  • n64 ABI: $a0-$a7 (8개 인자 레지스터)

시스템/특권 명령어

명령어설명비고
SYSCALL시스템 콜(System Call) 트랩
BREAK디버그 브레이크포인트 트랩
ERET예외 복귀 (EPC → PC, Status 복원)
MFC0 / MTC0CP0 레지스터 읽기/쓰기
DMFC0 / DMTC064-bit CP0 접근MIPS64
WAIT저전력 대기 (인터럽트까지)
EI / DI인터럽트 활성화/비활성화R2+
TLBRTLB 읽기 (Index → EntryHi/Lo)
TLBWITLB 쓰기 (Index 지정)
TLBWRTLB 쓰기 (Random 인덱스)
TLBPTLB 탐색 (EntryHi → Index)
CACHE캐시(Cache) 조작 (I-cache/D-cache)
SYNC메모리 배리어(Memory Barrier)
RDHWR하드웨어 레지스터 읽기 (ULR 등)R2+
GINVI / GINVT (R6)전역 TLB 무효화R6

예외 벡터 레이아웃

MIPS 예외 벡터 주소 (BEV=0, kseg0 기준) BEV=0 0x80000000 TLB Refill (32-bit) — TLBL/TLBS, EXL=0 0x80000080 XTLB Refill (64-bit) — MIPS64 TLB 미스 0x80000100 Cache Error — 캐시 패리티/ECC 에러 0x80000180 General Exception — SYSCALL, 인터럽트, 기타 모든 예외 0x80000200 Interrupt (Cause.IV=1) — 전용 인터럽트 벡터 (선택적) BEV=1 0xBFC00000 Reset / NMI — kseg1 (비캐시). 부트 시 BEV=1, 커널이 BEV=0으로 전환
EI/DI 인터럽트 관리 (R2+): Release 2부터 도입된 EI(Enable Interrupts)과 DI(Disable Interrupts)는 CP0 Status의 IE 비트를 원자적으로 설정/해제합니다. 이전 R1에서는 MFC0/MTC0으로 Status 레지스터를 읽고-수정-쓰기 해야 했으며, 이 사이에 인터럽트가 발생할 수 있는 경쟁 조건(Race Condition)이 있었습니다.
/* R2+: 원자적 인터럽트 비활성화 */
    di      $t0                    /* $t0 = 이전 Status, IE←0 */
    ehb                            /* Execution Hazard Barrier */
    /* ... 크리티컬 섹션 ... */
    ei                             /* IE←1, 인터럽트 재활성화 */

/* R1 (구형): MFC0/MTC0 패턴 — 경쟁 조건 가능 */
    mfc0    $t0, $12               /* Status 읽기 */
    ori     $t1, $t0, 1            /* IE 비트 세트 */
    mtc0    $t1, $12               /* Status 쓰기 — 여기서 인터럽트 경쟁 가능 */

원자적/동기화 명령어

명령어문법설명
LLll $t0, 0($a0)Load Linked (32-bit 독점 로드)
SCsc $t1, 0($a0)Store Conditional ($t1=0이면 실패, 1이면 성공)
LLDlld $t0, 0($a0)64-bit Load Linked (MIPS64)
SCDscd $t1, 0($a0)64-bit Store Conditional (MIPS64)
SYNCsync 0메모리 배리어 (0=full barrier)

LL/SC CAS 루프 패턴

/* compare_and_swap(addr=$a0, expected=$a1, desired=$a2) → old in $v0 */
cas_loop:
    ll      $v0, 0($a0)           /* Load Linked */
    bne     $v0, $a1, 1f          /* expected와 다르면 실패 */
    nop                             /* 지연 슬롯 */
    move    $t0, $a2               /* desired 값 복사 */
    sc      $t0, 0($a0)           /* Store Conditional */
    beqz    $t0, cas_loop          /* SC 실패(0) 시 재시도 */
    nop                             /* 지연 슬롯 */
1:
    jr      $ra                    /* $v0에 이전 값 반환 */
    nop                             /* 지연 슬롯 */
LL/SC 전진 보장 제약 조건: LL과 SC 사이의 코드는 매우 엄격한 제약을 따라야 합니다:
  • LL과 SC 사이에 다른 메모리 접근(load/store) 금지 — SC 실패를 유발할 수 있음
  • LL과 SC 사이의 명령어 수를 최소화 (구현에 따라 제한 있음, 일반적으로 수십 개 이내)
  • LL과 SC 사이에 분기/점프 금지 (조건부 이동 MOVZ/MOVN은 허용)
  • LL과 SC는 동일한 주소를 대상으로 해야 함
  • SC 결과 레지스터를 반드시 확인하여 실패 시 재시도해야 함
  • 다른 코어의 SC가 동일 캐시 라인(Cache Line)에 쓰면 LL의 링크가 해제되어 SC 실패

LL/SC 스핀락(Spinlock) 구현

/* arch_spin_lock(lock=$a0) — 간소화된 MIPS 스핀락 */
arch_spin_lock:
    .set    noreorder
1:
    ll      $t0, 0($a0)           /* 락 값 Load Linked */
    bnez    $t0, 1b               /* 이미 잠겨있으면 재시도 */
    li      $t1, 1                 /* 지연 슬롯: 잠금 값 준비 */
    sc      $t1, 0($a0)           /* Store Conditional (잠금 시도) */
    beqz    $t1, 1b               /* SC 실패 시 재시도 */
    nop                             /* 지연 슬롯 */
    sync                            /* 메모리 배리어 (acquire) */
    jr      $ra
    nop
    .set    reorder

/* arch_spin_unlock(lock=$a0) */
arch_spin_unlock:
    .set    noreorder
    sync                            /* 메모리 배리어 (release) */
    sw      $zero, 0($a0)          /* 락 해제 (일반 store로 충분) */
    jr      $ra
    nop
    .set    reorder
SYNC stype 변형: SYNC 명령어는 stype 필드(비트 10:6)로 배리어 범위를 지정합니다:
stype이름설명
0x00SYNC완전 배리어 — 모든 이전 load/store 완료 후 이후 접근 시작
0x10SYNC_WMB쓰기 배리어 — 이전 store 완료 후 이후 store 시작 (Linux wmb())
0x13SYNC_MB읽기-쓰기 배리어 (Linux mb())
0x12SYNC_RMB읽기 배리어 — 이전 load 완료 후 이후 load 시작 (Linux rmb())
Linux 커널의 arch/mips/include/asm/barrier.h에서 mb(), wmb(), rmb() 매크로가 이 stype 값을 사용합니다.

MSA (MIPS SIMD Architecture) 명령어

MSA는 128-bit 벡터 레지스터 $w0-$w31을 사용하며, 바이트(.b)/하프워드(.h)/워드(.w)/더블워드(.d) 요소 타입을 지원합니다.

명령어설명
LD.B/H/W/D $w0, 0($a0)벡터 로드 (128-bit)
ST.B/H/W/D $w0, 0($a0)벡터 저장
ADDV.B/H/W/D $w0, $w1, $w2벡터 정수 덧셈
SUBV.B/H/W/D $w0, $w1, $w2벡터 정수 뺄셈
MULV.B/H/W/D $w0, $w1, $w2벡터 정수 곱셈
MADDV / MSUBV벡터 곱셈-누적 / 곱셈-뺄셈
AND.V / OR.V / XOR.V / NOR.V벡터 비트 논리 연산
SLL.B/H/W/D / SRL / SRA벡터 시프트
CEQ.B/H/W/D / CLT / CLE벡터 비교 (같음/작음/이하)
FADD.W/D / FSUB / FMUL / FDIV벡터 부동소수점 연산
FMADD.W/D벡터 FMA
SHF.B/H/W $w0, $w1, imm4-요소 셔플
VSHF.B/H/W/D $w0, $w1, $w2벡터 셔플
PCKEV / PCKOD짝수/홀수 요소 팩
ILVL / ILVR좌/우 인터리브
HADD_S/U / HSUB_S/U수평 덧셈/뺄셈

커널 핵심 명령어

예외 핸들러 디스패치 (Cause.ExcCode)

MIPS에서 General Exception 벡터(0x80000180)에 도달하면, 커널은 Cause.ExcCode 필드를 확인하여 적절한 핸들러로 분기합니다. Linux 커널의 arch/mips/kernel/genex.S에서 이 디스패치 로직을 구현합니다.

/* General Exception 벡터 진입점 (0x80000180) */
/* arch/mips/kernel/genex.S — except_vec3_generic */
except_vec3_generic:
    .set    noreorder
    mfc0    $k1, $13               /* Cause 레지스터 읽기 */
    andi    $k1, $k1, 0x7c         /* ExcCode 필드 추출 (비트 6:2) */
    /* ExcCode * 4 = exception_handlers 테이블 인덱스 (이미 <<2 위치) */
    la      $k0, exception_handlers
    addu    $k0, $k0, $k1          /* 테이블 + ExcCode*4 */
    lw      $k0, 0($k0)            /* 핸들러 주소 로드 */
    jr      $k0                    /* 핸들러로 점프 */
    nop                             /* 지연 슬롯 */
    .set    reorder

/* exception_handlers 테이블 (ExcCode별 핸들러 포인터) */
/*  0: handle_int       — 인터럽트 */
/*  1: handle_tlbm      — TLB Modified */
/*  2: handle_tlbl      — TLB Load 미스 */
/*  3: handle_tlbs      — TLB Store 미스 */
/*  4: handle_adel      — Address Error (Load) */
/*  5: handle_ades      — Address Error (Store) */
/*  8: handle_sys       — System Call */
/*  9: handle_bp        — Breakpoint */
/* 10: handle_ri        — Reserved Instruction */
/* 11: handle_cpu       — Coprocessor Unusable */
/* 13: handle_ov        — Overflow */

예외 진입/복귀 전체 시퀀스

/* 예외 진입 — 레지스터 저장 (arch/mips/kernel/genex.S SAVE_ALL 매크로) */
exception_entry:
    .set    noreorder
    .set    noat
    mfc0    $k0, $12               /* Status 레지스터 */
    mfc0    $k1, $14               /* EPC (복귀 주소) */

    /* 커널 스택 포인터 확보 */
    addiu   $sp, $sp, -320         /* pt_regs 구조체 크기만큼 할당 */

    /* 모든 범용 레지스터 저장 */
    sw      $zero, 0($sp)          /* $0 (형식상) */
    sw      $at, 4($sp)            /* $at */
    sw      $v0, 8($sp)            /* $v0 */
    sw      $v1, 12($sp)           /* $v1 */
    sw      $a0, 16($sp)           /* $a0-$a3 */
    sw      $a1, 20($sp)
    sw      $a2, 24($sp)
    sw      $a3, 28($sp)
    /* ... $t0-$t7, $s0-$s7, $t8-$t9 저장 생략 ... */
    sw      $gp, 112($sp)          /* $gp */
    sw      $fp, 120($sp)          /* $fp */
    sw      $ra, 124($sp)          /* $ra */

    /* CP0 레지스터 저장 */
    sw      $k0, 128($sp)          /* Status */
    sw      $k1, 132($sp)          /* EPC */
    mfc0    $t0, $13
    sw      $t0, 136($sp)          /* Cause */
    mfc0    $t0, $8
    sw      $t0, 140($sp)          /* BadVAddr */
    /* HI/LO도 저장 (R2의 경우) */
    mfhi    $t0
    sw      $t0, 144($sp)          /* HI */
    mflo    $t0
    sw      $t0, 148($sp)          /* LO */
    .set    at
    .set    reorder

/* 예외 복귀 — 레지스터 복원 (RESTORE_ALL 매크로) */
exception_exit:
    .set    noreorder
    .set    noat
    /* HI/LO 복원 */
    lw      $t0, 144($sp)
    mthi    $t0
    lw      $t0, 148($sp)
    mtlo    $t0
    /* CP0 복원 */
    lw      $t0, 128($sp)          /* Status */
    lw      $t1, 132($sp)          /* EPC */
    mtc0    $t0, $12               /* Status 복원 */
    mtc0    $t1, $14               /* EPC 복원 */
    /* 모든 범용 레지스터 복원 */
    lw      $ra, 124($sp)
    lw      $fp, 120($sp)
    lw      $gp, 112($sp)
    /* ... $s0-$s7, $t0-$t9, $a0-$a3, $v0-$v1 복원 ... */
    lw      $at, 4($sp)
    addiu   $sp, $sp, 320          /* 스택 프레임 해제 */
    eret                            /* PC←EPC, EXL←0 (원자적) */
    .set    at
    .set    reorder

SYSCALL 진입 경로

/* 유저 공간: SYSCALL 명령어 → 예외 벡터로 트랩 */
/* 예외 벡터: 0x80000180 (General Exception, BEV=0) */
/* Cause.ExcCode = 8 (Syscall) */

/* arch/mips/kernel/scall32-o32.S 핵심 경로 */
handle_sys:
    lw      $v0, PT_R2($sp)        /* syscall 번호 ($v0) */
    sll     $v0, $v0, 2            /* × 4 (테이블 인덱스) */
    la      $t1, sys_call_table
    addu    $t1, $t1, $v0
    lw      $t0, 0($t1)            /* 시스콜 핸들러 주소 */
    jalr    $t0                    /* 핸들러 호출 */
    nop                             /* 지연 슬롯 */

ERET — 예외 복귀

/* Status.EXL=1 → EPC에서 복귀, EXL=0으로 복원 */
    mfc0    $k0, $14               /* EPC 읽기 */
    mfc0    $k1, $12               /* Status 읽기 */
    /* Status 복원 처리 ... */
    mtc0    $k1, $12               /* Status 쓰기 */
    eret                            /* PC←EPC, EXL←0 */

TLB 조작 패턴

/* TLB Refill 핸들러 (0x80000000) */
tlb_refill:
    mfc0    $k0, $4                /* Context 레지스터 */
    lw      $k1, 0($k0)            /* PTE 로드 */
    mtc0    $k1, $2                /* EntryLo0 */
    lw      $k1, 4($k0)            /* 다음 PTE */
    mtc0    $k1, $3                /* EntryLo1 */
    tlbwr                           /* Random 인덱스로 TLB 쓰기 */
    eret

CACHE 명령어

/* D-cache writeback + invalidate */
static inline void flush_dcache_line(unsigned long addr)
{
    asm volatile(
        "cache %0, 0(%1)"
        :: "i"(Hit_Writeback_Inv_D), "r"(addr));
}
CACHE 명령어 연산 타입: CACHE 명령어는 5-bit op 필드로 캐시 종류(I/D)와 연산을 지정합니다. 하위 2비트가 캐시 종류, 상위 3비트가 연산을 나타냅니다:
op 값매크로설명
0x00Index_Invalidate_II-cache: 인덱스로 무효화
0x01Index_Writeback_Inv_DD-cache: 인덱스로 기록 후 무효화
0x08Index_Store_Tag_II-cache: 태그 저장 (초기화용)
0x09Index_Store_Tag_DD-cache: 태그 저장 (초기화용)
0x10Hit_Invalidate_II-cache: 주소 히트 시 무효화 (자주 사용)
0x11Hit_Invalidate_DD-cache: 주소 히트 시 무효화 (쓰기 미저장)
0x15Hit_Writeback_Inv_DD-cache: 주소 히트 시 기록 후 무효화 (가장 일반적)
0x19Hit_Writeback_DD-cache: 주소 히트 시 기록 (무효화하지 않음)
커널에서 가장 많이 사용되는 패턴: DMA 전송 전 Hit_Writeback_Inv_D로 D-cache를 메모리에 기록하고, 자체 수정 코드 후 Hit_Invalidate_I로 I-cache를 무효화합니다.

명령어 인코딩

MIPS는 3가지 기본 인코딩 타입을 사용합니다: R(레지스터), I(즉시값), J(점프).

MIPS 32-bit 인코딩 타입 (R / I / J) 31 26 25 21 20 16 15 11 10 6 5 0 R: op (6) rs (5) rt (5) rd (5) sa (5) funct (6) I: op (6) rs (5) rt (5) immediate (16) J: op (6) target (26) R: ADD/SUB/AND/OR/XOR/SLL/SRL/SRA/JR/JALR/MULT/DIV I: ADDI/LW/SW/BEQ/BNE/LUI/ORI/ANDI/SLTI | J: J/JAL

인코딩 실제 예: addu $t0, $t1, $t2

R-type 인코딩 분석: addu $t0, $t1, $t2는 R-type 명령어입니다. 각 필드에 실제 값을 대입하면:
필드이진수설명
op0 (SPECIAL)000000R-type은 op=0, funct로 구분
rs$t1 = $901001소스 레지스터 1
rt$t2 = $1001010소스 레지스터 2
rd$t0 = $801000목적지 레지스터
sa000000시프트 양 (ADDU에선 미사용)
funct0x21 (ADDU)100001ADDU 함수 코드
결과: 000000 01001 01010 01000 00000 100001 = 0x012A4021

R6 Compact Branch 인코딩

R6 Compact Branch 인코딩 (지연 슬롯 없음) BC: 110010 (0x32) offset[25:0] (부호 확장, <<2 → +/-128MB 범위) BEQZC: 110110 (0x36) rs (5) offset[20:0] (부호 확장, <<2 → +/-4MB 범위) BEQC: 001000 (0x08) rs (5) rt (5) offset[15:0] (부호 확장, <<2) R6 compact branch는 지연 슬롯이 없으며, 분기 다음 명령어는 분기하지 않았을 때만 실행됩니다. 기존 R2 BEQ/BNE 인코딩과 opcode가 다르므로 R2/R6 바이너리는 호환되지 않습니다.

TLB 관리

MIPS는 소프트웨어 관리 TLB를 사용합니다. TLB 미스 시 CPU가 자동으로 페이지 테이블(Page Table)을 검색하는 x86/ARM64와 달리, MIPS는 TLB Refill 예외를 발생시키고 커널이 직접 TLB 엔트리를 채워야 합니다. 이를 위해 EntryHi, EntryLo0/1, PageMask, Index CP0 레지스터와 TLBWR/TLBWI/TLBR/TLBP 명령어를 사용합니다.

MIPS 소프트웨어 관리 TLB 구조 TLB 엔트리 구조 (쌍 페이지 매핑) EntryHi: VPN2 [31:13] | ASID [7:0] PageMask [28:13] Index [31,5:0] EntryLo0: PFN [29:6] | C [5:3] | D V G EntryLo1: PFN [29:6] | C [5:3] | D V G — 짝수/홀수 페이지 쌍 VPN2: 가상 페이지 번호 (짝수 페이지, VA[31:13]) | ASID: 주소 공간 식별자 (8비트, 256개) EntryLo0/EntryLo1 비트 필드 bit 0 (G) : Global — ASID 무시 bit 1 (V) : Valid — 유효한 매핑 bit 2 (D) : Dirty — 쓰기 허용 bit 5:3 (C): Cache Coherency 2=Uncached, 3=Cacheable(WB) 4=Cacheable(WA), 5=Uncached Accel bit 29:6 (PFN): 물리 프레임 번호 TLB 관리 명령어 TLBR Index → EntryHi/Lo0/Lo1 읽기 TLBWI Index 위치에 TLB 쓰기 TLBWR Random 위치에 TLB 쓰기 TLBP EntryHi로 TLB 검색 → Index TLBINV / TLBINVF (R6) TLB 무효화 TLB Refill 예외 흐름 (ExcCode=2/3) TLB 미스! ExcCode=2(L)/3(S) 0x80000000 진입 PTE 조회 MTC0 EntryLo0/1 TLBWR Context CP0: BadVPN2 [22:4] | PTEBase [31:23] → 페이지 테이블 엔트리 주소 직접 계산 TLB Refill 핸들러는 최소 명령어로 작성 (성능 크리티컬, ~20 명령어) PageMask: 4KB(0), 16KB(0x6000), 64KB(0x1E000), 256KB(0x7E000), ... Wired 레지스터: TLB[0..Wired-1]은 TLBWR에서 교체 대상 제외 (고정 매핑)
/* arch/mips/mm/tlbex.c — TLB Refill 핸들러 (빌드 시 동적 생성) */
/* 가장 성능 크리티컬한 커널 코드 — ~20 명령어로 최적화 */

/* 개념적 TLB Refill 핸들러 (R2/R6 공통 원리) */
tlb_refill_handler:
    /* 1. k0/k1은 예외에서 자유롭게 사용 가능 (커널 전용) */
    mfc0    k1, C0_CONTEXT       /* Context.PTEBase + BadVPN2 → PTE 주소 */
    lw      k0, 0(k1)            /* EntryLo0 = PTE[even] */
    lw      k1, 4(k1)            /* EntryLo1 = PTE[odd] */
    mtc0    k0, C0_ENTRYLO0      /* EntryLo0 설정 */
    mtc0    k1, C0_ENTRYLO1      /* EntryLo1 설정 */
    ehb                           /* Execution Hazard Barrier */
    tlbwr                         /* Random 위치에 TLB 쓰기 */
    eret                          /* 예외 복귀 (EPC로 점프) */

/* TLB 프로빙과 무효화 */
    mtc0    t0, C0_ENTRYHI       /* 검색할 VPN2|ASID 설정 */
    ehb
    tlbp                          /* TLB 검색 → Index에 결과 */
    ehb
    mfc0    t1, C0_INDEX          /* Index.P(bit31)=1이면 미스 */
    bltz    t1, not_found         /* 음수 → TLB에 없음 */
    /* Index >= 0 → 해당 위치에 무효 엔트리 쓰기 */
    mtc0    zero, C0_ENTRYLO0
    mtc0    zero, C0_ENTRYLO1
    ehb
    tlbwi                         /* Index 위치에 쓰기 (무효화) */

CP0 레지스터 비트 필드 상세

CP0 (Coprocessor 0)는 MIPS의 시스템 제어 레지스터 집합입니다. x86의 CR0-CR4/MSR, ARM64의 시스템 레지스터에 대응합니다. 커널은 mfc0/mtc0로 CP0에 접근하며, 예외 처리·TLB 관리·캐시 설정·인터럽트 제어의 핵심입니다.

MIPS CP0 핵심 레지스터 비트 필드 Status (CP0 $12, sel 0) bit 0 (IE) : Interrupt Enable bit 1 (EXL) : Exception Level (예외 중) bit 2 (ERL) : Error Level (에러 중) bit 4:3 (KSU): 모드 (00=커널,10=유저) bit 15:8 (IM): 인터럽트 마스크 (IP7:0) bit 22 (BEV): Bootstrap 예외 벡터 bit 27 (FR) : FPR 모드 (1=64비트) bit 28 (CU0): CP0 유저 접근 허용 bit 29 (CU1): FPU 활성화 EXL=1/ERL=1 → 인터럽트 비활성, 커널 모드 Cause (CP0 $13, sel 0) bit 6:2 (ExcCode): 예외 코드 0=Int, 1=TLB Mod, 2=TLBL 3=TLBS, 4=AdEL, 5=AdES 8=Syscall, 9=Bp, 10=RI 11=CpU, 13=Trap, 15=FPE bit 15:8 (IP): 인터럽트 Pending IP7: 타이머, IP6-2: 외부 인터럽트 IP1-0: 소프트웨어 인터럽트 bit 31 (BD): Branch Delay 슬롯 BD=1 → EPC는 분기 명령어 (재실행 필요) Config (CP0 $16, sel 0-5) bit 2:0 (K0): kseg0 캐시 정책 bit 14:13 (AT): 아키텍처 타입 Config1: I$/D$ 크기, TLB 엔트리 수 Config3: ULRI(UserLocal), VInt, VEIC 기타 핵심 CP0 레지스터 EPC ($14) 예외 복귀 주소 BadVAddr ($8) 주소 에러/TLB 미스 주소 PRId ($15) 프로세서 ID (제조사+모델) Count/Compare ($9/$11) 타이머 (IP7) 예외 벡터 주소 (BEV=0 기준) 0x80000000 TLB Refill (ExcCode=2/3, EXL=0) 0x80000180 일반 예외 (모든 ExcCode) 0x80000200 인터럽트 (Config3.VInt=1 시) EBase + 0x200 + n*0x20 벡터 인터럽트 (VI 모드) BEV=1(부팅): 0xBFC00200 (TLB Refill), 0xBFC00380 (일반) — ROM/Flash 영역
CP0 레지스터번호핵심 필드커널 사용
Index$0P(probe result), Index[5:0]TLBWI/TLBP 대상
Random$1Random[5:0]TLBWR 자동 감소 카운터
EntryLo0/1$2/$3PFN, C, D, V, GTLB 쓰기 데이터
Context$4PTEBase, BadVPN2TLB Refill 빠른 PTE 조회
PageMask$5Mask[28:13]가변 페이지 크기 (4KB-256MB)
Wired$6Wired[5:0]고정 TLB 엔트리 수
BadVAddr$8전체 VA주소 예외/TLB 미스 주소
Count$932비트 카운터타이머 (CPU 클럭/2)
EntryHi$10VPN2, ASIDTLB 매칭 키
Compare$1132비트 비교값Count==Compare → IP7 인터럽트
Status$12IE, EXL, ERL, KSU, IM, CU인터럽트/모드 제어
Cause$13ExcCode, IP, BD예외 원인 판별
EPC$14복귀 주소예외 복귀 (eret)
PRId$15,0Company, ProcessorCPU 식별
EBase$15,1Exception Base예외 벡터 베이스 재배치
Config$16K0, AT, AR, MT아키텍처 설정
⚠️

Hazard Barrier 필수: MIPS에서 CP0 레지스터를 mtc0로 수정한 후 해당 값이 실제로 반영되기까지 실행 해저드(Execution Hazard)가 있습니다. R2에서는 ehb(sll $0,$0,3) 명령어를, R6에서는 ehb 또는 충분한 nop 삽입이 필요합니다. TLBWI/TLBWR 전에 ehb를 빠뜨리면 이전 CP0 값이 반영되지 않아 잘못된 TLB 엔트리가 기록될 수 있습니다.

ABI 호출 규약 (o32/n32/n64)

MIPS에는 세 가지 주요 ABI가 존재합니다. o32는 MIPS32의 전통적 ABI로 4개의 인자 레지스터($a0-$a3)를 사용하며, n32n64는 MIPS64에서 8개의 인자 레지스터($a0-$a7)를 사용합니다. Linux 커널에서는 arch/mips/kernel/scall32-o32.Sarch/mips/kernel/scall64-n64.S에서 각 ABI의 시스템 콜 진입점(Entry Point)을 분리 구현합니다.

특성o32n32n64
레지스터 폭32-bit64-bit (32-bit 주소)64-bit
인자 레지스터$a0-$a3 (4개)$a0-$a7 (8개)$a0-$a7 (8개)
반환값$v0, $v1$v0, $v1$v0, $v1
Callee-saved$s0-$s7, $fp, $sp$s0-$s7, $fp, $sp$s0-$s7, $fp, $sp
Caller-saved$t0-$t9, $at, $v0-$v1, $a0-$a3$t0-$t3, $at, $v0-$v1, $a0-$a7$t0-$t3, $at, $v0-$v1, $a0-$a7
FP 인자$f12, $f14$f12-$f19 (8개)$f12-$f19 (8개)
스택 정렬8바이트16바이트16바이트
syscall 번호$v0 (4000+)$v0 (6000+)$v0 (5000+)
HI/LO 저장Caller 책임Caller 책임Caller 책임
MIPS ABI 레지스터 규약 비교 (o32 vs n64) MIPS ABI 레지스터 사용 규약 비교 o32 ABI $a0-$a3 (4개 인자) → 나머지 스택 $v0-$v1 (반환값) $t0-$t9 (10개 임시, Caller-saved) $s0-$s7 (8개 보존, Callee-saved) o32 스택 프레임 높은 주소 ↑ [caller args area: 16B 최소] [$ra 저장] [$fp 저장] [$s0-$s7 저장] [지역 변수] 낮은 주소 ↓ $sp → [arg build area] n64 ABI $a0-$a7 (8개 인자) → 나머지 스택 $v0-$v1 (반환값) $t0-$t3 (4개 임시, Caller-saved) $s0-$s7 (8개 보존, Callee-saved) n64 스택 프레임 높은 주소 ↑ [home area 없음 (ABI 차이)] [$ra 저장] [$fp 저장] [$s0-$s7 저장] [지역 변수] 낮은 주소 ↓ $sp → [16B 정렬] 핵심 차이점 • o32: $a0-$a3 = $4-$7, $t0-$t9 = $8-$15,$24,$25 (10개 임시) • n64: $a0-$a7 = $4-$11, $t0-$t3 = $12-$15 (o32의 $t4-$t7이 $a4-$a7로 재할당) • o32 스택: arg area 16B 최소 예약 필수 / n64 스택: home area 불필요, 16B 정렬

o32 vs n64 함수 호출 비교

/* o32 ABI: 5개 인자 함수 호출 — $a0-$a3 + 스택 */
/* int func(int a, int b, int c, int d, int e); */
    li      $a0, 1                 /* 첫 번째 인자 */
    li      $a1, 2                 /* 두 번째 인자 */
    li      $a2, 3                 /* 세 번째 인자 */
    li      $a3, 4                 /* 네 번째 인자 */
    li      $t0, 5
    sw      $t0, 16($sp)           /* 다섯 번째 인자 → 스택 */
    jal     func                   /* 함수 호출 */
    nop                             /* 지연 슬롯 */
    /* 반환값: $v0 */
/* n64 ABI: 동일한 5개 인자 — 모두 레지스터 */
/* long func(long a, long b, long c, long d, long e); */
    li      $a0, 1                 /* 첫 번째 인자 */
    li      $a1, 2                 /* 두 번째 인자 */
    li      $a2, 3                 /* 세 번째 인자 */
    li      $a3, 4                 /* 네 번째 인자 */
    li      $a4, 5                 /* 다섯 번째 인자 (레지스터!) */
    jal     func                   /* 함수 호출 */
    nop                             /* 지연 슬롯 */
    /* 반환값: $v0 */

HI/LO 레지스터 저장 (o32)

/* HI/LO 레지스터 컨텍스트 저장/복원 */
/* 곱셈/나눗셈 결과가 HI:LO에 남아있으면 caller가 저장해야 함 */
save_hilo:
    mfhi    $t0                    /* HI → $t0 */
    mflo    $t1                    /* LO → $t1 */
    sw      $t0, 0($s0)            /* HI 저장 */
    sw      $t1, 4($s0)            /* LO 저장 */

restore_hilo:
    lw      $t0, 0($s0)            /* HI 복원 */
    lw      $t1, 4($s0)            /* LO 복원 */
    mthi    $t0                    /* $t0 → HI */
    mtlo    $t1                    /* $t1 → LO */

/* R6에서는 HI/LO 제거 — MUL rd,rs,rt 직접 사용 */
/* R6: MUL $v0, $a0, $a1   → $v0 = $a0 * $a1 하위 32비트 */
/* R6: MUH $v1, $a0, $a1   → $v1 = $a0 * $a1 상위 32비트 */
n32 ABI: n32는 n64와 동일한 레지스터 규약(8개 인자 레지스터)을 사용하지만 포인터와 long 타입이 32비트입니다. MIPS64 프로세서에서 32비트 주소 공간으로 동작하므로, 64비트 레지스터를 활용하면서도 메모리 효율이 높습니다. Linux 커널에서는 CONFIG_MIPS32_O32, CONFIG_MIPS32_N32 옵션으로 ABI 호환 계층을 선택합니다.

예외/인터럽트 처리

MIPS 예외 처리는 Status 레지스터의 EXL(Exception Level) 비트를 중심으로 동작합니다. 예외 발생 시 하드웨어가 자동으로 EXL=1로 설정하여 인터럽트를 비활성화하고 커널 모드로 전환한 뒤, Cause.ExcCode에 예외 원인을 기록하고, EPC에 복귀 주소를 저장합니다. 소프트웨어는 General Exception 벡터(0x80000180)에서 ExcCode를 디코딩하여 적절한 핸들러로 분기합니다.

MIPS 예외/인터럽트 처리 흐름과 Status/Cause 레지스터 MIPS 예외 처리 흐름 (General Exception) 예외 발생 하드웨어 자동 동작 1. Status.EXL ← 1 (커널 모드, IRQ 비활성) 2. Cause.ExcCode ← 예외 원인 코드 3. EPC ← 예외 발생 PC (BD=1이면 분기 PC) 4. BadVAddr ← 문제 가상 주소 (해당 시) 벡터 선택 EXL=0 + TLB미스 → 0x80000000 캐시 에러 → 0x80000100 그 외 모든 예외 → 0x80000180 BEV=1 → 0xBFC00xxx General Exception Handler (except_vec3_generic) mfc0 $k1, $Cause /* Cause 레지스터 읽기 */ andi $k1, $k1, 0x7c /* ExcCode 추출 (bit[6:2]) */ lw $k0, exception_handlers($k1) /* 핸들러 테이블 조회 */ jr $k0 /* 핸들러로 점프 */ ExcCode 주요 값: 0=Int 2=TLBL 3=TLBS 4=AdEL 5=AdES 8=Sys 9=Bp 10=RI 13=Tr do_page_fault (TLBL/S) handle_sys (Syscall) do_ri / do_bp (RI/Bp) eret (EPC → PC, EXL ← 0) eret 실행 시: Status.EXL ← 0, PC ← EPC → 유저 모드 복귀 (KSU 필드 반영), 인터럽트 재활성

예외 벡터 초기화

/* arch/mips/kernel/traps.c — 예외 벡터 설치 */
void __init trap_init(void)
{
    /* exception_handlers[] 테이블 초기화 */
    set_handler(0 * 4, handle_int);       /* ExcCode 0: 인터럽트 */
    set_handler(1 * 4, handle_tlbm);      /* ExcCode 1: TLB Modified */
    set_handler(2 * 4, handle_tlbl);      /* ExcCode 2: TLB Load */
    set_handler(3 * 4, handle_tlbs);      /* ExcCode 3: TLB Store */
    set_handler(4 * 4, handle_adel);      /* ExcCode 4: Address Error Load */
    set_handler(5 * 4, handle_ades);      /* ExcCode 5: Address Error Store */
    set_handler(8 * 4, handle_sys);       /* ExcCode 8: System Call */
    set_handler(9 * 4, handle_bp);        /* ExcCode 9: Breakpoint */
    set_handler(10 * 4, handle_ri);       /* ExcCode 10: Reserved Instruction */

    /* TLB Refill 핸들러를 0x80000000에 복사 */
    set_uncached_handler(0x0, &tlb_handler, 0x80);

    /* General Exception 핸들러를 0x80000180에 복사 */
    set_uncached_handler(0x180, &except_vec3_generic, 0x80);
}

General Exception 디스패치

/* arch/mips/kernel/genex.S — General Exception 벡터 */
NESTED(except_vec3_generic, 0, sp)
    .set    push
    .set    noat
    mfc0    k1, CP0_CAUSE          /* Cause 레지스터 읽기 */
    andi    k1, k1, 0x7c          /* ExcCode 필드 추출 (bit 6:2) */
#ifdef CONFIG_64BIT
    dsll    k1, k1, 1             /* 64비트: 8바이트 포인터 */
#endif
    PTR_L   k0, exception_handlers(k1)  /* 핸들러 주소 로드 */
    jr      k0                    /* 핸들러로 점프 */
    .set    pop
    END(except_vec3_generic)

Status/Cause 레지스터 조작

/* 인터럽트 마스크 조작 예제 */
enable_irq:
    mfc0    $t0, $12              /* Status 읽기 */
    ori     $t0, $t0, 0x8401      /* IE=1, IM2=1, IM7=1 활성 */
    mtc0    $t0, $12              /* Status 쓰기 */
    ehb                            /* 실행 해저드 배리어 */

disable_irq_save:
    di      $v0                    /* R2: 인터럽트 비활성 + 이전 Status 반환 */
    ehb                            /* 해저드 배리어 */
    jr      $ra
    nop

restore_irq:
    mtc0    $a0, $12              /* 저장된 Status 복원 */
    ehb                            /* 해저드 배리어 */
    jr      $ra
    nop
⚠️

$k0/$k1 전용 레지스터: 예외 핸들러 진입 시점에서는 아직 레지스터를 저장할 스택 포인터조차 신뢰할 수 없습니다. 그래서 MIPS ABI는 $k0($26)과 $k1($27)을 커널 전용으로 예약했습니다. 유저 프로그램은 이 레지스터를 사용하면 안 되며, 예외 핸들러가 경고 없이 덮어쓸 수 있습니다.

커널 어셈블리 실전 예제

Linux MIPS 커널은 성능 크리티컬 경로와 하드웨어 의존 코드를 어셈블리로 직접 구현합니다. 시스템 콜 진입, 컨텍스트 스위치, 원자적 연산(LL/SC), 유저 공간 메모리 접근, TLB Refill 핸들러가 대표적입니다.

시스템 콜 진입 (handle_sys)

/* arch/mips/kernel/scall32-o32.S — o32 시스템 콜 핸들러 */
NESTED(handle_sys, PT_SIZE, sp)
    .set    noat
    SAVE_SOME                      /* $at, $v0-$v1, $a0-$a3, ... 저장 */
    SAVE_AT
    SAVE_TEMP

    lw      $t0, PT_R2($sp)        /* 시스콜 번호 ($v0에서 저장됨) */
    subu    $t0, $t0, __NR_O32_Linux /* 오프셋 계산 */
    sltiu   $t1, $t0, __NR_O32_Linux_syscalls
    beqz    $t1, illegal_syscall   /* 범위 초과 */

    sll     $t0, $t0, 2            /* × 4 (함수 포인터 크기) */
    la      $t1, sys_call_table    /* 시스콜 테이블 주소 */
    addu    $t1, $t1, $t0
    lw      $t0, 0($t1)            /* 핸들러 함수 주소 로드 */
    jalr    $t0                    /* 시스콜 핸들러 호출 */
    nop                             /* 지연 슬롯 */

    sw      $v0, PT_R2($sp)        /* 반환값 저장 */
    j       syscall_exit
    nop
    END(handle_sys)

컨텍스트 스위치 (resume)

/* arch/mips/kernel/r4k_switch.S — 프로세스 전환 */
LEAF(resume)
    mfc0    $t1, CP0_STATUS        /* 현재 Status 저장 */
    sw      $t1, THREAD_STATUS($a0) /* prev→thread.cp0_status */

    /* callee-saved 레지스터 저장 */
    sw      $s0, THREAD_REG16($a0)
    sw      $s1, THREAD_REG17($a0)
    sw      $s2, THREAD_REG18($a0)
    sw      $s3, THREAD_REG19($a0)
    sw      $s4, THREAD_REG20($a0)
    sw      $s5, THREAD_REG21($a0)
    sw      $s6, THREAD_REG22($a0)
    sw      $s7, THREAD_REG23($a0)
    sw      $sp, THREAD_REG29($a0)  /* prev 스택 포인터 */
    sw      $fp, THREAD_REG30($a0)
    sw      $ra, THREAD_REG31($a0)

    /* next 프로세스로 전환 */
    lw      $s0, THREAD_REG16($a1)
    lw      $s1, THREAD_REG17($a1)
    lw      $s2, THREAD_REG18($a1)
    lw      $s3, THREAD_REG19($a1)
    lw      $s4, THREAD_REG20($a1)
    lw      $s5, THREAD_REG21($a1)
    lw      $s6, THREAD_REG22($a1)
    lw      $s7, THREAD_REG23($a1)
    lw      $sp, THREAD_REG29($a1)  /* next 스택 포인터 */
    lw      $fp, THREAD_REG30($a1)
    lw      $ra, THREAD_REG31($a1)

    lw      $t1, THREAD_STATUS($a1)
    mtc0    $t1, CP0_STATUS        /* next Status 복원 */

    move    $v0, $a0               /* prev 태스크 반환 */
    jr      $ra
    nop
    END(resume)

원자적 연산 (LL/SC 루프)

/* arch/mips/include/asm/atomic.h — atomic_add 어셈블리 */
/* LL/SC 루프: 하드웨어 비교-교환 없이 원자성 보장 */
atomic_add:           /* $a0=값, $a1=atomic_t 주소 */
1:
    ll      $t0, 0($a1)            /* Load Linked: 값 로드 + 링크 설정 */
    addu    $t0, $t0, $a0          /* 덧셈 */
    sc      $t0, 0($a1)            /* Store Conditional: 링크 유효 시 저장 */
    beqz    $t0, 1b               /* SC 실패(0) → 재시도 */
    nop                             /* 지연 슬롯 */
    jr      $ra
    nop

/* R6에서는 SC 실패 시 자동 재시도하는 llsc_wb 옵션 가능 */
/* CONFIG_WAR_R10000_LLSC: R10000 LL/SC 버그 워크어라운드 */

유저 공간 메모리 접근 (copy_to_user)

/* arch/mips/lib/memcpy.S — __copy_user 핵심 루프 */
/* 유저 공간 접근 시 페이지 폴트 가능 → fixup 섹션 필요 */
__copy_user:
    .set    noreorder
EXC(    lw      $t0, 0($a1),    l_fixup)   /* 유저에서 읽기 */
EXC(    lw      $t1, 4($a1),    l_fixup)
EXC(    sw      $t0, 0($a0),    s_fixup)   /* 커널에 쓰기 */
EXC(    sw      $t1, 4($a0),    s_fixup)
    addiu   $a0, $a0, 8
    addiu   $a1, $a1, 8
    subu    $a2, $a2, 8
    bnez    $a2, __copy_user
    nop

/* EXC 매크로: __ex_table에 fixup 주소 등록 */
/* 페이지 폴트 발생 시 fixup으로 점프하여 -EFAULT 반환 */

l_fixup:
    move    $v0, $a2               /* 미복사 바이트 수 반환 */
    jr      $ra
    nop

TLB Refill 핸들러

/* arch/mips/mm/tlbex.c에서 동적 생성되는 TLB Refill 핸들러 (개념) */
/* 0x80000000에 배치, 최대 32개 명령어 */
tlb_refill:
    mfc0    k0, CP0_BADVADDR       /* 미스된 가상 주소 */
    mfc0    k1, CP0_CONTEXT        /* PTEBase + BadVPN2 */
    lw      k0, 0(k1)              /* PTE 짝수 페이지 */
    lw      k1, 4(k1)              /* PTE 홀수 페이지 */
    mtc0    k0, CP0_ENTRYLO0       /* EntryLo0 설정 */
    mtc0    k1, CP0_ENTRYLO1       /* EntryLo1 설정 */
    ehb                            /* 해저드 배리어 */
    tlbwr                          /* Random 위치에 TLB 쓰기 */
    eret                           /* 복귀 (EXL ← 0) */

/* Linux 실제 구현: tlbex.c에서 CPU 모델별 최적 코드를 런타임 생성 */
/* MIPS32: Context 레지스터의 PTEBase로 PGD 접근 */
/* MIPS64: XCONTEXT로 3-level 페이지 테이블 탐색 */
동적 TLB 핸들러 생성: Linux MIPS는 TLB Refill 핸들러를 부팅 시 arch/mips/mm/tlbex.cbuild_r4000_tlb_refill_handler()에서 CPU 모델에 맞게 동적으로 생성합니다. R2/R6, 32/64비트, HugePage 지원 여부에 따라 최적화된 명령어 시퀀스가 만들어집니다.

캐시 연산

MIPS의 CACHE 명령어는 I-cache와 D-cache를 직접 제어할 수 있는 특권 명령어입니다. 명령어 형식은 cache op, offset(base)이며, op 필드가 캐시 종류(2비트)와 연산 종류(3비트)를 인코딩합니다. DMA 전송이나 자기 수정 코드(SMC) 시나리오에서 올바른 캐시 관리가 필수적입니다.

MIPS CACHE 명령어 인코딩과 연산 흐름 MIPS CACHE 명령어 인코딩과 연산 CACHE 명령어 인코딩 (I-type format) | 101111 | base[5] | op[4:0] | offset[15:0] | opcode rs op[4:2]=연산 op[1:0]=캐시종류 주소 오프셋 캐시 종류 (op[1:0]) 00 = Primary I-cache 01 = Primary D-cache 10 = Tertiary (구현 의존) 11 = Secondary cache 연산 종류 (op[4:2]) 000 = Index Invalidate 001 = Index Load Tag 010 = Index Store Tag 100 = Hit Invalidate 101 = Hit Writeback Invalidate 110 = Hit Writeback (dirty만) 111 = Fetch and Lock 주요 사용 시나리오 DMA 수신 전: D-cache Invalidate DMA 송신 전: D-cache Writeback 코드 수정 후: D-cache WB + I-cache Inv 부팅 초기화: Index Invalidate (전체) 캐시 락: Fetch and Lock (실시간) Index 연산 인덱스(캐시 라인 번호)로 직접 접근 — 태그 무시, 전체 순회 가능 Hit 연산 가상 주소로 캐시 탐색 — 태그 매칭 시에만 연산 수행 (미스 시 무시) DMA 캐시 일관성 시퀀스 DMA_TO_DEVICE: D-cache Hit Writeback → DMA 전송 DMA_FROM_DEVICE: DMA 수신 → D-cache Hit Invalidate

캐시 명령어 사용

/* MIPS 캐시 연산 기본 사용법 */
/* cache op, offset(base) 형식 */

/* D-cache 라인 Writeback + Invalidate (Hit) */
    cache   0x15, 0($a0)          /* Hit_Writeback_Inv_D (101_01) */

/* I-cache 라인 Invalidate (Hit) */
    cache   0x10, 0($a0)          /* Hit_Invalidate_I (100_00) */

/* D-cache 전체 Invalidate (Index) — 부팅 시 초기화 */
__init_dcache:
    li      $t0, 0x80000000       /* kseg0 시작 */
    li      $t1, DCACHE_SIZE       /* D-cache 전체 크기 */
    addu    $t1, $t0, $t1
1:
    cache   0x01, 0($t0)          /* Index_Writeback_Inv_D */
    addiu   $t0, $t0, LINESIZE    /* 다음 캐시 라인 */
    bne     $t0, $t1, 1b
    nop

r4k_blast_dcache 구현

/* arch/mips/mm/c-r4k.c — D-cache 전체 flush */
static void r4k_blast_dcache(void)
{
    unsigned long dc_lsize = cpu_dcache_line_size();
    unsigned long dc_size  = current_cpu_data.dcache.waysize
                           * current_cpu_data.dcache.ways;
    unsigned long addr = CKSEG0;  /* kseg0: 0x80000000 */
    unsigned long end  = addr + dc_size;

    while (addr < end) {
        cache_op(Index_Writeback_Inv_D, addr);
        addr += dc_lsize;
    }
}

/* DMA 동기화: 가상 주소 범위의 D-cache flush */
static void r4k_dma_cache_wback_inv(unsigned long addr, unsigned long size)
{
    if (size >= dcache_size) {
        r4k_blast_dcache();  /* 전체 flush가 더 빠름 */
    } else {
        blast_dcache_range(addr, addr + size);  /* 범위 flush */
    }
}

DMA 캐시 동기화

/* arch/mips/mm/dma-noncoherent.c — DMA 캐시 동기화 */
void arch_sync_dma_for_device(phys_addr_t paddr, size_t size,
                              enum dma_data_direction dir)
{
    switch (dir) {
    case DMA_TO_DEVICE:
        dma_cache_wback((unsigned long)phys_to_virt(paddr), size);
        break;
    case DMA_FROM_DEVICE:
        dma_cache_inv((unsigned long)phys_to_virt(paddr), size);
        break;
    case DMA_BIDIRECTIONAL:
        dma_cache_wback_inv((unsigned long)phys_to_virt(paddr), size);
        break;
    }
}

/* MIPS 하드웨어 캐시 일관성이 없는 플랫폼에서 필수 */
/* CONFIG_DMA_NONCOHERENT: 대부분의 MIPS SoC */
⚠️

I-cache/D-cache 비일관성: MIPS는 일반적으로 Harvard 캐시 구조를 사용하여 I-cache와 D-cache가 분리됩니다. 자기 수정 코드(JIT, 모듈 로딩)에서는 반드시 D-cache Writeback 후 I-cache Invalidate를 수행해야 합니다. flush_icache_range()가 이 시퀀스를 처리합니다.

마이크로아키텍처 변종

MIPS ISA는 여러 Release와 변종으로 나뉩니다. 특히 Release 6(2014)은 이전 버전과 하위 호환이 깨지는 대규모 변경을 도입했습니다. 지연 슬롯 없는 compact branch, HI/LO 레지스터 제거, 정렬되지 않은 메모리 접근 지원, 일부 명령어 재인코딩 등이 핵심 변경 사항입니다. 또한 microMIPSnanoMIPS 변종은 코드 밀도를 높이기 위한 가변 길이 인코딩을 제공합니다.

특성MIPS32/64 R2MIPS32/64 R6microMIPSnanoMIPS
출시2003201420092018
명령어 길이고정 32비트고정 32비트16/32비트 혼합16/32/48비트 혼합
지연 슬롯있음 (필수)compact branch 추가있음/없음 혼합없음
HI/LO 레지스터사용 (MULT→HI:LO)제거 (MUL rd,rs,rt)R2 기반R6 기반
비정렬 접근예외 발생 (LWL/LWR 사용)하드웨어 지원R2 동일R6 동일
분기 조건BEQ/BNE + 지연 슬롯BC/BALC (compact, PC-rel)B16/BEQ16 등BC/BALC
PC-relative없음ALUIPC, AUIPC, LWPCADDIUPC전면 지원
Linux 지원전면 지원4.0+ 지원부분 지원미지원
주요 CPUMIPS24K/34K/74K, OcteonWarrior I6400/I6500M14K/M14KcI7200 (IoT)
코드 밀도기본기본~35% 절감~40% 절감

R6 vs pre-R6 분기 차이

/* pre-R6 (R2): 지연 슬롯 있는 분기 */
    beq     $a0, $a1, target     /* 분기 결정 */
    addiu   $v0, $zero, 1        /* 지연 슬롯: 항상 실행됨! */
    /* 분기 안 됐으면 여기로 */
    addiu   $v0, $zero, 0

/* R6: compact branch — 지연 슬롯 없음 */
    beqc    $a0, $a1, target     /* compact: 지연 슬롯 없음 */
    /* 분기 안 됐으면 즉시 여기 실행 */
    addiu   $v0, $zero, 0

/* R6: PC-relative 주소 계산 (PIC 코드에 유용) */
    auipc   $t0, %pcrel_hi(symbol) /* PC + upper 20비트 */
    addiu   $t0, $t0, %pcrel_lo(symbol) /* + lower 12비트 */

/* R6: 비정렬 로드 (LWL/LWR 대체) */
    lw      $t0, 1($a0)            /* R6: 비정렬 주소도 하드웨어 처리 */
/* pre-R6: 비정렬 4바이트 로드 */
    lwl     $t0, 3($a0)            /* Left: 상위 바이트 로드 */
    lwr     $t0, 0($a0)            /* Right: 하위 바이트 로드 */

Compact Branch 활용

/* R6 compact branch 종류 */
/* 모두 지연 슬롯 없음, 분기 조건 직후 다음 명령어는 비분기 시에만 실행 */

/* 레지스터 비교 분기 */
    beqc    $a0, $a1, equal       /* $a0 == $a1 */
    bnec    $a0, $a1, not_equal   /* $a0 != $a1 */
    bltc    $a0, $a1, less        /* $a0 < $a1 (signed) */
    bgec    $a0, $a1, ge          /* $a0 >= $a1 (signed) */
    bltuc   $a0, $a1, less_u      /* $a0 < $a1 (unsigned) */
    bgeuc   $a0, $a1, ge_u        /* $a0 >= $a1 (unsigned) */

/* 즉석 비교 분기 (0과 비교) */
    beqzc   $a0, is_zero          /* $a0 == 0 */
    bnezc   $a0, not_zero         /* $a0 != 0 */
    bltzc   $a0, negative         /* $a0 < 0 */
    bgezc   $a0, non_neg          /* $a0 >= 0 */

/* 무조건 compact 분기 */
    bc      target                /* 무조건 점프 (26비트 오프셋) */
    balc    target                /* 무조건 함수 호출 (link) */

/* 커널 영향: R6 지원 시 지연 슬롯 nop 제거 → 코드 크기 감소 */
R6 하위 호환성 파괴: Release 6은 MIPS 역사상 처음으로 하위 호환성을 깬 릴리스입니다. R2 바이너리는 R6 CPU에서 실행할 수 없고 그 반대도 마찬가지입니다. Linux 커널은 CONFIG_CPU_MIPSR2CONFIG_CPU_MIPSR6로 빌드 타임에 구분합니다. 일부 R2 전용 명령어(LWL/LWR, MULT→HI:LO)를 R6에서 사용하면 Reserved Instruction(RI) 예외가 발생합니다.

EVA (Enhanced Virtual Addressing)

MIPS EVA(Enhanced Virtual Addressing)는 MIPS32 Release 3(2010)에서 도입된 확장으로, 커널과 유저 공간이 동일한 가상 주소 범위를 사용할 수 있게 합니다. 기존 MIPS에서는 kseg0/kseg1(0x80000000~0xBFFFFFFF)이 커널 전용이었지만, EVA는 SegCtl CP0 레지스터를 통해 세그먼트 매핑(Mapping)을 재구성하여 커널이 유저 주소 공간의 전체 4GB를 활용할 수 있게 합니다.

MIPS EVA 주소 공간 재구성 MIPS EVA 주소 공간 비교 전통 MIPS32 레이아웃 kseg2 (0xC0000000-0xFFFFFFFF) 커널 TLB 매핑 (1GB) kseg1 (0xA0000000-0xBFFFFFFF) 비캐시 물리 (512MB) kseg0 (0x80000000-0x9FFFFFFF) 캐시 물리 (512MB) useg (0x00000000-0x7FFFFFFF) 유저 TLB 매핑 (2GB) 유저: 2GB / 커널: 2GB (고정) EVA 재구성 레이아웃 커널 전용 세그먼트 (SegCtl 재구성) 0xE0000000-0xFFFFFFFF (512MB) 커널 물리 매핑 (캐시/비캐시) SegCtl로 물리 주소 직접 매핑 커널+유저 오버랩 가능 영역 0x00000000-0xDFFFFFFF 유저: 최대 3.5GB 사용 가능! 커널도 유저 VA 범위에 TLB 매핑 가능 → lbe/sbe 명령어로 유저 접근 구분 EVA EVA 핵심 메커니즘 1. SegCtl0/1/2 (CP0 $5, sel 2/3/4): 6개 세그먼트의 물리 주소 매핑, 캐시 속성, 접근 권한 재구성 2. lbe/lhe/lwe/sbe/she/swe: EVA 전용 로드/스토어 — 커널 모드에서 유저 주소 공간 접근 3. 커널과 유저가 같은 VA를 사용할 수 있어 copy_to_user()에서 TLB 교체 불필요 4. CONFIG_EVA: Malta, Ci40 등 EVA 지원 플랫폼에서 활성화 ⚠ EVA 비활성 시 lbe/sbe는 Reserved Instruction 예외 발생

EVA 전용 로드/스토어 매크로

/* arch/mips/include/asm/eva.h — EVA 유저 접근 매크로 */
/* EVA 활성화 시: 커널 모드에서 유저 VA 접근에 특수 명령어 사용 */

/* 유저 공간 바이트 로드 */
#ifdef CONFIG_EVA
#define user_lw(reg, addr)    "lwe " reg ", " addr  /* EVA: lwe 명령어 */
#define user_sw(reg, addr)    "swe " reg ", " addr  /* EVA: swe 명령어 */
#define user_lb(reg, addr)    "lbe " reg ", " addr
#define user_sb(reg, addr)    "sbe " reg ", " addr
#else
#define user_lw(reg, addr)    "lw " reg ", " addr   /* 일반: lw 명령어 */
#define user_sw(reg, addr)    "sw " reg ", " addr   /* 일반: sw 명령어 */
#define user_lb(reg, addr)    "lb " reg ", " addr
#define user_sb(reg, addr)    "sb " reg ", " addr
#endif

/* copy_from_user에서 사용 예시 */
/* user_lw("$t0", "0($a1)")   → EVA: "lwe $t0, 0($a1)" */
/*                            → 일반: "lw $t0, 0($a1)"  */

/* EVA 장점: 유저 3.5GB 가능 (기존 2GB 한계 극복) */
/* 임베디드 MIPS SoC (Ci40, Malta) 에서 주로 사용 */
EVA와 커널 메모리: EVA 모드에서는 커널도 일부 주소 범위에서 TLB를 통해 매핑해야 하므로, 기존 kseg0의 물리 직접 접근 이점이 줄어듭니다. 그러나 유저 프로세스(Process)에 최대 3.5GB 주소 공간을 제공할 수 있어, 메모리가 큰 임베디드 시스템에서 유리합니다. Linux에서는 CONFIG_EVA 옵션으로 활성화하며, arch/mips/include/asm/mach-malta/spaces.h에서 세그먼트 배치를 정의합니다.

MT (Multi-Threading) / VPE

MIPS MT(Multi-Threading) ASE는 하드웨어 수준의 멀티스레딩을 지원하는 확장입니다. 하나의 물리 코어 내에 여러 개의 TC(Thread Context)와 VPE(Virtual Processing Element)를 배치하여, 각 VPE가 독립적인 프로세서처럼 동작합니다. Intel Hyper-Threading과 유사한 개념이지만, MIPS MT는 더 세밀한 하드웨어 자원 공유 제어를 제공합니다.

MIPS MT 아키텍처: VPE, TC 구조 MIPS MT (Multi-Threading) 아키텍처 물리 코어 (하드웨어 공유 자원: ALU, FPU, Cache, TLB) VPE 0 (Virtual Processing Element) 독립 CP0, 독립 예외 처리, 독립 인터럽트 TC 0 GPR[32] PC, HI/LO TCStatus, TCBind TC 1 GPR[32] PC, HI/LO TCStatus, TCBind VPEControl, VPEConf0/1 Linux → 하나의 CPU로 인식 VPE 1 (Virtual Processing Element) 독립 CP0, 독립 예외 처리, 독립 인터럽트 TC 2 GPR[32] PC, HI/LO TCStatus, TCBind TC 3 GPR[32] PC, HI/LO TCStatus, TCBind VPEControl, VPEConf0/1 Linux → 별도 CPU로 인식 공유 자원: ALU, FPU, I-cache, D-cache, TLB (VTLB 태그에 VPE ID 포함) VSMP 모드 (CONFIG_MIPS_MT_SMP) • 각 VPE = 독립 Linux CPU (SMP) • TC는 VPE에 1:1 바인딩 • 표준 SMP 스케줄러 사용 • 대부분의 현대 MIPS MT 사용 SMTC 모드 (CONFIG_MIPS_MT_SMTC) • 각 TC = 독립 Linux CPU • VPE 내 TC끼리 세밀한 자원 공유 • IPI를 TC 간 소프트웨어 인터럽트로 구현 • 유지보수 중단 (4.6에서 제거)

VPE/TC 구성과 SMTC 스케줄러(Scheduler) 통합

/* arch/mips/kernel/mips-mt.c — MIPS MT 초기화 */
void __init mips_mt_init(void)
{
    unsigned int mvpconf0 = read_c0_mvpconf0();
    unsigned int nvpe = (mvpconf0 & MVPCONF0_PVPE) >> MVPCONF0_PVPE_SHIFT;
    unsigned int ntc  = (mvpconf0 & MVPCONF0_PTC)  >> MVPCONF0_PTC_SHIFT;

    pr_info("MIPS MT: %d VPEs, %d TCs\n", nvpe + 1, ntc + 1);

    /* VSMP 모드: 각 VPE에 하나의 TC 바인딩 */
    /* VPE 0 = Boot CPU, VPE 1+ = Secondary CPUs */
}

/* arch/mips/kernel/smp-mt.c — VSMP 부팅 */
static void vsmp_boot_secondary(int cpu, struct task_struct *idle)
{
    /* DVP: 다른 VPE 비활성화 */
    dvpe();

    /* 대상 TC의 TCRestart에 부팅 함수 주소 설정 */
    write_tc_c0_tcrestart((unsigned long)&smp_bootstrap);

    /* TC 활성화: TCStatus.A = 1 */
    write_tc_c0_tcstatus(
        read_tc_c0_tcstatus() | TCSTATUS_A);

    /* EVP: VPE 실행 재개 */
    evpe(EVPE_ENABLE);
}

/* MT 전용 CP0 레지스터: MVPControl, MVPConf0/1, VPEControl, */
/*   VPEConf0/1, TCStatus, TCBind, TCRestart, TCHalt, TCContext */
/* 명령어: dvpe/evpe (VPE 제어), dmt/emt (MT 제어), fork/yield */
MT와 SMP 토폴로지(Topology): VSMP 모드에서 각 VPE는 Linux SMP의 독립 CPU로 인식됩니다. 예를 들어 MIPS 34Kc는 1코어 2VPE로, Linux에서 NR_CPUS=2로 동작합니다. 74Kf는 2코어 각 1VPE 구조입니다. /proc/cpuinfo에서 VPE 번호를 확인할 수 있으며, 스케줄러는 VPE 간 캐시/TLB 공유를 고려한 토폴로지 정보를 활용합니다.

alternatives 패칭

MIPS Linux 커널은 alternatives 프레임워크를 사용하여 부팅 시 CPU 기능에 따라 명령어를 동적으로 패칭합니다. 예를 들어 R6 CPU에서는 지연 슬롯 없는 compact branch로 교체하고, LL/SC 버그가 있는 R10000에서는 워크어라운드 코드를 삽입합니다. x86의 alternatives와 유사한 개념으로, cpu_has_* 매크로로 CPU 기능을 감지하고 해당하는 최적 코드 경로를 선택합니다.

MIPS alternatives 런타임 패칭 흐름 MIPS alternatives 런타임 패칭 흐름 부팅 cpu_probe() CPU 기능 감지 Config0→1→2→3 체인 파싱 apply_alternatives() .altinstructions 섹션 순회 코드 패칭 memcpy + I$ flush 원본 코드 (.text 661:662) beqz $a0, target # R2: 지연 슬롯 nop # 지연 슬롯 낭비 R6? 대체 코드 (.altinstr_replacement 663:664) beqzc $a0, target # R6: compact branch # nop 제거 → 코드 절감 주요 alternatives 적용 사례 • cpu_has_mips_r6: compact branch, MUL rd,rs,rt, 비정렬 LW/SW • WAR_R10000_LLSC: LL/SC 루프에 SYNC 삽입 • cpu_has_eva: lw→lwe, sw→swe 교체 • cpu_has_msa: FPU→MSA 레지스터 저장

MIPS alternatives 매크로

/* arch/mips/include/asm/alternative.h — alternatives 프레임워크 */
/* 부팅 시 CPU 기능에 따라 명령어 동적 교체 */

#define ALTERNATIVE(old, new, feature) \
    "661:\n"                              \
    old "\n"                              \
    "662:\n"                              \
    ".section .altinstructions, \"a\"\n"  \
    ".word 661b\n"       /* 원본 위치 */   \
    ".word 663f\n"       /* 대체 코드 */   \
    ".hword " __stringify(feature) "\n"  \
    ".hword 662b - 661b\n" /* 원본 길이 */ \
    ".hword 664f - 663f\n" /* 대체 길이 */ \
    ".previous\n"                         \
    ".section .altinstr_replacement, \"ax\"\n" \
    "663:\n" new "\n664:\n"                \
    ".previous\n"

/* 사용 예: R6에서 compact branch 사용 */
static inline void some_function(void)
{
    asm volatile(
        ALTERNATIVE(
            "beqz %0, 1f\n nop",         /* 기본: R2 지연 슬롯 */
            "beqzc %0, 1f",              /* R6: compact, nop 불필요 */
            cpu_has_mips_r6
        )
        : : "r"(val)
    );
}

/* apply_alternatives(): 부팅 시 .altinstructions 섹션 순회 */
/* cpu_has_* 확인 후 원본 코드를 대체 코드로 memcpy + I-cache flush */

cpu_has_* 기능 감지와 런타임 패칭

/* arch/mips/include/asm/cpu-features.h — CPU 기능 매크로 */
/* Config 레지스터 체인 (Config→Config1→Config2→...) 파싱 결과 */

#define cpu_has_mips_r2   (cpu_data[0].isa_level & MIPS_CPU_ISA_M32R2)
#define cpu_has_mips_r6   (cpu_data[0].isa_level & MIPS_CPU_ISA_M32R6)
#define cpu_has_tlb       (cpu_data[0].options & MIPS_CPU_TLB)
#define cpu_has_llsc      (cpu_data[0].options & MIPS_CPU_LLSC)
#define cpu_has_msa       (cpu_data[0].ases & MIPS_ASE_MSA)
#define cpu_has_eva       (cpu_data[0].options & MIPS_CPU_EVA)
#define cpu_has_mt        (cpu_data[0].ases & MIPS_ASE_MIPS_MT)
#define cpu_has_vz        (cpu_data[0].ases & MIPS_ASE_VZ)
#define cpu_has_dsp       (cpu_data[0].ases & MIPS_ASE_DSP)
#define cpu_has_smartmips (cpu_data[0].ases & MIPS_ASE_SMARTMIPS)

/* arch/mips/kernel/cpu-probe.c — 부팅 시 Config 체인 파싱 */
void cpu_probe(void)
{
    unsigned int config0 = read_c0_config();
    unsigned int config1 = read_c0_config1();

    /* Config.AT: 아키텍처 타입 (0=R1, 1=R2, 2=R6) */
    switch (config0 & MIPS_CONF_AT) {
    case 0: c->isa_level |= MIPS_CPU_ISA_M32R1; break;
    case 1: c->isa_level |= MIPS_CPU_ISA_M32R2; break;
    case 2: c->isa_level |= MIPS_CPU_ISA_M32R6; break;
    }

    /* Config1: TLB 엔트리 수, I/D 캐시 설정 */
    if (config1 & MIPS_CONF1_FP) c->options |= MIPS_CPU_FPU;
    if (config1 & MIPS_CONF1_WR) c->options |= MIPS_CPU_WATCH;

    /* Config3: ASE (MT, MSA, EVA, VZ 등) */
    unsigned int config3 = read_c0_config3();
    if (config3 & MIPS_CONF3_MT)  c->ases |= MIPS_ASE_MIPS_MT;
    if (config3 & MIPS_CONF3_MSA) c->ases |= MIPS_ASE_MSA;

    /* alternatives 적용 */
    apply_alternatives();
}
WAR (Workaround) 패칭: MIPS alternatives는 CPU 버그 워크어라운드에도 사용됩니다. 대표적으로 CONFIG_WAR_R10000_LLSC는 R10000의 LL/SC 하드웨어 버그를 우회하기 위해 LL/SC 루프 주변에 추가 배리어 명령어를 삽입합니다. arch/mips/include/asm/war.h에 플랫폼별 워크어라운드가 정의되어 있습니다.

참고 자료

다음 학습: