본문 바로가기

System Programming/CSAPP Book

Ch 8-1. Control Flow, Exception Handling, Exception의 종류

Exceptional Control Flow (ECF)

프로세서가 돌아가고 있는 동안, PC(=Program Counter)는 항상 메모리에 저장되어 있는 명령어를 가리키고 있다. (가리키고 있다는 것은 해당 명령어가 저장되어 있는 주소 값을 가지고 있다는 의미이다.) 하나의 명령어가 실행되면 다음 명령어를 실행하기 위해 PC 값이 다음 주소를 가리킬텐데, 이러한 transition을 'control transfer'이라고 한다.

 

Control transfer은 연이은(=adjacent) 메모리 주소의 sequence로 이루어지기도 하지만, jump, call, return 명령어와 같이 not adjacent한 메모리 주소로 transition 하기도 한다. 이러한 transfer은 모두 프로그램 내에서 이루어지는 것인데, computer system은 system state에 대해서도 적절히 반응해야 한다. 여기서 말하는 system state은 앞으로 포스팅에서 더 구체적으로 다루겠지만, 예를 들자면 hardware timer, network로부터 들어온 packet 데이터 등이 포함된다. 이러한 것들은 하나의 프로그램의 실행과는 별개로 일어나는 일들이다. 이렇듯 computer system level에서 이루어지는 abrupt change들을 'exceptional control flow(ECF)'라고 한다.

 

CSAPP 교재의 Ch 8에서는 ECF를 다루는데, 이를 통해 computer system과 관련된 중요한 concept를 이해할 수 있다.

 

Exceptions

(1) Process state & Event

Exception은 프로세서의 state에 대해서 적절히 반응하기 위한 일련의 control flow를 말하고, 위에서 말한 exceptional control flow에 해당한다. 이는 일부는 hardware에 의해, 또 일부는 operating system에 의해 구현된다. 프로세서의 state은 프로세서 내의 여러 개의 bit, signal들로 이루어져 있는데, 이러한 state에 뭔가 변화가 생겼을 때 exception이 발생한다.

 

프로세서 state의 변화를 'event'라고 하는데, event의 예로는 virtual memory page fault, arithmetic overflow, divide by zero 등과 같이 명령어와 관련있는 것들도 있고, system timer, I/O와 같이 명령어와 무관하게 일어나는 것들도 있다.

 

프로세서는 event의 여부를 항상 확인하고 있으며 (bit/signal로 encoding되어 있으므로 이것을 검사하는 것에 해당한다), 만약 event가 일어났다면 exception table을 참조하여 적절한 operating system subroutine(=exception handler)으로 control를 넘겨준다. OS가 exception에 대한 처리를 완료한 뒤에, 다시 프로그램으로 돌아와 실행을 이어가게 된다.

 

(2) Exception handling: hardware의 관점

system에서 발생할 수 있는 모든 exception은 unique nonnegative integer인 exception number가 할당되어 있다. 위에서 언급한 virtual memory page fault, ... 과 같은 명령어에 의한/무관한 모든 exception에 대해서 그러하다.

 

이러한 모든 exception을 처리할 수 있는 handler 코드가 존재하며, exception table은 각각의 exception에 대한 handler 코드의 시작 주소를 가리킨다. exception table은 system boot time에 초기화된다.

 

이제 runtime에 프로세서는 exception bit들을 통해서 어떤 exception이 일어났는지를 detect하고, 이를 통해 상황에 맞는 exception number를 판단한다. exception number를 offset으로 하여 exception table을 참조해 exception handler 코드 시작 주소를 얻는다. 이 때, exception table의 base address는 exception table base register에 저장되어 있다. base address와 exception number로부터 얻은 offset을 더하여 참조할 exception table의 주소를 얻는 것이다.

 

여기까지가 hardware인 프로세서가 담당하는 역할인데, hardware는 event를 detect한 후 적절한 주소로 control을 넘겨준다. 책에서는 이를 'hardware triggers the exception'이라고 표현한다. 이제 그 뒤에 exception에 대한 처리는 exception handler라는 software에 의해 이루어지게 된다.

 

