티스토리 뷰

Study/OS

[Process] Exceptional Control

생각많은 소심남 2014. 8. 28. 01:52

* 이 글은 coursera에 공개되어 있는 강의인 HW/SW Interface의 강의 내용을 요약한 글입니다.


cpu의 역할을 뭘까? 당연히 사용자가 명령하는 것을 정해진 주기마다 수행한다. 그런데 사용자의 명령은 내부의 어떤 과정을 통해서 intruction이라는 개념으로 변환이 되고 이걸 읽고 실행하는 과정을 거치게 된다. 우리가 컴퓨터를 켜는 순간부터 끌때까지 말이다.

 보통 이런 과정을 control flow라고 한다. 예를 들어서 linux kernel 내부에서 network 기능이 수행될때의 그림은 다음과 같이 표현된다.



아마 cs를 전공하는 사람이라면 초창기에 열심히 control flow 를 그리면서 프로그램을 설계하는 방법을 익혔을 것이다. 앞에 나온 그림처럼 cpu는 일정시간마다 나열된 instruction을 순차적(sequential)으로 읽는다. 아무것도 안하고 있는 순간에도 분명 cpu는 instruction을 계속 읽고 있을 것이다.

 그런데 의문을 가질 수 있다. 우리가 멀티테스크니 뭐니해서 컴퓨터 상에 프로그램을 많이 동시에 실행시키고 있는데 이렇게 순차적으로 instruction이 수행되면 과연 다 수행할 수 있을까에 대해서 말이다. 그래서 어느 시점인가에는 sequential하게 수행되는 control flow를 변화시켜져야 할 필요가 생긴다. 보통 control flow를 바꾸는 방법은 크게 두가지가 있다. 첫번째는 jmp instruction을 써서 다른 메모리 영역으로 넘어가는 것이다. 여기에 jmp 와 관련한 조건이 있는지 없는지의 여부에 따라서 conditional jmp일지 unconditional jmp일지가 결정된다. 두번째는 앞에서 잠깐 소개한 것처럼 프로그램이 실행될 때마다 call/ret 을 통해서 instruction pointer를 바꾸는 방법이다. 

이렇게 소개한 방법은 control flow를 바꿔주면서 program 내의 state를 변경해주는 방법이다. 그런데 이런 program에서 벗어나 하드웨어와 관련한 state를 바꿔주기 위해서는 어떻게 할까? 보통 프로그램이 실행되고 있는 도중에 ctrl+alt+del을 눌러주면 프로그램이 종료된다. 아니면 프로그램의 오류로 인해서 갑자기 이런 창이 뜨기도 한다.



이런건 program의 state가 아닌 system 자체의 state를 변경해줘야 한다. program의 state를 변경하는 것만으로는 단순히 program의 execution과 termination에만 관여하지, 시스템적으로 문제가 발생한 건 program의 권한 밖이기 때문이다. 그래서 이런 것들은 앞에서 소개한 jmp나 call/ret만으로는 할 수 없는 것이다. 이때문에 따로 system state를 바꿔주는 과정을 exception이라고 한다. exception이 발생하게 되면 control flow는 다음과 같이 수행된다. 



가령 프로그램내에서 처리하기 버거운 일들, 예를 들어 실수를 0으로 나눴다던가 page fault가 발생한 것은 os 차원에서 control flow를 바꿔야 할 상황이 되고, 이 경우에는 OS에 있는 exception handler가 수행된다. 대부분은 exception이 발생하면 현재 수행되고 있는 프로그램을 종료(abort)시키게 되는데 앞에서 잠깐 소개한 Blue Screen 역시 Windows에서의 exception handling이라고 할 수 있다. 그런데 지금 예시가 다 시스템에 문제가 생겨서 exception이 발생하는 것으로 느낄 수도 있는데 실제로 뭔가 저장을 하는 순간에도 exception이 발생한다. 즉, I/O가 발생할 경우에도 말이다. 첨언을 하자면 I/O 작업 자체는 computing에 있어서 성능저하를 야기한다. 따라서 I/O에 주어지는 일을 최대한 빨리 끝나야 되는데 이때 exception이 발생한다. 

 보통 이렇게 exception이 발생하게 되면 현재 instruction이 수행되고 있는 지점을 저장하고 exception handler가 가리키는 곳으로 넘어가게 된다. 그러면 앞에서도 소개한 것처럼 exception의 종류도 많은데 이 모든 것들을 어떻게 처리할 수 있을까에 대한 의문이 발생한다. exception handler는 이런 system의 state를 바꿔야 될 중요 상황 몇가지에 대해서 numbering을 한 후에 이를 table로 관리한다. 그래서 exception이 발생하면 어떤 종류의 exception인지를 구별한 후 해당 table이 가리키는 곳으로 넘어가 수행하게 되는데 이런 기준을 Interrupt vector라고 한다.

