[컴퓨터구조] Exceptions
Control Flow
CPU는 작동하는 동안 instruction들을 하나씩 읽고 실행하기를 반복하는데, 이를 CPU의 Control flow라고 한다.
jump와 branch를 비롯한 instruction은 control flow를 변화시키는 대표적인 사례이다. 그런데 control flow는 이러한 branching에 의한 것 말고도, 입력이 들어오는 때나 division by zero같은 예외가 발생했을 때, 실행 중인 user program이 OS와 상호작용할 때 등의 경우에 변화해야 한다. 이러한 흐름을 exceptional control flow라고 한다.
Exceptions
Exceptional control flow의 가장 기본적인 메커니즘이 바로 Exception이다. Exception이란, user program 코드를 실행하던 중 OS kernel의 코드로 control을 이동시키는 사건을 말한다. Exception은 user program이 실행되는 도중 division by zero, page fault, 외부 입력 수신 등의 'event'가 발생하면 호출된다.
Exception이 발생하면 control이 OS kernel의 exception handler로 넘어가는데, exception handler는 exception table이라는 구조에 저장되어 있다. 각 handler에는 고유한 exception number k가 붙어 있어서 (interrupt vector라고도 한다), k를 인덱스로 삼아 handler에 접근하는 것이다. Handler의 실행이 끝나면, control은 다시 user program으로 돌아간다.
Exception은 synchronous exception과 asynchronous exception (혹은 interrupt)로 나누어볼 수 있다. 여기서 asynchronous excpetion은 사용자의 keyboard interrupt (Ctrl+C 입력), 네트워크 패킷 도착이나 디스크에서 데이터 읽기 완료 등 프로세서 외부에서의 event 입력이 비동기적으로 들어오는 경우를 말한다. 외부 회로에서 프로세서 칩에 연결된 interrupt pin에 신호를 주면 interrupt를 발생시킬 수 있다. 실제 컴퓨터에서는 프로세서 외부의 timer chip이 몇 ms마다 한 번씩 timer interrupt를 일으키는데, 이는 OS가 user program에서 control을 주기적으로 다시 가져오기 위함이다.
synchronous exception은 user program 실행 도중 자체적으로 발생하는 exception으로, 다음과 같이 분류해볼 수 있다.
Synchronous exceptions
- Traps: system call과 같은 의도적으로 발생한 exception. control이 돌아오면 'next instruction'부터 다시 시작한다.
- Faults: page fault와 같이 적당한 조치를 취한다면 user program을 계속 실행할 수도 있는 exception이다. fault가 발생하면 fault를 발생시킨 'current instruction'을 다시 실행하거나, user program을 abort한다.
- Aborts: illegal instruction과 같이 해결할 수 없는 문제가 있어 반드시 user program을 abort해야 하는 exception이다.
Exception Handlers
Exception을 어떤 방식으로 처리할 수 있는지에 대해 더 알아보자.
RISC-V에서는 handler 프로그램으로 control을 넘기기 전, exception을 일으킨 원인에 대한 정보를 레지스터에 저장한다. Supervisor Exception Program Counter (SEPC)는 exception을 발생시킨 (혹은 interrupt가 발생한 시점에 실행 중이었던) instruction의 PC를 저장한다. 그리고 Supervisor Exception Cause Register (SCAUSE)는 exception의 원인에 따라 exception code를 저장하는 64-bit의 레지스터이다. 예를 들어, undefined opcode는 2, 하드웨어 오작동은 12와 같이 문제 원인에 대해 코드를 부여하는 방식이다. 이렇게 정보를 저장하면 그 다음엔 exception handler로 control을 넘겨 처리가 이루어진다.
이러한 방식 말고도, 다수의 exception handler 프로그램을 준비한 뒤 exception vector에 따라 서로 다른 handler로 control을 주는 처리 방식도 있다.
handler로 control이 넘어오면 handler는 무엇을 할까? 먼저 exception 원인에 따라 알맞은 handler에 배정하고, user program을 다시 실행할 수 있는지 확인한다. 다시 실행할 수 있는 것으로 밝혀지면, SEPC를 이용해 user program으로 돌아간다. 그렇지 않다면 user program을 종료하고 SEPC와 SCAUSE 레지스터의 정보를 이용해 사용자에게 에러를 보고한다.
Exceptions in a Pipeline
이제 기본적인 exception 처리 과정에 대해 알았으니, Pipelined processor에서 exception이 발생한 경우를 생각해 보자.
기본적으로, exception 처리 과정은 mispredicted branch에 대한 처리 과정과 비슷하다. 그래서 exception이 발생했고, restart 가능하다면, 그냥 pipeline을 flush 해주어 처리하면 된다.
그러나 Pipeline에서 여러 inst의 수행이 겹쳐진 구조는 exception 처리를 복잡하게 만든다. 한 cycle 동안 동시에 여러 stage에서 exception이 발생할 수 있기 때문이다. 예를 들어 EX, MEM, WB stage에서 동시에 각각 exception이 발생할 수 있다. 이런 경우에 어떻게 exception을 처리할까?
프로그램에서 Inst는 실행 순서대로 pipeline에 진입하기 때문에, exception이 발생한 inst들 중 pipeline에서 가장 앞서 있는 inst가 faulting instruction일 것이다. 따라서 이 로직에 따라 faulting inst의 위치를 하드웨어적으로 감지한 뒤, 그 뒷 순서의 inst는 모두 flush하여 처리할 수 있다. 이처럼 faulting inst의 위치를 명확히 아는 경우를 Precise exception이라 한다.
반대로, pipeline에서 exception이 발생했을 때 faulting inst의 위치가 주어지지 않는 경우를 Imprecise exception이라고 부른다. 이 경우에는 pipeline 전체를 멈추고 상태들을 저장한 뒤, handler 소프트웨어가 알아서 faulting inst를 찾아 exception 상태를 해결하도록 한다. 이러한 접근 방식은 exception 감지와 해결에 필요한 하드웨어를 줄이지만, 그만큼 handler 소프트웨어가 복잡해진다.
현대의 프로세서는 동시에 여러 instruction을 pipeline에 진입시키기도 하며 (multiple issue), 원래 실행 순서와 다르게 진입시키기도 한다 (out-of-order). 일반적으로 이렇게 복잡한 프로세서에서는 imprecise exception 방식을 적용하기 어렵다.
References
- D. Patterson, J. Hennessy (2021). Computer Organization and Design: RISC-V edition (2nd ed). Morgan Kaufmann.
- Lecture Notes from SNU CSE (김지홍 교수님 강의노트)