트랩: 예외와 인터럽트
사용자 프로그램이 ecall 한 줄을 실행하면 하트는 하던 일을 멈추고 운영체제 코드로 뛰어든다. 타이머가 울릴 때도, 아직 메모리에 없는 페이지를 건드릴 때도 마찬가지다. 이렇게 정상적인 흐름을 끊고 처리기로 제어를 넘기는 일이 트랩이다. 이 장에서는 트랩이 일어나는 순간 CSR이 어떤 순서로 바뀌는지, 어떤 트랩을 누가 받는지(위임), 여러 인터럽트가 한꺼번에 오면 무엇을 먼저 받는지, 그리고 PLIC·AIA 같은 인터럽트 컨트롤러가 장치의 신호를 하트까지 어떻게 전달하는지를 차례로 따라간다.
- mtvec·mepc·mcause·mtval·mscratch가 트랩 처리에서 맡는 역할을 설명한다.
- mcause의 예외·인터럽트 원인 코드를 읽고, 여러 예외가 겹칠 때의 우선순위를 안다.
- 트랩 진입과 MRET/SRET 복귀 때 mstatus의 MIE·MPIE·MPP(와 S 쪽 짝)가 바뀌는 순서를 단계별로 따라간다.
- Direct·Vectored 모드에서 처리기 주소가 어떻게 정해지는지 계산한다.
- medeleg·mideleg 위임과 “전역 인터럽트 활성” 규칙으로 어떤 인터럽트가 어디로 가는지 판정한다.
- mtime·mtimecmp·stimecmp 타이머, PLIC의 claim/complete, AIA의 큰 그림을 이해한다.
트랩: 흐름을 끊고 처리기로
1장에서 본 정의를 다시 떠올리자. 예외(exception)는 지금 실행하는 명령 때문에 생긴 비정상 상황이고, 인터럽트(interrupt)는 명령과 상관없이 바깥에서 비동기적으로 들어온 사건이다. 둘 중 무엇이든 제어가 처리기로 넘어가는 일 자체를 트랩(trap)이라 한다. 17장에서 보았듯 하트가 특권 모드를 바꾸는 정상적인 길은 트랩(올라가기)과 xRET 명령(내려가기)뿐이다.
sepc에 ecall 자신의 주소를 적으므로, 다음 명령으로 돌아가려면 처리기가 직접 4를 더해야 한다.규격은 트랩을 받는 쪽의 모드에 따라 두 가지로 부른다. 특권 수준이 올라가는 트랩(U→S, S→M 등)은 수직 트랩(vertical trap), 같은 수준에 머무는 트랩(S 모드에서 난 예외를 S 모드가 받는 경우 등)은 수평 트랩(horizontal trap)이다. 어느 쪽이든 하드웨어가 하는 일은 같다. 돌아올 주소와 원인을 CSR에 적고, 인터럽트를 끄고, 모드를 바꾸고, 처리기 주소로 뛴다.
정밀한 트랩
트랩이 일어났을 때 “어디까지 실행되었는가”가 분명해야 처리기가 고치고 다시 실행할 수 있다. 트랩 걸린 명령보다 앞선 명령은 모두 끝났고, 그 명령과 뒤따르는 명령은 아무 흔적도 남기지 않은 상태를 정밀한 트랩(precise trap)이라 한다. 비특권 규격은 트랩마다 정밀하게 처리할지를 실행 환경(EEI)이 정하되 가능하면 정밀성을 유지하라고 권고한다. 특권 규격의 PMP 위반은 “항상 정밀하게” 트랩된다고 못 박혀 있고(20장), 반대로 오래된 버스가 돌려주는 오류 응답처럼 정밀하게 잡기 어려운 경우는 부정확한 버스 오류 인터럽트로 보고될 수 있다고 적혀 있다.
| 동기 예외 | 비동기 인터럽트 | |
|---|---|---|
| 원인 | 지금 명령 (잘못된 명령, 페이지 폴트, ecall, ebreak …) | 바깥 사건 (타이머, 장치, 다른 하트의 신호, 카운터 넘침) |
| xepc | 예외를 일으킨 명령의 주소 | 끼어든 시점에 아직 실행하지 않은 명령의 주소 |
| xcause 최상위 비트 | 0 | 1 |
| 재현성 | 같은 입력이면 같은 곳에서 다시 생긴다 | 언제 올지 모른다 |
| 끌 수 있나 | 끌 수 없다 (위임으로 받을 곳만 고른다) | mie 비트와 전역 활성 비트로 가린다 |
트랩을 받아 적는 CSR들
M 모드와 S 모드는 트랩을 받을 수 있는 두 수준이다. 각자 똑같은 모양의 CSR 한 벌을 가진다. 이름의 첫 글자 m/s만 다르고 역할은 짝을 이룬다. 처리기 주소(xtvec), 돌아갈 주소(xepc), 원인(xcause), 부가 정보(xtval), 처리기용 임시 칸(xscratch)이다.
| 역할 | M 모드 CSR | S 모드 CSR | 하는 일 |
|---|---|---|---|
| 상태 | mstatus 0x300 | sstatus 0x100 | 전역 인터럽트 활성(xIE)과 트랩 직전 상태(xPIE·xPP) |
| 위임 | medeleg 0x302mideleg 0x303 | — | 예외·인터럽트를 S 모드로 넘길지 정한다 |
| 인터럽트 활성 | mie 0x304 | sie 0x104 | 인터럽트별 켜기·끄기 |
| 처리기 주소 | mtvec 0x305 | stvec 0x105 | BASE와 MODE(Direct/Vectored) |
| 임시 칸 | mscratch 0x340 | sscratch 0x140 | 처리기가 첫 레지스터를 얻는 데 쓴다 |
| 돌아갈 주소 | mepc 0x341 | sepc 0x141 | 트랩 걸린(끼어든) 명령의 가상 주소 |
| 원인 | mcause 0x342 | scause 0x142 | Interrupt 비트 + 원인 코드 |
| 부가 정보 | mtval 0x343 | stval 0x143 | 잘못된 주소, 명령 비트, 또는 0 |
| 인터럽트 펜딩 | mip 0x344 | sip 0x144 | 지금 기다리는 인터럽트 |
bltz 한 번으로 둘을 구별할 수 있다. S 모드의 stvec·scause·sepc·stval도 모양이 같다(폭만 SXLEN).몇 가지 세부 규칙이 있다.
- mepc는 WARL이며 유효한 가상 주소를 모두 담을 수 있어야 한다. C 확장이 없어 IALIGN=32이면 하위 두 비트가 0으로 읽힌다. 트랩이 M 모드로 들어올 때만 하드웨어가 쓰고, 그 밖에는 소프트웨어만 쓴다.
- mcause의 원인 코드 필드는 WLRL이다. 구현이 지원하는 코드만 담을 수 있다고 보장된다. scause는 0–31(하위 5비트)을 반드시 담을 수 있어야 한다.
- mtval은 0이 될 수도 있다. 플랫폼이 어떤 예외에서 반드시 정보를 남길지 정한다. 0이 아니라면 주소 관련 예외에서는 잘못된 가상 주소, 잘못된 명령 예외에서는 명령 비트(오른쪽 정렬)를 담는다.
mscratch: 처리기의 첫 레지스터
처리기에 막 들어온 순간에는 문제가 하나 있다. 모든 정수 레지스터에 중단된 프로그램의 값이 들어 있어서, 무엇이든 하려면 레지스터 하나를 비워야 하는데 비울 곳을 계산할 레지스터조차 없다. RISC-V는 xscratch와 CSR 교환 명령으로 이 문제를 푼다. 평소 mscratch에 하트별 저장 공간의 주소를 넣어 두고, 처리기 첫 명령에서 sp와 맞바꾸는 것이다.
MIPS는 운영체제 전용 사용자 레지스터 두 개(k0/k1)를 따로 떼어 이 문제를 풀었다. 규격의 비고는 그 방식이 사용자 레지스터를 줄이고, 특권 수준이 늘거나 트랩이 중첩되면 확장되지 않으며, 사용자로 돌아가기 전에 지워야 하는 보안 부담까지 있다고 지적한다. RISC-V는 사용자 ISA를 운영체제 사정으로 “오염”시키지 않으려고 CSR 교환으로 같은 효과를 낸다.
원인 코드: mcause가 말해 주는 것
mcause에 적히는 값은 Interrupt 비트와 원인 코드의 조합이다. RV32에서 머신 타이머 인터럽트는 0x80000007, U 모드의 ecall은 0x00000008로 읽힌다. 아래 표는 v20260120 규격의 표를 그대로 옮긴 것이다. 비어 있는 번호는 예약(Reserved)이다.
| 코드 | 예외 (Interrupt = 0) | xtval에 남는 값 (0이 아니라면) |
|---|---|---|
| 0 | Instruction address misaligned (명령 주소 정렬 오류) | 잘못된 가상 주소 |
| 1 | Instruction access fault (명령 접근 폴트) | 잘못된 가상 주소 |
| 2 | Illegal instruction (잘못된 명령) | 명령 비트 (선택) |
| 3 | Breakpoint (중단점) | 주소 중단점은 그 주소, ebreak는 0 또는 명령 주소 |
| 4 | Load address misaligned (적재 주소 정렬 오류) | 잘못된 가상 주소 |
| 5 | Load access fault (적재 접근 폴트) | 잘못된 가상 주소 |
| 6 | Store/AMO address misaligned | 잘못된 가상 주소 |
| 7 | Store/AMO access fault | 잘못된 가상 주소 |
| 8 | Environment call from U-mode | 0 |
| 9 | Environment call from S-mode | 0 |
| 11 | Environment call from M-mode | 0 |
| 12 | Instruction page fault (명령 페이지 폴트) | 잘못된 가상 주소 |
| 13 | Load page fault (적재 페이지 폴트) | 잘못된 가상 주소 |
| 15 | Store/AMO page fault | 잘못된 가상 주소 |
| 16 | Double trap (이중 트랩, Ssdbltrp) | 원래 원인은 mtval2에 |
| 18 | Software check (소프트웨어 검사, CFI 등) | 0 · 2(착지 패드) · 3(그림자 스택) |
| 19 | Hardware error (하드웨어 오류) | 잘못된 가상 주소 |
| 24–31, 48–63 | 사용자 정의(custom) 용도 | — |
| 코드 | 인터럽트 (Interrupt = 1) | 보통의 출처 |
|---|---|---|
| 1 | Supervisor software interrupt (SSI) | 다른 하트가 보내는 S 수준 신호, 또는 M 모드 소프트웨어 |
| 3 | Machine software interrupt (MSI) | 메모리 매핑된 msip 레지스터 (하트 간 인터럽트) |
| 5 | Supervisor timer interrupt (STI) | stimecmp(Sstc) 또는 M 모드 소프트웨어가 STIP를 세움 |
| 7 | Machine timer interrupt (MTI) | mtime ≥ mtimecmp |
| 9 | Supervisor external interrupt (SEI) | PLIC·APLIC·IMSIC 같은 외부 인터럽트 컨트롤러 |
| 11 | Machine external interrupt (MEI) | 외부 인터럽트 컨트롤러 |
| 13 | Counter-overflow interrupt (LCOFI) | Sscofpmf 성능 카운터 넘침 |
| ≥ 16 | 플랫폼 용도 | 플랫폼이 정한다 |
같은 ecall이 실행한 모드에 따라 8·9·11로 다르게 보고된다. 규격의 비고에 따르면 그래야 “U 모드의 ecall은 S로 위임하고, S 모드의 ecall은 M이 받는” 식의 선택적 위임이 가능하다. 유닉스 계열 운영체제의 전형적인 설정이 바로 이것이다. 10번은 예약되어 있다.
예외가 겹칠 때: 동기 예외 우선순위
명령 하나가 여러 예외 조건을 동시에 만족할 수 있다. 예를 들어 정렬도 어긋나고 페이지도 없는 적재가 그렇다. 이때 mcause에 보고할 예외는 규격의 고정된 우선순위로 고른다.
트랩 진입과 xRET 복귀, 한 단계씩
트랩을 받을 모드를 x(M 또는 S), 트랩 직전의 모드를 y라 하자. 규격이 정한 하드웨어 동작은 짧다.
| 트랩 진입 (y → x) | xRET 복귀 (xPP에 y가 들어 있을 때) |
|---|---|
xepc ← 트랩 걸린(끼어든) 명령의 주소 | pc ← xepc |
xcause ← Interrupt 비트 + 원인 코드 | xIE ← xPIE |
xtval ← 부가 정보 또는 0 | 모드 ← y (xPP 값) |
xPIE ← xIE, 그다음 xIE ← 0 | xPIE ← 1 |
xPP ← y, 모드 ← x | xPP ← 구현된 가장 낮은 모드 (U가 있으면 U) |
pc ← xtvec가 정한 주소 | y ≠ M이면 mstatus.MPRV ← 0 |
xPP는 x 이하의 모드만 담을 수 있다. 그래서 MPP는 2비트(U=00, S=01, M=11), SPP는 1비트(U=0, S=1)다. xIE·xPIE·xPP는 깊이 2짜리 작은 스택처럼 동작한다. 트랩으로 “밀어 넣고” xRET으로 “꺼낸다”. 복귀 뒤 xPP를 가장 낮은 모드로 바꿔 두는 것은 스택을 잘못 관리하는 소프트웨어 버그를 드러내기 위해서다.
아래 실행기에서 여러 상황을 골라 한 단계씩 진행해 보자. 단계마다 바뀌는 CSR이 금색으로 표시된다. 값은 RV32 기준이며 mtvec = 0x80000100, stvec = 0x80200800(둘 다 Direct 모드)로 두었다.
트랩 단계별 실행기
하드웨어는 ecall·ebreak의 xepc에 그 명령 자신의 주소를 적는다. 그대로 xRET하면 같은 ecall을 무한히 다시 실행한다. 시스템 호출 처리기는 xepc에 4를 더해야 하고(압축 명령 c.ebreak라면 2), 페이지 폴트 처리기는 반대로 xepc를 그대로 두어 같은 명령을 다시 실행시킨다. 또 A 확장이 있으면 xRET이 LR 예약을 지워도 되고 안 지워도 되므로, 필요하면 처리기가 가짜 SC로 예약을 지운다.
처리기 주소: Direct와 Vectored
mtvec의 하위 두 비트 MODE가 처리기 주소를 정하는 방식을 고른다. MODE=0(Direct)이면 모든 트랩이 BASE 한 곳으로 간다. MODE=1(Vectored)이면 동기 예외는 여전히 BASE로 가지만, 인터럽트는 BASE + 4 × 원인 코드로 간다. 예를 들어 머신 타이머 인터럽트(7)는 BASE + 0x1c, stvec에서 S 타이머 인터럽트(5)는 BASE + 0x14다. MODE 값 2 이상은 예약되어 있다.
j)가 들어갈 자리다. 원인 코드가 예약된 칸(+0x08, +0x10 …)은 그림에서 생략했다. +0x00 칸은 모든 동기 예외가 함께 쓴다.mtvec 점프 표 계산기
…02처럼 4의 배수가 아니게 쓰면 하위 두 비트가 버려진다.BASE는 언제나 4바이트 정렬이지만, 규격은 Vectored 모드에서 더 엄격한 정렬(예: 64바이트·256바이트)을 요구해도 된다고 허락한다. 덧셈기 없이 비트를 이어 붙이기만 해서 주소를 만들 수 있게 하려는 배려다. mtvec은 WARL이므로, 원하는 값을 쓴 뒤 다시 읽어 실제로 받아들여졌는지 확인하는 것이 안전하다. 리셋 벡터와 NMI 벡터 주소는 플랫폼 규격이 정한다.
위임: 트랩을 S 모드로 바로 보내기
기본적으로 모든 트랩은 M 모드가 받는다. 그러나 사용자 프로그램의 시스템 호출이나 페이지 폴트까지 매번 M 모드 펌웨어를 거쳐 커널로 되돌려 보내면 느리다. 그래서 S 모드가 있는 하트에는 위임 레지스터(medeleg, mideleg)가 반드시 있다. medeleg의 비트 i를 켜면 원인 i인 예외가, mideleg의 비트 i를 켜면 원인 i인 인터럽트가 S 모드나 U 모드에서 일어났을 때 S 모드 처리기로 바로 간다.
“Traps never transition from a more-privileged mode to a less-privileged mode. For example, if M-mode has delegated illegal-instruction exceptions to S-mode, and M-mode software later executes an illegal instruction, the trap is taken in M-mode, rather than being delegated to S-mode.”
Volume II, Machine-Level ISA — Machine Trap Delegation Registers — 위임은 “낮은 모드에서 난 트랩을 어디서 받을지”만 정한다.
위임된 트랩이 S 모드로 들어오면 S 쪽 CSR만 바뀐다. scause·sepc·stval이 쓰이고, mstatus의 SPP·SPIE·SIE가 갱신된다. mcause·mepc·mtval과 MPP·MPIE는 건드리지 않는다. 그 밖의 규칙을 정리하면 다음과 같다.
medeleg는 64비트다(RV32에서는 상위 절반을medelegh0x312로 접근).mideleg는 MXLEN 비트이고 비트 배치가 mip와 같다(STI 위임은 비트 5).- M 모드에서만 생길 수 있는 예외의 비트는 읽기 전용 0이어야 한다. 특히
medeleg[11](M 모드 ecall)은 읽기 전용 0이고, 이중 트랩은 위임할 수 없으므로medeleg[16]도 읽기 전용 0이다. - 어떤 비트를 위임할 수 있는지는 구현마다 다르다. 모든 비트에 1을 써 보고 다시 읽으면 알 수 있다. 다만 medeleg에는 읽기 전용 1인 비트가 있으면 안 된다.
- 인터럽트를 위임하면 위임한 쪽(M 모드)에서는 그 인터럽트가 가려진다. 예를 들어 mideleg[5]가 켜져 있으면 M 모드로 실행 중일 때 STI는 받지 않는다.
H 확장이 있으면 HS 모드가 받은 트랩을 다시 VS 모드로 넘기는 hedeleg·hideleg가 더해진다. 같은 원리가 한 층 더 쌓이는 것이므로 21장에서 다룬다.
인터럽트: 펜딩, 활성, 그리고 우선순위
인터럽트 i는 세 겹의 문을 통과해야 트랩이 된다. ① 펜딩(mip[i]=1), ② 개별 활성(mie[i]=1), ③ 전역 활성이다. mip와 mie의 비트 번호는 mcause의 인터럽트 원인 코드와 같다. 비트 15:0은 표준 인터럽트에, 16 이상은 플랫폼 용도에 쓴다.
mideleg로 위임된 비트만 보이고 나머지는 읽기 전용 0이다.전역 활성 규칙
전역 활성 비트는 mstatus의 MIE(M 수준)와 SIE(S 수준)다. 규칙의 핵심은 “지금 모드”를 기준으로 판단한다는 점이다.
| 지금 모드 ↓ / 인터럽트의 목적지 → | M으로 갈 인터럽트 (mideleg[i]=0) | S로 갈 인터럽트 (mideleg[i]=1) |
|---|---|---|
| M | MIE=1일 때만 | 가려짐 (받지 않음) |
| S | 항상 켜짐 (MIE 무시) | SIE=1일 때만 |
| U | 항상 켜짐 (MIE 무시) | 항상 켜짐 (SIE 무시) |
규격의 문장 그대로 옮기면 “모드 x에서 실행 중일 때 더 낮은 모드의 인터럽트는 항상 전역적으로 꺼져 있고, 더 높은 모드의 인터럽트는 항상 전역적으로 켜져 있다.” 그래서 높은 모드가 낮은 모드에 제어를 넘기기 전에 특정 인터럽트를 막고 싶다면 전역 비트가 아니라 mie의 개별 비트를 꺼야 한다. 여러 인터럽트가 동시에 들어오면 M으로 갈 인터럽트가 S로 갈 인터럽트보다 먼저이고, 같은 목적지 안에서는 고정된 순서를 따른다.
| 목적지 | 우선순위 (높음 → 낮음) |
|---|---|
| M 모드 | MEI → MSI → MTI → SEI → SSI → STI → LCOFI |
| S 모드 | SEI → SSI → STI → LCOFI |
규격의 비고는 이 순서의 이유를 이렇게 설명한다. 높은 모드의 인터럽트가 먼저여야 선점이 가능하다. 외부 인터럽트는 빠른 응답이 필요한 장치에서 오므로 내부(타이머·소프트웨어)보다 먼저다. 소프트웨어 인터럽트는 하트 간 메시지에 쓰이는 반면 타이머는 시분할용이라 정밀도가 덜 중요하므로 소프트웨어가 타이머보다 먼저다.
인터럽트 중재기
| 인터럽트 (원인) | mip | mie | mideleg | 판정 |
|---|
타이머: mtime, mtimecmp, 그리고 stimecmp
플랫폼은 일정한 주파수로 증가하는 64비트 실시간 카운터 mtime과 하트별 64비트 비교 레지스터 mtimecmp를 메모리 매핑 레지스터로 제공한다. 주소는 플랫폼이 정한다(흔히 CLINT·ACLINT라고 부르는 장치가 이 레지스터들과 하트 간 인터럽트용 msip 레지스터를 담는다, 22장). 부호 없는 값으로 비교해 mtime ≥ mtimecmp가 되면 MTIP가 켜지고, mtimecmp에 더 큰 값을 쓸 때까지 유지된다. 사이클 카운터 대신 벽시계 시간을 쓰는 이유는, 전력 절약을 위해 클록 주파수가 수시로 바뀌는 프로세서에서도 시간이 정확해야 하기 때문이다.
Sstc 확장이 생기기 전에는 S 모드 운영체제가 타이머를 맞출 때마다 SBI 호출로 M 모드에 부탁해야 했다. M 모드는 자기 타이머 하나에 여러 논리 타이머를 겹쳐 관리하다가 때가 되면 STIP를 세워 S로 넘겼다. Sstc는 S 모드에 stimecmp(RV32에서는 상위 절반 stimecmph 0x15D)를 주어 이 왕복을 없앤다. time ≥ stimecmp이면 STIP가 켜지며, M 모드가 menvcfg.STCE를 켜 두어야 S 모드에서 stimecmp에 접근할 수 있다.
RV32에서는 mtimecmp를 32비트씩 두 번에 나눠 써야 한다. 규격은 중간값 때문에 엉뚱한 인터럽트가 나지 않도록 다음 순서를 예로 든다. 하위 절반에 먼저 −1(모두 1)을 써서 비교값이 잠시라도 작아지지 않게 한다.
WFI: 인터럽트를 기다리며 쉬기
wfi는 “처리할 인터럽트가 생길 때까지 이 하트를 멈춰도 된다”는 힌트다. 기다리는 동안 인터럽트가 오면 다음 명령에서 트랩이 일어나므로 mepc는 wfi 다음 명령을 가리킨다. 몇 가지 특징이 있다.
- WFI의 깨어남은 전역 활성 비트(MIE·SIE)와 mideleg의 영향을 받지 않는다. 개별 활성(mie)된 인터럽트가 펜딩되면 어느 수준의 것이든 깨어나야 한다. 그래서 인터럽트를 전역으로 꺼 둔 채 WFI로 기다렸다가, 트랩 없이 이어서 처리하는 방식도 가능하다.
- 구현은 아무 이유 없이 깨어나도 되고, 아예 NOP로 만들어도 된다. 따라서 소프트웨어는 WFI 뒤에서 조건을 다시 확인하고 반복해야 한다.
- mstatus.TW=1이면 M보다 낮은 모드의 WFI가 정해진 시간 안에 끝나지 않을 때 잘못된 명령 예외가 된다. 하이퍼바이저는 이것으로 놀고 있는 게스트를 다른 게스트로 바꾼다. S 모드가 있으면 U 모드의 WFI도 같은 조건에서 예외가 된다.
인터럽트 컨트롤러: PLIC와 AIA
mip에는 외부 인터럽트를 위한 비트가 MEIP·SEIP 둘뿐이다. 수십, 수백 개의 장치 인터럽트를 이 두 선으로 모아 주는 것이 하트 바깥의 인터럽트 컨트롤러다. 규격의 비고도 “하트의 몇 안 되는 원천 인터럽트는 고정 우선순위로 단순하게 두고, 복잡한 우선순위는 바깥의 컨트롤러가 맡는다”고 설명한다.
PLIC: 플랫폼 수준 인터럽트 컨트롤러
PLIC(Platform-Level Interrupt Controller)는 최대 1023개의 인터럽트 소스(ID 0은 “없음”으로 예약)를 최대 15872개의 문맥(context)으로 나눠 보낸다. 문맥은 대개 “하트 하나의 특권 모드 하나”이며, 어느 문맥이 어느 하트·모드인지는 구현이 문서로 정한다.
규칙은 간단하다.
- 우선순위는 작은 부호 없는 정수다. 0은 “절대 인터럽트하지 않음”, 1이 가장 낮다. 같은 우선순위끼리는 ID가 작은 쪽이 이긴다.
- 임계값: 문맥은 임계값 이하의 우선순위를 가진 인터럽트를 모두 가린다. 임계값 0이면 우선순위가 0이 아닌 인터럽트를 모두 받는다.
- claim: 처리기가 claim/complete 레지스터를 읽으면 PLIC은 그 문맥에서 가장 높은 우선순위의 펜딩 인터럽트 ID를 돌려주고 펜딩 비트를 원자적으로 지운다. 없으면 0을 돌려준다. claim은 임계값의 영향을 받지 않는다.
- complete: 처리가 끝나면 받은 ID를 같은 레지스터에 쓴다. 게이트웨이는 completion을 받은 뒤에야 그 소스의 다음 인터럽트를 코어로 넘긴다.
| 오프셋 (base +) | 내용 |
|---|---|
0x000004 – 0x000FFC | 소스 1–1023의 우선순위 (소스마다 32비트, 0x000000은 예약) |
0x001000 – 0x00107C | 펜딩 비트 배열 (ID N은 워드 N/32의 비트 N mod 32) |
0x002000 + 0x80 × 문맥 | 문맥별 활성 비트 배열 |
0x200000 + 0x1000 × 문맥 | 문맥별 우선순위 임계값 |
0x200004 + 0x1000 × 문맥 | 문맥별 claim(읽기) / complete(쓰기) |
PLIC 따라가기: 알림 → claim → 처리 → complete
| 소스 | 우선순위 | 문맥 0 활성 | 문맥 1 활성 | 장치 |
|---|
AIA: 메시지 신호 인터럽트 시대의 구조
PCI Express 같은 장치는 선을 당기는 대신 특정 주소에 메모리 쓰기를 해서 인터럽트를 알린다. 이것이 메시지 신호 인터럽트(MSI)다. AIA(Advanced Interrupt Architecture) 규격은 두 가지 컨트롤러와 하트 쪽 CSR 확장(Smaia·Ssaia)을 정의한다.
AIA 규격은 최대 16,384개 하트를 지원하고, 하트 쪽 CSR(miselect/mireg를 통한 간접 접근, mtopi 등)을 더해 타이머·소프트웨어 인터럽트까지 포함한 모든 인터럽트의 상대 우선순위를 소프트웨어가 정할 수 있게 한다. 자세한 구성은 22장에서 플랫폼 관점으로 다시 본다.
NMI와 이중 트랩: 처리기가 위험할 때
트랩 처리기의 앞부분, 즉 xepc·xcause 같은 중요한 값을 아직 저장하지 못한 구간에서 또 트랩이 나면 그 값들이 덮어써져 원래 트랩으로 돌아갈 길을 잃는다. 그래서 규격은 처리기가 이 구간에서는 인터럽트를 켜지도 예외를 일으키지도 않도록 짜야 한다고 요구한다. 그런데 하드웨어 오류처럼 피할 수 없는 사건은 어떻게 할까?
하드웨어 오류용 마스크 불가 인터럽트. 구현이 정한 벡터로 M 모드에서 바로 뛰며 mepc·mcause를 덮어쓴다. 그래서 M 모드 처리기 도중에 오면 되돌아갈 수 없다.
mnscratch 0x740, mnepc 0x741, mncause 0x742, mnstatus 0x744를 더해 NMI가 자기만의 칸에 상태를 적는다. mnret으로 돌아가며, mnstatus.NMIE가 0인 동안은 모든 인터럽트가 가려진다.
mstatus.MDT 비트. M 모드로 트랩이 들어올 때 MDT가 0이면 1로 만들고, 이미 1이면 “예기치 않은 트랩”이다. MRET·SRET이 MDT를 0으로 되돌린다.
sstatus.SDT 비트(menvcfg.DTE로 켬). SDT=1인 채로 S로 트랩이 들어오면 M 모드에 원인 16(double trap)으로 보고하고, 원래 원인은 mtval2에 담는다.
핵심 정리
- 트랩은 예외(동기)나 인터럽트(비동기)로 처리기에 제어가 넘어가는 일이다. 특권 모드를 올리는 유일한 정상 경로이며, xRET이 그 반대 길이다.
- 트랩 진입 시 하드웨어는 xepc·xcause·xtval을 쓰고, xPIE ← xIE, xIE ← 0, xPP ← 이전 모드, 모드 ← x, pc ← xtvec을 수행한다. xRET은 그 반대로 꺼내고 xPP를 가장 낮은 모드로 바꾼다.
- mcause의 최상위 비트가 인터럽트 여부다. 예외 코드는 0–9, 11, 12, 13, 15, 16, 18, 19가 정의되어 있고, 인터럽트는 1·3·5·7·9·11·13이다.
- Vectored 모드에서 인터럽트는 BASE + 4 × 원인으로, 예외는 언제나 BASE로 간다.
- medeleg·mideleg는 S·U 모드에서 난 트랩을 S 모드로 바로 보낸다. M 모드에서 난 트랩은 위임과 상관없이 M이 받는다.
- 더 높은 모드로 갈 인터럽트는 지금 모드에서 항상 전역적으로 켜져 있고, 더 낮은 모드로 갈 인터럽트는 항상 꺼져 있다. 동시 발생 시 M 목적지가 먼저이고, M 안에서는 MEI·MSI·MTI·SEI·SSI·STI·LCOFI 순서다.
- MTIP는
mtime ≥ mtimecmp로, Sstc의 STIP는time ≥ stimecmp로 켜진다. PLIC은 우선순위·임계값·claim/complete로 외부 인터럽트를 문맥에 나눠 주고, AIA는 APLIC과 IMSIC으로 MSI까지 다룬다.
확인 퀴즈
U 모드에서 주소 0x10148의 ecall이 S 모드로 위임되었다. 트랩 직후 sepc 값은?
RV32에서 mcause를 읽었더니 0x80000007이었다. 무슨 트랩인가?
mtvec = 0x80000001(BASE 0x80000000, Vectored)일 때 머신 외부 인터럽트의 처리기 주소는?
medeleg[2]=1(잘못된 명령 위임)인 상태에서 M 모드 코드가 잘못된 명령을 실행했다. 트랩은 어디로 가는가?
U 모드에서 실행 중, mstatus.MIE=0, mip.MTIP=1, mie.MTIE=1이다. 어떻게 되는가?
S 모드로 트랩이 들어갈 때 sstatus의 SIE·SPIE는 어떻게 바뀌는가?
MEI와 MTI가 동시에 펜딩·활성이고 M 모드에서 MIE=1이다. 먼저 받는 것은?
PLIC 문맥의 임계값이 3이고, 우선순위 3인 소스가 펜딩·활성 상태다. 이 문맥은?
더 읽을거리
- Volume II: Machine-Level ISA, Version 1.13 — mstatus 스택, mtvec, 위임, mip/mie, mcause 표, 예외 우선순위, mtime·mtimecmp, WFI, NMI (v20260120)
- Volume II: Supervisor-Level ISA, Version 1.13 — sstatus, stvec, sip/sie, scause, sepc, stval, stimecmp
- "Sstc" Extension for Supervisor-mode Timer Interrupts, Version 1.0
- "Smrnmi" Extension for Resumable Non-Maskable Interrupts, Version 1.0
- "Smdbltrp" · "Ssdbltrp" Double Trap Extensions, Version 1.0
- RISC-V Platform-Level Interrupt Controller Specification v1.0.0
- RISC-V Advanced Interrupt Architecture v1.0 — APLIC, IMSIC, Smaia/Ssaia
- Volume I: Introduction — Exceptions, Traps, and Interrupts — 트랩의 네 분류와 정밀성