[virtualization]6.direct execution

ROSI·2023년 7월 4일

프로그램 실행방법 중 하나로 direct execution이 있다.
프로그램을 기계어로 컴파일하여 직접 실행하는 방식이다.
그 외에도 인터프리터, 자바 가상머신, just in time 컴파일러(기본적으로 인터프리터, 빈번한 부분은 실시간으로 컴파일), 가상머신 등이 있다.

OS가 CPU가상화를 구현할 때 고려해야하는 것 중 성능과 함께 제어권문제를 빼놓을 수 없다.
제어권을 상실하면 한 프로세스가 영원히 실행될 수 있고, 접근해서는 안되는 정보에 접근할 수 있다.
그래서 프로세스는 1.제한된 연산, 2.프로세스간 전환 이 가능하도록 실행되어야한다.

이를 준수하기 위해 RDE(restricted direct execution)기법은 다음과 같이 구현할 수 있다.

1. 제한된 연산

제한된 연산을 수행하다가, 필요하다면 미리 정의된 형태의 작업이 제공될 수있다.
프로세스의 특권레벨(privilege level)은 크게 사용자모드 또는 커널모드로 구분될 수 있다.
예를 들어 x86 instruction set에서는 0~3의 4가지의 특권레벨이 존재한다.
특권 레벨 0은 사용자모드, 3은 커널 모드이다.
사용자모드의 프로세스는 일부 자원에 대한 접근이 제한되어있다.
커널모드의 프로세스는 모든 자원에 대한 접근이 가능하며, 특권 명령(privileged instruction)을 실행할 수 있다.
사용자모드에서 제한된 연산을 실행해야할 경우, 시스템콜이라는 API(Application Programming Interface. application과 다른 sw간의 상호작용을 위한 interface)를 사용하여 제한된 연산이 허용될 수 있다.
초기 UNIX는 약 20개의 시스템콜을 제공했고, 현대의 OS에서는 수백개의 시스템콜을 제공한다.
시스템콜을 통해 파일 시스템 접근, 프로세스 생성 및 제거, 다른 프로세스와의 통신 및 메모리 할당이 가능하다.
사용자모드의 프로세스가 시스템콜 래퍼함수를 호출하면 실행되는 프로토콜은 다음과 같다.

  • pc, 레지스터를 프로세스의 커널 스택에 저장한다.
  • 시스템콜에 전달할 인자와 시스템 콜 번호가 레지스터에 저장된다.
    ex. x86 instruction set
    시스템 콜 번호: EAX(Extended AX) 레지스터에 로드.
    시스템 콜 인자:
    EBX(Extended BX): 첫 번째 인자
    ECX(Extended CX): 두 번째 인자
    EDX(Extended DX): 세 번째 인자
    ESI(Extended SI): 네 번째 인자
    EDI(Extended DI): 다섯 번째 인자
    EBP(Extended BP): 여섯 번째 인자
    전달할 인자가 더 있으면, 사용자 스택에 저장된다.
  • trap instruction이라는 특권 명령을 실행하면(프로세스에서 어셈블리어 int 0x80실행), os의 예외 처리 메커니즘에 의해 trap interrupt가 CPU에 전달된다.
  • cpu의 특권레벨은 user mode에서 kernel mode로 상향조정된다.
  • kernel mode로 전환되면, kernel이 트랩테이블을 참조하고, 해당하는 트랩 핸들러로 분기한다.
    (트랩핸들러는 레지스터와 사용자 스택을 참조하여 인자를 받는다.)
  • 시스템 콜의 임무를 수행한다.
  • 커널스택으로부터 pc, 레지스터를 복원한다.
  • return-from-trap이라는 특권 명령을 실행하면, os의 예외 처리 메커니즘에 의해 cpu의 특권 레벨은 user mode로 전환된다.
  • pc로 분기한다.

1.1 트랩 인터럽트는 sw 인터럽트라고도 하며, 특정 예외 상황이나 중요한 동작을 처리하기 위해 의도적으로 발생시키는 인터럽트이다.

