이전에 쓴 글에서 이어서, 시스템에서의 Exception에 대해 다뤄보려고 한다.
논할 내용은 다음과 같다.
- Exceptions의 분류
- 비동기 사례: 인터럽트
- 동기 사례: Traps, Faults, and Aborts
Exceptions의 분류
Exception은 특정한 이벤트가 발생할 경우, control을 OS Kernel로 변경/전송해주는 역할을 한다.
Exception은 다양한 이벤트를 통해 발생할 수 있는데, 그 예시는 다음과 같다.
- 0으로 나누기
- page fault
- Ctrl-C 누르기
- I/O 요청 완료
위의 경우들의 종류에 따라, Exception은 프로세스를 잠시 멈추고 커널로 이동하여 예외를 핸들링한다.
만약 I_current에 있는 명령어를 실행하는 과정에서 Event가 발생하면, 해당 I_current가 종료된 뒤 Control Flow는 Kernel로 이동하게 된다. 이때 Exception의 종류에 따라, Exception Handler가 종료된 뒤 어떻게 다시 프로세스로 돌아갈 지가 결정된다.
- 만약 해당 이벤트 때문에 프로세스에서 마지막으로 실행한 명령이 올바르게 수행되지 않았다면, Return to I_current하여 I_current 지점부터 다시 수행할 수 있다.
- 만일 이벤트 발생이 명령어에 오류를 끼치지 않아 I_current의 수행까지 아무 문제 없었다면, return to I_next하여 I_current 바로 다음 명령어부터 순차적으로 진행할 수 있다.
- 만일 이벤트가 치명적이라 프로세스를 더 실행할 수 없다면, 프로세스를 중단(abort)시킬 수 있다.
위의 다양한 이벤트의 예시와, 프로세스로 어떻게 돌아갈지의 방법 등을 분류의 기준으로 삼아보자.
이때 우리는 4가지의 Exception 분류를 만들 수 있다. 저번 포스팅에서도 본 그 이미지이다.
자, 이제 각각의 Exception에 대해 설명해 보겠다.
비동기 사례: 인터럽트
인터럽트는 프로세스 바깥의 이벤트로 인해 발생하는 Exception으로, 인터럽트 핀을 통해 수행된다.
CPU는 프로세스의 명령어들을 순차적으로 수행하면서, 각각의 명령어를 실행시킨 뒤 인터럽트 핀의 상태를 확인한다. 이때 핀의 상태값이 high라면(즉, 전기 신호가 들어오고 있다면) 컨트롤 흐름을 Kernel로 넘긴다.
인터럽트가 발생한 경우, Exception에 대한 처리를 해준 뒤 I_next로 이동해서 수행하던 프로세스를 이어 진행한다.
이러한 인터럽트의 사례들은 외부에서의 연산 수행과 프로세스가 비동기로 운영되기 때문에, 비동기 예외로 구분한다.
인터럽트가 발생하는 상황은 어떤 것이 있을까? 대표적인 사례를 예로 보자.
Timer Interrupt
프로세서는 한 번에 하나의 프로세스만 실행할 수 있지만, OS에는 보통 수십개의 프로세스가 돌고 있다. 이를 순차적으로 time sharing해서 실행해야 하는데, 이를 위해 OS는 timer를 세팅하고 정해진 시간동안 프로세스를 실행시킨다. 만약 timer가 다 되면 context switching을 통해 프로세스를 다른 프로세스로 교체해 준다. 이때 timer는 프로세스 바깥에 있기 때문에, interrupt pin을 통해 알림을 전달하는 것이다.
I/O Interrupt from external device
이전 포스팅에서 예를 든 것처럼 네트워크를 통해 값이 전달되거나, 혹은 디스크에서 파일 입출력 연산을 수행하기 위해 연산을 요청했는데 완료되었다는 알림이 오는 경우. 이 두 경우 모두 프로세스 바깥에서의 I/O 연산이라는 이벤트 탓에 프로세스를 멈춰야 하는 경우에 속한다. 이 경우에도 Interrupt pin을 통해 신호를 줘서 Exception을 발생시킨다.
또한 비슷한 예시로 리눅스 콘솔에서 프로세스 실행 도중 Ctrl-C를 누르는 경우가 있다. 프로세스는 이 입력을 올바르게 받아서 처리하기 어려울 수 있지만 (예를 들어, 무한 루프에 빠진 프로그램은 저 입력을 수행할 수 없다. 그러나 무한 루프에 빠져 응답없음 상태가 된 프로그램도 Ctrl-C를 누르면 종료된다) OS가 해당 입력이 들어올 시 Interrupt pin을 조정하여 프로그램이 종료되어야 한다는 것을 알려주는 것이다.
동기 사례: Traps, Faults, and Aborts
위의 비동기 예외 사례와 달리, 프로세스의 명령어 실행에서 이벤트가 생겨나는, 즉 동기적인 예외 상황 역시 존재한다.
이를 크게 세 가지로 나누는데, 이제 각각의 예외가 어떤 이벤트에서 비롯되고 어떻게 수행되는지 알아보자.
Trap
트랩은 프로세스가 직접 System call을 호출하는 경우, 혹은 이외의 방법으로 OS에 특정 명령어 수행을 요청하는 경우에 발생하는 Exception이다. 의도적으로 호출되는 것이라는 데에 집중하면 이해하기 좋다.
스스로 event를 만들어 Kernel로 흐름을 옮겨주기를 요청했기 때문에, I_current에서 호출되었다면 거기에서 Kernel의 Exception Handler로 넘어가 해당 system call에 맞는 명령어를 찾는다. 필요한 값을 exception handler에서 반환하면서, I_next로 이동하여 명령어를 순차적으로 수행한다.
Fault
fault는 의도적이지 않지만 복구 가능한 문제로 인해 발생하는 Exception이다.
대표적인 예로 Page Fault가 발생하는 경우가 있겠다.
컴퓨터의 DRAM 메모리는 명확한 한계를 가지기 때문에, 만약 가지고 있는 DRAM 용량보다 큰 메모리가 필요하다면 난감해진다. 그렇기 때문에 OS는 가상 메모리라는 방법을 사용한다. 가상 메모리를 구현할 때에는실제 DRAM에는 없는 공간을 디스크나 SSD 등에 있는 파티션 공간과 연결하고 이를 RAM처럼 사용하게 되는데, 만약 DRAM에 없는 데이터를 메모리에 요청할 경우 문제가 발생할 수 있다.
Page Fault는 위에서 언급한 것처럼, DRAM에 없는 데이터를 프로세스가 요청한 경우에 발생하는 이벤트이다. 이 경우 Kernel은 SSD나 디스크에 있는 데이터를 DRAM으로 옮겨줘야 속도 저하나 오류 없이 원하는 동작을 수행할 수 있다.
그렇기 때문에 Page Fault가 발생하게 되면, 일단 프로그램을 잠시 멈추고 Kernel로 가게 된다. Kernel은 디스크나 SSD에 있는 값을 DRAM에 옮겨주고, 프로세스가 마지막으로 수행하려던 명령어(I_current)를 다시 수행하라고 전달한다. 방금 전에는 메모리에 없어서 해당 명령어를 수행하지 못했으니, 다시 수행하게 하여 page fault가 난 것을 감추는 것이다.
이렇게 다시 실행이 되어 원하는 값을 메모리에서 가져올 경우, 사용자는 page fault가 났다는 사실도 모르고 편안하게 프로세스의 실행을 즐길 수 있다. 물론 아래 이미지에서 암시되듯, 상황에 따라서는(가상 메모리에서 가져오려는 값이 정작 디스크에 없거나) page fault를 해결하지 못해 abort가 일어나는, 즉 프로세스가 중단되는 일도 일어날 수 있다.
Page Fault에 대한 설명이 많이 부실했다. 이에 대해서는 추후 포스팅해보려고 한다.
아래 글을 참고하면 이해하기가 더 쉬우리라 믿는다.
Abort
Abort는 의도하지 않았으며, 복구할 수도 없는 치명적인 오류 이벤트에 대한 예외 상황이다. 유효하지 않은 명령어의 입력, 패리티 비트 오류 등으로 발생할 수 있다.
위에서 언급된 유효하지 않은 명령어의 실행은 프로세스의 실행이 OS에까지 위험을 끼칠 수 있는 상황으로 간주된다. 그런데 패리티 비트 오류는 왜 위험한 상황으로 간주될까?
패리티 비트는 정보의 전달 과정에서 비트의 1의 개수가 짝수인지 홀수인지 검증하는 비트인데, 오류를 검출하고 수정하는 데에 사용할 수 있다. 만약 하드웨어의 전력 문제 등으로 비트플립(0과 1이 뒤바뀌는 하드웨어 오류)이 발생한다면 패리티 비트를 통해 오류임을 확인할 수 있고, 각각의 오류로 예상되는 값을 수정했을 때 패리티 비트가 유효한지 검사하여 오류를 해결할 수도 있다는 이야기이다.
그런데 패리티 비트를 통해 오류를 검출했지만 이를 해결할 수 없다면? 이는 치명적인 오류(손상된 메모리 값)이기 때문에 이에 대한 프로세스를 계속 실행시키는 것은 예상치 못한 결과를 불러오는 위험한 행동으로 간주될 수 있다.
Abort가 handling될 경우, kernel은 프로세스를 종료시키게 된다.
이렇게 시스템에서의 기초적인 Exceptions의 종류, 그리고 그것이 어떻게 수행되는지에 대해 적어보았다.
Java나 C++ 개발을 꽤 오래 하면서도 Exception이란 개념이 어떻게 생겼는지는 고민해 본 적이 없었다.
그런데 이렇게 정리를 해보고 공부를 해보면서, 프로그램의 오류에 대해 어떻게 대응할지 당대에 고민한 흔적들이 보여 재미있었다. 게다가 오류에 대한 분류와 대응 방법은 내가 응용할 수도 있는 영역이구..
요새 시스템 프로그래밍에도 점점 재미가 붙는다. 다음에는 프로세스에 대해 포스팅해 보려고 한다.
잘못된 내용이 있다면 지적 부탁드립니다.
'프로그래밍 > 기타' 카테고리의 다른 글
[JS] var, let, const, 호이스팅과 클로저 (0) | 2021.06.17 |
---|---|
팻 핑거와 테스트 코드 (1) | 2021.05.17 |
[시스템] Control Flow와 Exceptions (0) | 2021.05.02 |
DTO 직렬화 과정에서 PropertyNamingStrategy 사용하기 (0) | 2021.03.04 |
[학습] 리액티브 프로그래밍 (0) | 2021.02.26 |
댓글