참고 자료
[책] 운영체제 아주 쉬운 세 가지 이야기
6장 제한적 직접 실행 원리 개요
CPU를 가상화하기 위해서 운영체제는 여러 작업들이 동시에 실행되는 것처럼 보이도록 물리적인 CPU를 공유한다.
CPU 시간을 나누어 씀으로써 가상화를 구현할 수 있다.
이러한 가상화 기법을 구현하기 위해서는 몇 가지 문제를 해결해야 한다.
1. 성능 저하 (시스템에 오버헤드를 주지 않으면서 가상화를 구현)
2. 제어 문제 (운영체제가 프로세스에 대한 제어권이 있어야 함)
6.1 기본 원리: 제한적 직접 실행
운영체제 개발자들은 프로그램을 빠르게 실행하기 위해 제한적 직접 실행(Limited Direct Execution) 기법을 개발했다. 프로그램을 CPU 상에서 직접 실행시키는 것.
해당 그림은 직접 실행 (아무 제한이 없는) 프로토콜이다. 하지만 이 방식은 몇 가지 문제가 있다.
프로그램을 직접 실행시킨다면 프로그램이, 운영체제가 원치 않은 일을 하지 않는다는 것을 어떻게 보장할 수 있을까?
프로그램 실행 시, 운영체제는 어떻게 프로그램의 실행을 중단하고 다른 프로세스로 전환시킬 수 있는가, 즉 시분할 기법을 어떻게 구현할 수 있느냐는 것
개발자들은 제한이 없는 직접 실행 기법에서 가상화를 위해 제한적 직접 실행 기법으로 발전시켰다. 프로그램 실행에 제한을 두지 않으면 운영체제는 어떠한 것도 제어할 수 없으며 따라서 단순한 라이브러리일 뿐이다.
6.1 문제점 1: 제한된 연산
직접 실행의 장점은 빠르게 실행된다는 것이다. 기본적으로 프로그램이 CPU에서 실행되기 때문이다. 하지만 문제점이 발생한다. 만일 프로세스가 특수한 종류의 연산을 수행하길 원한다면 어떻게 될 것인가? 이러한 연산에는 디스크 I/O 요청이나 메모리와 같은 시스템 자원에 대한 추가할당 요청 등이 포함된다. 프로세스가 원하는 대로 할 수 있게 방치하는 방안이 있다. 그러나 이는 안전한 시스템을 구축하는 데에는 방해 요인이다.
이 때문에 사용자 모드(user mode) 라고 알려진 새로운 모드가 도입되었다. 사용자 모드에서 실행되는 코드는 할 수 있는 일이 제한된다. 프로세스가 사용자 모드에서 실행 중이면 입출력 요청을 할 수 없도록 설정한다. 이때 입출력 요청을 하면 프로세서가 예외를 발생시키고, 운영체제는 해당 프로세스를 제거한다.
커널 모드(kernel mode)는 사용자 모드와 대비되는 모드로서 운영체제의 중요한 코드들이 실행된다. 이 모드에서 실행되는 코드는 모든 특수한 명령어를 포함하여 원하는 모든 작업을 수행할 수 있다. 그렇다면 사용자 프로세스가 디스크를 읽기와 같은 특권 명령어를 실행해야 할 때는 어떻게 해야 하는가? 이런 제한 작업의 실행을 허용하기 위하여 거의 현대 모든 하드웨어는 사용자 프로세스에게 시스템 콜을 제공한다.
시스템 콜을 실행하기 위해 프로그램은 trap 특수 명령어를 실행해야 한다. 이 명령어는 커널 안으로 분기하는 동시에 특권 수준을 커널 모드로 상향 조정한다. 커널 모드로 작업을 끝내고 완료되면 운영체제는 return-from-trap 특수 명령어를 호출한다. 이 명령어는 특권 수준을 사용자 모드로 다시 조정하여 호출한 사용자 프로그램으로 리턴한다.
trap 명령어를 수행할 때 주의가 필요하다. 호출한 프로세스의 필요한 레지스터들을 저장해야 한다. 운영체제가 return-from-trap 명령어 실행 시 사용자 프로세스로 제대로 리턴할 수 있도록 하기 위함이다. 예를 들어 PC, 플래그 레지스터와 다른 몇 개의 레지스터를 각 프로세스의 커널 스택에 저장한다. return-from-trap 명령어가 이 값들을 스택에서 pop하여 사용자의 프로그램의 실행을 다시 시작한다.
그렇다면 trap이 운영체제 코드의 어디를 실행할지 어떻게 알까? 호출한 프로세서는 분기할 주소를 명시할 수 없다. 주소를 명시한다는 것은 커널 내부의 원하는 지접을 접근할 수 있다는 것이기 때문에 위험하다. 이러한 문제 때문에 커널은 trap 발생 시 어떤 코드를 실행할지 신중하게 통제해야 한다. 커널은 부팅 시 트랩 테이블(trap table)을 만들고 이를 이용하여 시스템을 통제한다. 컴퓨터가 부트될 때는 커널 모드에서 동작하기 때문에 하드웨어를 원하는대로 제어할 수 있다.
운영체제가 하는 초기 작업 중 하나는 하드웨어에게 예외 사건이 발생했을 때 어떤 코드를 실행해야 하는지 알려주는 것이다. 운영체제는 특정 명령어를 사용하여 하드웨어에게 트랩 핸들러(trap handler)의 위치를 알려준다. 하드웨어는 이 정보를 전달받으면 해당 위치를 기억하고 있다.
트랩 테이블 / 트랩 핸들러 개념은 인터럽트 벡터 테이블(Interrupt Vector Table, IVT) 인터럽트 서비스 루틴(Interrupt Service Routine, ISR) 개념이다.
6.2 문제점 2: 프로세스 간 전환
직접 실행의 두 번째 문제는 프로세스 간 전환을 할 수 있어야 한다는 점이다. CPU에서 실행하고 있지 않다면 운영체제는 CPU 를 다시 획득하여 프로세스를 전환할 수 있는가?
협조 방식: 시스템 콜 기다리기
협조방식으로 알려진 방법은 과거의 시스템에서 채택되었던 방식이다. 응용 프로그램이 비정상적인 행위를 하게 되면 운영체제에게 제어가 넘어간다. 협조 방식의 스케줄링 시스템에서 운영체제는 시스템 콜이 호출되기를 기다리거나 불법적인 연산이 일어나기를 기다려서 CPU 의 제어권을 다시 획득한다. 이상적이라기보다 수동적인 방법 아닐까? 예를 들어 악의적이든 버그로 인한 일이든 프로세스가 무한 루프에 빠져 시스템 콜을 호출할 수 없을 때에는 어떤 일이 벌어지는가? 운영체제가 할 수 있는 일은 무엇인가?
이 방식은 운영체제가 다른 작업을 실행할 결정을 할 수 있도록 주기적으로 CPU 를 포기할 것이라고 가정한다.
비협조 방식: 운영체제가 전권을 행사
협조적 방법에서 프로세스가 무한 루프에 빠졌을 경우 할 수 있는 일은 컴퓨터를 다시 부팅하는 것이다. 이건 정상적인 해결책이 아니다. 그래서 개발자들은 다른 해결책인 타이머 인터럽트(timer interrupt)를 개발했다. 타이머 인터럽트가 발생하면 수행 중인 프로세스는 중단되고 미리 구성된 운영체제의 인터럽트 핸들러(인터럽트 서비스 루틴, ISR)가 실행된다. 이 시점에 운영체제는 CPU제어권을 다시 얻게 되고 자신이 원하는 일을 할 수 있다. 원하는 일이란 현재의 프로세스를 중단하고 다른 프로세스를 실행시키는 작업(컨텍스트 스위칭)이 해당된다.
문맥의 저장과 복원
시스템 콜을 통하여 협조적으로 하던, 타이머 인터럽트를 통하여 강제적으로 하던, 운영체제가 제어권을 다시 획득하면 컨텍스트 스위칭에 대한 여부를 결정해야 한다. 이 결정은 스케줄러(scheduler)라는 부분에 의해 내려진다.
다른 프로세스로 전환하기로 결정되면 운영체제는 문맥 교환(context switch)이라고 알려진 코드를 실행한다. 운영체제는 현재 실행 중인 프로세스의 레지스터 값을 커널 스택 같은 곳에 저장하고(PCB) 곧 실행될 프로세스의 커널 스택으로부터 레지스터 값을 복원한다. 그렇게 함으로써 운영체제는 return-from-trap 명령어가 마지막으로 실행될 때 현재 실행중이던 프로세스가 리턴하는 것이 아니라 다른 프로세스로 리턴하여 실행을 다시 시작할 수 있다. 프로세스 전환을 위하여 운영체제는 현재 실행 중인 프로세스의 범용 레지스터, 프로그램 카운터뿐 아니라 현재 커널 스택 포인터를 저장한다. 그리고 곧 실행될 프로세스의 범용 레지스터, 프로그램 카운터를 복원하고 커널 스택을 이 프로세스의 커널 스택으로 전환한다. 이로써 운영체제는 인터럽트된 프로세스 문맥에서 전호나 코드를 호출하고 실행될 프로세스 문맥으로 리턴할 수 있다. 운영체제가 마지막으로 return- from-trap 명령어를 실행하면 곧 실행될 프로세스가 현재 실행 중인 프로세스가 된다.
6.4 병행성이 걱정
만약 시스템 콜을 처리하는 도중에 타이머 인터럽트가 발생하면 어떤 일이 발생하는가?
하나의 인터럽트를 처리하고 있을 때 다른 인터럽트가 발생하면 어떤 일이 생기는가?
운영체제는 인터럽트 또는 트랩을 처리하는 도중에 다른 인터럽트가 발생할 때 어떤 일이 생기는가에 대해 신중하게 고려할 필요가 있다.
운영체제는 이 다루기 힘든 상황을 어떻게 처리할까?
가장 간단한 해법은 인터럽트를 처리하는 동안 인터럽트를 불능화시키는 것이다. 이럴 경우 하나의 인터럽트가 처리되고 있는 동안 다른 어떤 인터럽트 CPU에게 전달되지 않는다. 하지만 인터럽트를 너무 오랫동안 불능화시키면 인터럽트를 놓치게 되고 기술적으로도 좋지 않다.
운영체제는, 또한 내부 자료 구조에 동시에 접근하는 잃을 방지하기 위해 많은 정교한 락(lock) 기법을 개발해 왔다. 이 잠금 기법은 커널 안에서 동시에 다수의 활동이 진행될 수 있게 허용한다. 이 책의 병행성에 관한 부분에서 살펴보겠지만 이런 잠금 기법은 복잡해질 수 있으며 그로 인해 흥미로우면서도 발견하기 힘든 버그를 만들어 낸다.
'Computer Sience > Operating System' 카테고리의 다른 글
[OS] 운영체제 아주 쉬운 세 가지 이야기 7장 스케줄링 (1) | 2025.04.07 |
---|---|
[OS] 운영체제 아주 쉬운 세 가지 이야기 5장 프로세스 API (0) | 2025.03.31 |
[OS] 운영체제 아주 쉬운 세 가지 이야기 4장 프로세스의 개념 (0) | 2025.03.31 |
[OS] 운영체제 아주 쉬운 세 가지 이야기 2장 운영체제 개요 (0) | 2025.03.22 |
스레드 컨텍스트 스위칭 vs 프로세스 컨텍스트 스위칭 (0) | 2024.05.18 |