(x86에서 주로 소프트웨어 인터럽트 번호 0x80을 사용)
Unix os에서 gcc와 함께 설치된 표준c언어라이브러리에 포함되어 있는 write함수 내부에서 특권레벨을 상향조정시키는 경우를 예로들면 다음과 같다.

  • write함수는 내부적으로 syscall이라는 시스템콜 wrapper함수를 사용한다.
  • syscall함수는 내부적으로 int 0x80이라는 어셈블리어 명령을 사용한다.
    (인터럽트 번호 0x80은 CPU의 특권 레벨을 커널 모드로 바꾸는 역할을 한다.)
  • 어셈블리 명령어 int의 처리중에는 일시적으로 cpu의 특권레벨이 커널 모드로 전환된다.
    일반적으로는 int명령이 끝나면 원래의 특권레벨로 돌아간다.
    (해당 경우에는 0x80인터럽트가 특권레벨을 커널모드로 바꾸기때문에 원래 특권레벨로 돌아가지않음)
  • 인터럽트 디스크립터 테이블의 0x80 번째 항목을 참조한다. 이 항목은 시스템 콜을 처리하기 위한 인터럽트 서비스 루틴의 주소를 가리킨다.
  • 인터럽트 서비스 루틴은 os의 코드에 포함되어 있어서, os는 해당 작업을 수행하기 위해 필요한 커널 코드를 실행한다.
  • 최종적으로 0x80 인터럽트를 cpu에 전달한다.
  • CPU의 특권 레벨이 커널 모드로 바뀐다.

1.2 Trap(sw interrupt) & Interrupt(hw interrupt)

Trap와 Interrupt는 이벤트 처리 기법이라는 공통점이 있지만, 이벤트 발생 요인과 처리 방법이 다르다.
Trap은 동기적(synchronous)으로 이벤트를 처리하는 기법이며, Interrupt는 비동기적(asynchronous)으로 이벤트를 처리하는 기법이다.
1.2.1. Trap(동기적)

Trap은 주로 시스템 콜을 처리하는데 이용된다. 즉, Interrupt와는 달리 주로 현재 처리하고 있는 프로그램에 의해서 발생하게 된다.
이와같이 발생한 이벤트는 Trap Handler에 의해 처리된다. Trap handler에서는 Interrupt handler와 달리 동기적으로 이벤트를 처리하므로 현재 처리하고 있는 프로그램에 대한 Context를 저장할 필요도 없고 복원할 필요도 없다.
따라서 Trap이 발생하게 되어 Trap Handler가 특정 동작을 수행하고 나면, SP(Stack Pointer)와 PC(Program Counter)만을 이용하여 기존에 수행하던 동작으로 복귀할 수 있게 된다.
1.2.2. Interrupt(비동기적)

Interrupt는 주로 네트워크의 Packet 도착 혹은 I/O에 대한 이벤트를 처리하는데 이용된다.
Interrupt는 Trap과 달리 현재 처리하고 있는 프로그램에 의해 발생하는 것이 아니라 특정 하드웨어에 의해 발생하게 된다.
따라서 Interrupt는 하드웨어의 우선순위에 따라 우선순위를 가지게 되고, 하드웨어들이 동시에 Interrupt를 걸어도 적절히 처리가 가능하게 된다. 이와같이 발생한 이벤트는 Interrupt Handler에 의해 처리된다. Interrupt Handler에서는 Interrput Service Routine을 통해 이벤트를 처리하게 되는데, Interrupt를 처리한 뒤 복귀해야 하는 프로세스가 현재 프로세스가 아닐 수 있기 때문(원래 흐름과 비동기적)에 Interrupt Service Routine 진입 전에 반드시 Context에 대한 기록이 요구된다.
따라서 Interrupt에 대한 처리를 마치면 저장된 Context를 복원하여 중단된 시점부터 다시 프로세스를 처리하게 된다.
1.2.3. 비교
Trap의 경우에는 Trap Service Routine 내에서 처리 도중에 Interrupt를 받을 수도 있지만, Interrupt의 경우에는 Interrupt Service Routine 내에서 처리 도중에 Interrupt를 받을 수 없게 되어있다.
이는 Interrupt의 Depth가 깊어짐에 따라 하나의 Interrupt가 끝나는데 긴 시간이 요구되는 구조가 될 수 있기 때문이다.
결과적으로 Trap은 프로세스의 Context를 저장하지 않아도 된다는 면에서 Interrupt보다 가볍지만, Trap에 대한 처리가 완료될 때까지 Block된다는 특징이 있다. Interrupt는 Trap의 반대의 특징을 갖고 있다고 생각할 수 있다.
만일 Interrupt가 동시에 발생하면 우선순위에 따라 처리하는데, 동일한 우선순위를 가진 Interrupt를 받게 되면 운영체제와 하드웨어에 의해 하나의 Interrupt를 처리하고 나머지 Interrupt는 저장해두거나 무시하게 된다.