해당 Exception table에는 각 exception handler를 가리키는 포인터가 들어있다. 그래서 잠깐 예를 들은것처럼 0번 exception인 것을 알면 divided by 0 exception을 handling할 수 있는 지점으로 instruction pointer가 넘어가서 exception을 수행하게 된다. 당연히 이런 exception을 구분하는 숫자는 각 상황을 나타내야 하는 것이므로 unique해야 되고, 해당 exception이 발생했단 징후가 발생하면 몇번이 발생하든 반복적으로 수행되어야 한다. 그래야 system state를 적절하게 바꿀 수 있기 때문이다.

 이런 exception의 종류도 두가지로 나뉜다. 그중에서 키보드 입력 같은 경우는 진짜 간혹 발생하는 exception이다. 또는 ctrl+alt+del 같은 입력도 사용자가 주는 exception이다. 이런 종류의 exception은 cpu가 매번 이 exception 발생할 것에 대해서 대비하지 않아도 된다. 당연히 발생빈도가 높지도 않은 exception을 준비하는 건 성능저하를 야기하기 때문이다. 그래서 이런 걸 Asynchronous Exception(비동기 exception) 혹은 Interrupt라고 한다. 보통 cpu diagram을 보면 pin 배열에 INT라고 되어 있는게 있다.


지금 위 그림은 일종의 PIC Controller인데 여기도 33/34 pin에 INT0/INT1 이라고 써져있다. 이말은 이 핀을 통해서 Interrupt를 수행하겠다는 말이다. 다시말해 이핀을 통해서 interrupt가 발생하면 process의 state를 바꿀 수 있다는 것이다. 이밖에도 마우스 이벤트도 이런 interrupt라고 할 수 있겠다.

반대로 exception이 주기적으로 발생하는 Synchronous Exception도 있다. 이 중에서 trap이라고도 하는데 보통 kernel 내부에서 돌아가는 system call도 일종의 exception인데 이것도 자주 수행되기 때문에 이런 경우는 trap으로 처리하게 된다. 또는 fault도 어떻게 보면 synchronous exception이라고 할 수 있는데 이게 다른 exception과의 차이는 일반적인 exception은 끝날때 다음 instruction 주소를 반환하는데 fault로 처리되는 exception중 일부는 문제가 발생했던 부분을 re execution 할 수 있다는 것이다. 물론 아닌 것도 있다. 또하나 더 있는게 abort 인데 이건 그냥 device나 machine 자체에 문제가 발생했을때 그냥 종료 시키는 exception이 되겠다.

 예를 들어서 Trap의 처리과정을 한번 보겠다. linux syscall 중에 open이라는 함수가 있는데 이 함수의 역할은 인자로 file name을 받아서 해당 file의 descriptor를 반환해주는역할을 한다. 그러면 이 open도 일종의 I/O이기 때문에 exception이 될것이고, 필요할때만 사용하기 때문에 trap으로 처리할 수 있다. 실제 코드는 다음과 같이 된다.



cd는 interrupt로 mapping된 instruction인데(보통 이런건 메뉴얼을 보면 확인할 수 있다.) 위와 같은 경우는 exception table중 0x80에 들어있는 pointer가 가리키는 exception을 처리하라는 의미다. 그래서 exception 내에서는 OS가 해당 file이 있는지의 여부, 권한 등을 확인하고 이에 대한 descriptior를 %ebx에 저장하게 된다. 그러면 이걸 exception에 빠져나옴과 동시 pop함으로써 사용자가 file의 상태를 인지할 수 있게 된다. 

 또다른 예시인 page fault다.


사용자는 전역적으로 배열을 선언한 후에 이중 일부 공간에 값을 넣고자 한다. 그럼 이 코드는 다음과 같이 수행된다.


우선 값을 넣는 과정이니 movl을 써야하는데 문제는 지금 저렇게 할당되어 있는 영역이 memory가 아닌 disk에 있을 때다. 그러면 disk에 있는 배열을 memory로 load하는 과정이 필요한데 이 과정이 page fault다. 그런데 앞에서 소개했던 것처럼 fault의 일부는 문제가 발생했던 부분을 re execution한다. 지금 예시가 바로 그런건데 이렇게 page fault가 발생해서 disk로부터 memory로 배열에 해당하는 page를 만든 후에는 다시 배열에 값을 넣는 과정을 수행하게 된다. 그래서 전체적으로 따지면 이 movl은 2번 수행된 셈이 된다. 물론 이런 부분은 상황에 따라서는 시스템 부하가 되기도 한다. 

그리고 아닌 경우도 있다고 했는데 바로 segmentation fault가 발생하는 경우다.


이 코드는 당연히 문제가 발생한다. 1000만 할당되어 있는 상태에서 5000번지에 값을 넣으면 segmentation fault가 발생하게 된다. 이때는 exception handling이 다음과 같이 된다.


앞과 동일하게 page fault가 발생하고 이에 따라서 배열에 해당하는 page를 할당하려다보니까 주소가 맞지 않게 된다. 이때는 exception handler는 SIGSEGV signal을 날려주고 abort되게 된다.


간단하게 exception 처리과정에 대해서 요약하고 정리해보았다. 내가 연구실에서 했던 부분도 일종의 이것과 관련된 부분이었고, 이걸 어떻게 처리하나 고민을 많이 했던것 같다. 물론 지금도 잘은 모르겠지만.. 아무튼 강의가 궁금한 사람은 해당강의를 들어보면서 복습해보면 좋을 거 같다.


- reference

https://www.coursera.org/course/hwswinterface

'Study > OS' 카테고리의 다른 글

[Process] Fork-exec Model  (0) 2014.08.29
[Process] Creating New Processes  (0) 2014.08.29
[Process] What is a process?  (2) 2014.08.29
[Memory] Memory Consideration in OS  (0) 2013.11.26
[OS] Context Switch  (0) 2013.06.29
[OS] POSIX Thread  (0) 2013.06.25
[Process] Inter Process Communication (IPC)  (5) 2013.06.21
댓글