CPU를 가상화하기 위해서는 몇 가지 문제를 해결해야 한다.
- 성능 저하
→ 시스템에 과중한 오버헤드를 주지 않으면서 가상화를 구현할 수 있을까?
- 제어 문제
→ CPU에 대한 통제를 유지하면서 프로세스를 효율적으로 실행시킬 수 있는 방법은 무엇인가?
운영체제의 입장에서는 자원 관리의 책임자로서 특히 제어 문제가 중요하다. 제어권을 상실하면 한 프로세스가 영원히 실행을 계속할 수 있고 컴퓨터를 장악하거나 접근해서는 안 되는 정보에 접근하게 된다. 제어권을 유지하면서 성능 저하가 없도록 하는 것이 운영체제를 구축하는 데 핵심적인 도전 과제이다.
✅ 제한적 직접 실행의 원리
운영체제 개발자들은 프로그램을 빠르게 실행하기 위하여 제한적 직접 실행이라는 기법을 개발하였다. 이 아이디어의 ‘직접 실행’에 해당하는 부분은 간단하다. 프로그램을 CPU 상에서 그냥 직접 실행시키는 것이다.
위 그림은 제한이 없는 기본적인 직접 실행 프로토콜이다. 이 접근법은 CPU를 가상화함에 있어 몇 가지 문제를 일으킨다.
- 프로그램을 직접 실행시킨다면 프로그램이 운영체제가 원치않는 일을 하지 않는다는 것을 보장할 수 있는가?
- 프로세스 실행 시, 운영체제는 어떻게 프로그램의 실행을 중단하고 다른 프로세스로 전환시킬 수 있는가?
프로그램 실행에 제한을 두지 않으면 운영체제는 어떠한 것도 제어할 수 없으며 따라서 단순한 라이브러리일 뿐이다.
문제점 1: 제한된 연산
직접 실행의 장점은 빠르게 실행된다는 것이지만, 프로세스가 특수한 종류의 연산을 수행하길 원한다면 문제가 발생한다. 예를 들어, 디스크 입출력 요청이나 CPU 또는 메모리와 같은 시스템 자원에 대한 추가할당 요청 등이 있다.
프로세스가 원하는 대로 할 수 있게 하려면 파일에 대한 접근을 허용하기 전에 접근 권한을 검사하면 된다. 하지만 이 방법도 프로세스가 디스크에 대하여 입출력하는 것을 제한하지 않으면 프로세스는 전체 디스크를 읽고 쓸 수 있기 때문에 접근 권한을 검사하는 기능이 아무런 의미가 없다.
이 때문에 사용자 모드와 커널 모드가 생겼다!
사용자 모드는 할 수 있는 일이 제한되어 있고, 커널 모드는 모든 작업을 수행할 수 있다. 따라서, 사용자가 특수 명령어를 실행하고 싶으면, 커널 모드를 잠깐 들러서(시스템 콜) 실행해야 한다. 시스템 콜을 실행하려면 프로그램은 trap 특수 명령어를 실행해야 한다. 작업이 완료되면 운영체제는 return-from-trap 특수 명령어를 호출하여 사용자 프로그램으로 리턴한다.
문제점 2: 프로세스 간 전환
직접 실행의 두 번째 문제점은 프로세스 간 전환을 할 수 있어야 한다는 점이다. 프로세스 간 전환은 당연히 간단해야 한다! 운영체제는 실행 중인 프로세스를 계속 실행할 지, 멈추고 다른 프로세스를 실행할 것인지 결정해야 한다.
하지만 문제가 있다. CPU에서 프로세스가 실행 중이라는 것은 운영체제는 실행 중이지 않다는 것을 의미한다. 운영체제가 실행하고 있지 않다면 어떻게 이런 일들을 할 수 있을까?
운영체제는 어떻게 CPU를 다시 획득하여 프로세스를 전환할 수 있을까?
협조 방식: 시스템 콜 기다리기
대부분의 프로세스는 자주 시스템 콜을 호출하여 CPU의 제어권을 운영체제에게 넘겨준다. 이런 유형의 운영체제는 yield 시스템 콜을 제공하는데, 이 시스템 콜은 운영체제에게 제어를 넘겨 운영체제가 다른 프로세스를 실행할 수 있게 한다.
또, 응용 프로그램이 비정상적인 행위를 하게 되면 운영체제에게 제어가 넘어간다. 예를 들어 어떤 수를 0으로 나누거나 접근할 수 없는 메모리에 접근하려고 하면 운영체제로의 트랩이 일어난다. 그러면 운영체제는 CPU를 획득하여 해당 행위를 하는 프로세스를 종료시켜버린다.
협조 방식의 스케쥴링 시스템에서 운영체제는 1. 시스템 콜이 호출되기를 기다리거나, 2. 불법적인 연산이 일어나기를 기다려서 CPU의 제어권을 다시 획득한다.
하지만, 악의적이든 버그이든 프로세스가 무한 루프에 빠져 시스템 콜을 호출할 수 없을 때는 어떻게 하나???
비협조 방식: 운영체제가 전권을 행사
프로세스가 시스템 콜을 호출하기를 거부하거나 실수로 호출하지 않아 운영체제에게 제어를 넘기지 않을 경우 하드웨어의 추가적인 도움없이는 운영체제가 할 수 있는 일은 거의 없다. 사실, 협조적 방법에서 프로세스가 무한 루프에 빠졌을 경우 할 수 있는 일은 컴퓨터 시스템의 모든 문제를 해결해 왔던 아주 오랜 방법인…. 컴퓨터를 다시 부팅하는 방법밖에 없다.
프로세스가 비협조적인 상황에서도 CPU의 제어를 획득하는 방법은 무엇인가? 악의적인 프로세스가 컴퓨터를 장악하지 않도록 보장하기 위하여 운영체제는 무엇을 할 수 있을까?
그 해결책은 간단하다! 타이머 인터럽트를 이용하는 것이다. 타이머 장치는 수 밀리 초마다 인터럽트를 발생시키도록 프로그램 가능하다. 인터럽트가 발생하면 현재 수행 중인 프로세스는 중단되고 미리 구성된 운영체제의 인터럽트 핸들러가 실행된다. 이 시점에 운영체제는 CPU 제어권을 다시 획득하여 자신이 원하는 일을 할 수 있다.
시스템 콜을 통하여 협조적으로 하던, 또는 타이머 인터럽트를 통하여 약간은 강제적으로 하던, 운영체제가 제어권을 다시 획득하면 중요한 결정을 내려야 한다. 즉, 현재 실행 중인 프로세스를 계속 실행할 것인지 아니면 다른 프로세스로 전환할 것인지를 결정해야 한다. 이 결정은 운영체제의 스케쥴러라는 부분에 의해 내려진다.
만일 다른 프로세스로 전환하기로 결정했다면! 운영체제는 문맥 교환(Context Switch)라고 알려진 코드를 실행한다.
문맥 교환 ? - 운영체제가 현재 실행 중인 프로세스의 레지스터 값을 커널 스택 같은 곳에 저장하고 곧 실행될 프로세스의 커널 스택으로부터 레지스터 값을 복원하는 것이다!
✅ 병행성이 걱정이다..!
인터럽트 또는 트랩을 처리하는 도중에 다른 인터럽트가 발생할 때 어떤 일이 생기는가?
문제를 해결하는 가장 간단한 해법은 인터럽트를 처리하는 동안 인터럽트를 불능화 시키는 것이다. 하지만 이 방법은 좋은 방법이 아니다. 나중에 병행성에 관한 부분에서 살펴본다.
[참고 문헌] Operating Systems: Three Easy Pieces
Share article