
본 글의 내용은 Operating Systems: Three Easy Pieces의 Mechanism: Limited Direct Execution 챕터를 정리한 것입니다.
스케줄링 은 OS라는 시스템의 가상화를 어떤 식으로 효율적으로 구상할 지(가상화의 오버헤드를 줄일지)에 기여한 솔루션이었다.
CPU의 제어권을 유지하면서 효율적으로 프로세스를 작동시키는 것 또한 OS 에게 주어지는 과제다.
특정 프로그램이 무한히 작동하거나, 하드웨어 제어권을 가지거나, 중요한 정보에 마음대로 접근할 수 없도록 해야한다.
프로그램을 빠르게 실행시키려면, CPU가 직접 프로그램을 작동시키면 된다.
문제점
실행시킬 프로그램이 우리가 원하지 않았던 작업을 하지 않을 것이라 보장 할 수 있는가?
CPU가 직접적으로 실행을 하면 OS는 프로세스 스케줄링 (time sharing)을 어떻게 해야되는가?
위의 문제점들로 인해 CPU를 가상화 해야한다는 결과가 도출되었다.
직접 실행(Direct Execution)은 성능 면에서는 확실히 강점이 있다고 할 수 있다.
하지만 프로세스가 I/O 요청이나 CPU나 메모리 등에 액세스 하려는(접근 권한을 확장하려는) 시도를 한다면?
그런 접근을 제한없이 마음대로 할 수 있게 하는 것은 시스템이 지향하는 것과 어긋난다.
이런 접근을 제한하기 위해 탄생한 것이 User Mode 이다.
User Mode 란, 작성된 코드가 제한된 범주 내에서 실행되는 것을 의미한다.
User Mode 인 프로세스는 입출력 요청을 할 수가 없다. 그렇게 한다면 OS 가 프로세스를 죽인다.Kernel Mode 란, 반대로 코드가 OS 혹은 커널에서 실행되고 User Mode 에서는 제한되던 권한들을 부여받을 수 있다.
그런데 이렇게 모드가 나뉘어져 있다면, User Mode 는 평생 입출력 요청은 할 수가 없는 것인가?
→ User Mode 에서도 제한 권한에 접근할 수 있도록, System Call 이 제공된다.

System Call 은 커널 쪽에서 User Mode 프로그램에게 제한하고 있던 기능을 제공한다.
(굉장히 제한적으로)
이 System Call 은 trap instruction 이라는 정해진 통로로 Kernel Mode 로 진입한다.
Kernel Mode 로 진입하면, privileged level을 높이게 되는데,
이 때 해당 privileged level로 충족할 수 있는 허용된 오퍼레이션들을 실행할 수 있게 된다.
그래서 원하던 작업을 실행할 수 있게 되고,
그 후 return-from-trap instruction이 호출되면서 privileged level이 하향되며 User Mode 로 돌아간다.
Mode를 전환하는 과정에서, User Mode에서 사용된 정보들을 보존해야 하기 때문에 레지스터/PC의 정보를 Kernel Stack에 저장하고 복원한다.

OS 내에는 부팅할 때 생성되는 Trap Table 이 존재하고 그곳에 key-value 형태로 handler들이 등록되어 있기 때문에, Trap이 올바르게 동작하도록 설정할 수 있다. (Trap Table 의 위치는 CPU에 기록된다.)
이 Trap Table 의 존재 덕분에 OS 가 특정 예외가 발생하면 HW가 어떤 코드가 실행돼야 하는지 지시할 수 있다. (Trap Handler (핸들러도 Instruction임.)의 위치를 알려준다)
하지만 Trap Table 의 핸들러를 HW가 실행할 수 있도록 하는 것은 권한이 필요한 작업이고, User Mode 에서 요청할 시 프로세스가 파괴된다.
정확히 말하면, 프로세스는 OS 가 실행하는 것이 아니라, CPU가 실행하는 것이다.
그렇다면 OS 가 어떻게 프로세스를 스위칭하고, 종료할까?
→ System Call 이 발생할 때마다 제어권은 OS 에게 넘어오므로, 그때 하면 된다. System Call 은 파일 입출력이나, 예외 발생 등 다양한 상황에서 호출되기 때문이다.
→ 하지만 만약 프로세스가 System Call 을 발생시키지 않고, 무한 루프만을 돈다면?
→ OS 를 재부팅하는 방법밖에 없다.
⇒ 이런 경우를 타개하기 위해서 고안된 방식이 Timer Interrupt 이다.
각 프로세스의 Timer 가 만료되면, Interrupt 가 발생한다.
그럼 실행 중이던 프로세스는 중단된다.
OS 의 Interrupt Handler 가 실행된다.
이때 OS 는 CPU 제어권을 다시 획득하며, 프로세스를 멈추거나 컨텍스트 스위칭을 하는 등 원하는 동작을 할 수 있다.
Interrupt 가 발생했을 때 어떤 코드를 실행해야 되는지는 OS가 부팅 시에 HW에게 알려둔다. 그리고 부팅 과정 안에서 타이머를 시작한다.
타이머가 시작됐다는 것은, 더이상 OS가 실행 주도권에 대한 걱정을 하지 않아도 된다는 것과 같다.
컨텍스트 스위칭을 하려면 현재 실행하는 프로세스의 컨텍스트를 저장하고, 다음 프로세스의 컨텍스트를 불러와야 된다. (다른 커널 스택을 선택하기 위해 스택 포인터가 수정된다)
→ 이때도 return from trap 이 발생한다. (Trap Instruction 으로 Kernel Mode 가 되지 않았지만!)

한가지 더 확인할 것은, 위 예시에서 timer interrupt 가 발생한 이후, User Mode 상태의 레지스터 정보가 HW에 의해 저장된다는 것이다.
반면에 context switching 과정에서 일어나는 커널 레지스터의 정보는 OS 에 의해 저장되고, 저장 위치는 PCB 이다. (즉, 메모리)
만약 System Call 을 처리 중인데 Timer Interrupt 가 발생한다면?
Interrupt 처리 중에 또 Interrupt 가 발생한다면?
이러한 동시성에 대한 염려때문에, Lock 또는 Interrupt 비활성화 등이 논의되었다.
CPU 가상화의 한 축을 담당하는 low-level mechanism을 실현시키기 위해 Limited Direction Execution이 도입되었다. (User mode & Kernel mode)
CPU에서 프로그램을 직접 실행하지만, 프로세스는 각 모드에 의해 OS의 도움 없이는 실행할 수 있는 작업이 제한되는 것이다.
프로세스는 system call(trap instruction)을 통해 kernel mode에 진입할 수 있으며, 제한되던 작업을 실행 후 return-from-trap instruction을 통해 user mode로 복귀한다.
이런 system call을 통해 주도권이 OS에게 넘어가므로, system call이 호출되지 않으면 context switching을 할 수 없는 문제가 있는데, 이걸 해결하기 위해 timer interrupt가 등장하였다.
timer가 끝나면 interrupt가 발생하며, kernel mode로 진입하여 context switching이 가능해진다.