(3) Exception의 종류

첫째는 interrupt이다. Interrupt는 프로세서 외부에 존재하는 I/O device로부터 들어온 신호에 의해 asynchronous하게 발생한다. synch/asynch의 기준은 프로세서 명령어의 동작이라고 볼 수 있다. I/O device는 프로세서 내부 동작과 무관하게 발생하는 것이므로 asynch하다. I/O device의 예로는 network, disk controller, timer chip 등이 있다. 이에 대해 프로세서는 우선 현재 실행 중인 명령어를 실행 완료한 뒤, interrupt pin 신호를 detect한다. exception number를 통해 exception handler 주소를 얻을 수 있을 것이고, control를 넘겨준다. exception handling이 종료된다면, 기존에 실행하던 instruction sequence 상의 다음 instruction을 이어서 실행한다. 프로그램의 입장에서는 interrupt가 일어나지 않은 것처럼 실행을 이어갈 수 있다. 참고로, exception과 interrupt 용어에 관해 구분하자면, exception이 보다 포괄적인 의미로 쓰인다. interrupt는 I/O device와 같이 asynch.한 hardware-related exception이라는 의미를 갖는다.

 

둘째는 trap이다. 이는 의도적인(intentional) exception인데, 사용되는 이유는 user-level 프로그램과 kernel 간에 procedure-like interface를 제공하기 위함이다. 이는 흔히 system call이라고 부른다. interrupt와 마찬가지로, exception handler 코드가 실행된 뒤에는 다시 기존 instruction sequence 상의 다음 instruction을 이어서 실행하게 된다.

 

system call의 예시로는 파일 읽기, 새로운 process 만들기, program load, 현재 실행 중인 process 종료하기 등이 있다. 이러한 함수들은 programmer의 관점에서는 일반적인 함수들과 거의 동일하나, 이들은 user mode가 아닌 kernel mode에서 돌아간다는 차이가 있다. user mode에 비해 kernel mode에서 돌아가면 computer system resource에 대한 접근 권한이 다르다.

 

system call을 이용한 프로그램의 예시를 하나 살펴보자. 아래는 prinf를 이용한 hello 프로그램이다.

#include <stdio.h>
int main(){
	printf("hello world!\n");
    return 0;
}

이제 이를 system call를 이용해서 다음과 같이 실행하여도 동일한 결과를 얻는다.

int main() {
	write(1, "hello world!\n", 13);
	_exit(0);
}

write 함수의 경우, 1번째 arg는 stdout으로 출력 결과를 전송한다는 것을, 2번째 arg는 write하고 싶은 byte sequence를, 3번째 arg는 byte 수를 나타낸다. C standard library stdio.h 의 printf 함수보다 사용하기에 덜 직관적인데, C standard library가 이러한 system call에 대한 보다 편리한 버전의 wrapper function을 제공한다고 볼 수 있다.

 

셋째는 fault이다. 이는 어떠한 error condition을 만족시켰을 때 일어나는데, 우선 프로그램의 control이 fault handler로 넘어간다. 여기서 error condition이 해결될 수 있는지의 여부를 판단한다. 예를 들어, page fault exception의 경우 해결될 수 있는 fault이므로, disk에서 요청된 page를 가져온 뒤에 실행을 이어갈 수 있다. 반면에 해결할 수 없는 error의 경우 아래 설명할 넷째의 abort로 넘어간다.

 

넷째는 abort이다. 이것은 회복 불가능한 fatal error일 때 발생하고, 일반적으로 메모리 상의 parity error와 같이 hardware 상의 문제와 관련 있다. 이 때는 프로그램이 abort routine으로 가는데, control이 handler 코드로 넘어간 뒤 다시 user program으로 돌아오지 않는다. linux의 경우, divide by zero exception, protection fault (접근 권한이 없는 메모리를 참조하려고 할 때), 회복 불가능하다고 판단하여 abort routine으로 들어가게 된다.