2.프로세스간 전환

cpu의 점유를 획득하기 위한 os의 스케쥴링 방식은 협조 방식과 비협조 방식으로 나눌 수 있다.

  • 협조방식
    과거의 몇몇 시스템에서 채택되었던 방식이다.
    그냥 우호적인 프로세스가 시스템콜(yield)으로 cpu점유를 포기하는걸 기다리는 것이다.

  • 비협조방식
    프로세스가 비협조적이더라도 cpu의 제어를 획득할 수 있는 방법이다.
    각 프로세스에 할당되는 최대 실행 시간인 time quantum을 정해두고
    타이머 인터럽트가 발생할 때마다 time quantum이 소진되었는지 확인한다.
    소진되었다면 현재의 프로세스를 종료시키고 다른 프로세스를 실행시킨다.

** 타이머 인터럽트는 타이머 장치에 정해진 주기마다 발생한다.
이를 통해 os는 일정한 주기로 작업을 수행하거나 스케줄링을 조정할 수 있다.

2.1 문맥교환(context switching)

협조적이든 아니든, os가 cpu를 다시 획득하면 어떤 프로세스를 계속 실행할 것인지 결정해야한다.
이 결정은 os의 scheduler에 의해 내려진다.
다른 프로세스로 전환되는 것이 결정되면, os는 context switching이라는 코드를 실행한다.

프로세스 전환이 필요한 시점의 context switching프로토콜은 다음과 같다.

  • 현재 실행 중인 프로세스A는 타이머인터럽트에 의해 중단된다. (cpu는 커널모드 변경)
  • 프로세스A의 레지스터 값을 커널 스택에 저장한다.
  • 트랩핸들러로 분기한다.
  • (전환이 필요한 시점이라면) switch() 루틴을 호출한다.
  • A의 커널스택에 있는 레지스터의 현재 값을 A의 pcb에 저장하고 다시 A는 PCB 테이블에 저장된다.
  • PCB 테이블에 있는 프로세스B로부터 프로세스B의 레지스터 값을 복원하고, 이를 B의 커널스택에 저장한다.
  • B의 커널 스택으로 전환된다.
  • return from trap특권명령이 실행된다.
  • B의 커널스택에 있는 레지스터 값에서 B의 레지스터를 복원한다.
  • cpu는 사용자 모드로 변경된다.
  • B의 pc로 분기한다.

** switch루틴에서 서로 다른 프로세스의 커널스택에 접근할 수 있는 이유
os는 프로세스 간의 문맥 교환을 위한 PCB 테이블이라는 데이터 구조를 사용한다.
PCB 테이블에는 각 프로세스의 PCB가 포함되어 있기때문에
switch 루틴은 os가 직접 관리하는 PCB에 접근할 수 있다.


https://github.com/remzi-arpacidusseau/ostep-translations/tree/master/korean
https://efilevol42.oopy.io/38ec9f78-a491-47c9-8021-79e48ca4456d
https://www.javatpoint.com/trap-vs-interrupt-in-operating-system

profile
https://songdaegeun.github.io/ 으로 블로그이전 중입니다

0개의 댓글