Skip to content
Go back

[OSTEP]Virtualization-CPU Mechanisms 제한적 직접 실행 원리

Edit page

동시에 작업을 실행시키기 위해 CPU를 시간을 나누어 씀 방식으로 자원을 공유해 가상화를 구현한다.

운영체제는 가상화를 통해 효율적이게 운영을 하고있다 하지만 가상화를 하면서도 프로세스에 제어권을 잃지 않아야한다.

종종 프로세스는 하드웨어에 대한 제어가 필요하다 이에 대한 제어권은 운영체제가 잘 통제해야한다. 제어권을 잃어버린다면 영영 프로세스가 종료되지 않거나, 접근권한을 넘어버릴 수 있다.

그래서 운영체제는 성능저하 없이 제어권을 유지해야한다.

기본 원리 : 직접 실행

직접 실행은 간단하다. 프로그램을 그냥 CPU에서 실행시킨다. 그러면 아래의 표와 같이 동작한다.

운영체제프로그램
프로세스 목록의 항목을 생성
프로그램 메모리 할당
메모리에 프로그램 탑재
argc/argv를 위한 스택 셋업
레지스터 내용 삭제
call main() 실행
main() 실행
main에서 return 명령어 실행
프로세스 메모리 반환
프로세스 목록에서 항목 제거

간단하고 사용하기 쉽게 된다. 그런데 과연 가상화로 CPU 시분할은 어떻게 구현되는 가? 시분할을 하려면 프로그램을 중간에 중단 시켜야하는 데 그럴 방법이 지금 보이지 않는다.

문제점: 이 방식은 프로그램이 원하는 모든 작업을 수행할 수 있어 위험

문제: 제한된 직접연산

사용자 모드와 커널 모드

운영체제는 두 가지 실행 모드 제공

시스템 콜 메커니즘

프로세스가 특정 작업(I/O, 메모리 할당 등)을 수행해야 할 때:

  1. trap 명령어로 커널 모드 진입
  2. 운영체제가 요청 처리
  3. return-from-trap으로 사용자 모드 복귀

제한적 직접 실행 프로토콜

운영체제 @부트 (커널 모드)하드웨어
트랩 테이블을 초기화한다
syscall 핸들러의 주소를 기억한다
운영체제 @실행 (커널 모드)하드웨어프로그램 (사용자 모드)
프로세스 목록에 항목을 추가한다
프로그램을 위한 메모리를 할당한다
프로그램을 메모리에 탑재한다
argv를 사용자 스택에 저장한다
레지스터와 PC를 커널 스택에 저장한다
return-from-trap커널 스택으로부터 레지스터를 복원한다
사용자 모드로 이동한다
main으로 분기한다
main() 실행

시스템 콜을 호출한다
트랩 핸들러로 분기한다레지스터를 커널 스택에 저장한다
커널 모드로 이동한다
syscall의 작업을 수행한다
return-from-trap커널 스택으로부터 레지스터를 복원한다
사용자 모드로 이동한다
트랩 이후의 PC로 분기한다
main에서 리턴한다
trap(exit()를 통하여)
프로세스의 메모리를 반환한다
프로세스 목록에서 제거한다

return-from-trap 을 할 때 각 프로세스의 사용하던 레지스터들의 값 같은 것들을 저장해 사용자 프로세스로 제대로 리턴시켜준다. x86 시스템의 경우 **커널 스택(kernel stack)**에 저장한다. 그리고 return-from-trap실행 시 커널 스택에서 pop을 해서 반환한다.

트랩 테이블(trap table)

컴퓨터 부팅 시 생성되며, 운영 체제가 하드웨어에 트랩 핸들러(trap handler) 위치를 알려준다.

트랩 핸들러를 통해 하드웨어에서 어떠한 인터럽트 발생 시 하드웨어는 무엇을 할 수 있는지 알수 있다.

트랩 테이블을 활용해 트랩 핸들러 위치를 알려주는 것 또한 특권 명령어로 사용자모드로 실행 시 실행되지 않아야한다.

문제: 프로세스 간 전환

프로세스는 CPU에서 동작한다. 그 기간동안 운영체제는 실행되지 않는다. 그렇다면 어떻게 운영체제가 다시 CPU를 획득할 수 있는가?

협조 방식: 시스템 콜 기다리기

낙관적으로 프로세스가 CPU의 점유하는 것을 기다리기

운영체제가 CPU 점유를 얻을 수 있을만한 시나리오

  1. 프로세스에서 시스템 콜을 통한 운영체제 CPU 획득
  2. 프로세스에서 비정상적인 행위(0으로 나누기)로 인한 운영체재로 Trap 발생 -> CPU 획득

비협조 방식: 운영체제가 전권을 행사

프로세스가 시스템 콜 혹은 실수를 하지 않는다면 운영체제는 할 수 있는 것이 없다.

타이머 인터럽트(Timer Interrupt)

타이머 장치가 일정 시간마다 인터럽트를 발생시켜 운영체제는 **인터럽트 핸들러(interrupt handler)**로 CPU를 다시 점유한다.

물론 부팅 시 하드웨어에게 인터럽트 발생 시 실행해야할 코드를 알려주어야한다.

시스템 콜과 유사하게 동작된다.
인터럽트 발생 -> 상태 저장 -> 운영체제 -> return-from-trap -> 프로그램 다시 시작

문맥 저장/복원/교한

인터럽트 된 후 2가지 선택이 있다

  1. 기존 프로세스를 실행
  2. 새로운 프로세스 실행

운영체제의 스케줄링에 따른다. 새로운 프로세스가 시작되면 기존 레지스터 값들은 커널 스택에 저장되고 다음프로세스가 시작된다.

// 부팅 시
1. 타이머 인터럽트 핸들러 등록
2. 타이머 시작 (예: 10ms마다 인터럽트)

// 실행 중
1. 타이머 인터럽트 발생
2. 현재 프로세스 상태 저장
3. 스케줄러가 다음 프로세스 결정
4. 문맥 교환(Context Switch) 수행

xv6의 문맥 교환 코드 예시:

void switch(struct context **old, struct context *new) {
    // 1. 현재 프로세스의 레지스터를 old에 저장
    // 2. new로부터 새 프로세스의 레지스터를 복원
    // 3. 스택 포인터를 새 프로세스의 커널 스택으로 전환
}
운영체제 @부트 (커널 모드)하드웨어
트랩 테이블을 초기화한다
syscall 핸들러, 타이머 핸들러의 주소를 기억한다
인터럽트 타이머를 시작시킨다
타이머를 시작시킨다
X msec 지난 후 CPU를 인터럽트한다
운영체제 @실행 (커널 모드)하드웨어프로그램 (사용자 모드)
프로세스 A
타이머 인터럽트
A의 레지스터를 A의 커널 스택에 저장
커널 모드로 이동
트랩 핸들러로 분기
트랩을 처리한다
switch() 루틴 호출
A의 레지스터를 A의 proc 구조에 저장
B의 proc 구조로부터 B의 레지스터를 복원
B의 커널 스택으로 전환
return-from-trap (B 프로세스로)
B의 커널 스택을 B의 레지스터로 저장
사용자 모드로 이동
B의 PC로 분기
프로세스 B

조금 더?

생각할게 많다… Lock 과 같은 동시성 얘기를 할 수 있을 것 같다.


Edit page
Share this post on:

Previous Post
[System Design Interview] CH3. 시스템 설계 면접 공략법
Next Post
[OSTEP]Virtualization-Process API