CPU를 효율적으로 가상화하는 방법
-> OS가 time sharing을 통해 물리적인 CPU를 공유해야 한다.
여기서 발생하는 두가지 Issue
1. 성능: 시스템에 과도한 overhead 없이 가상화를 구현하려면 어떻게 해야 하는지
2. 제어: CPU에 대한 제어를 유지하면서 어떻게 효율적으로 프로세스를 실행하는지
01. Direct Execution
CPU에서 직접 프로그램을 실행하는 방법이다.
OS | Program |
1. proc list에 대한 entry 생성 (PCB) 2. prog에 대한 메모리 할당 3. 프로그램을 메모리에 Load 4. argc, argv로 stack setup 5. register clear 6. Execute main() --------------------------------proc 생성 과정 9. proc의 메모리 free 10. proc list에서 해당 proc 제거 |
7. Run main() 8. main()에서 return 실행 |
위처럼 프로그램 실행에 제한이 없다면, OS는 해당 프로세스가 끝날 때까지 아무것도 할 수가 없다. (Time sharing 불가, 그냥 OS는 library 됨)
또한 이 방법은 빠르지만 아래와 같은 문제점들이 있다.
문제점 1: 제한된 작업
- 프로세스가 디스크에 I/O 요청 같은 제한된 작업을 수행하려고 하는 경우 불가
- CPU나 메모리 같은 더 많은 시스템 자원에 대한 access권한 얻고자 하는 경우 불가
-> 그럼 프로세스가 위 작업들을 할 수 있도록 하자 -> 프로세스가 맘대로 disk 접근 -> 보호 문제 발생
=> 아래와 같은 Protected Control Transfer(보호제어전송) 사용으로 해결
- User mode: Applications은 하드웨어 자원에 대한 full access 권한 xx
- Kernel mode: OS가 실행됨, 머신의 전체 자원에 access 할 수 O (제한된 작업 가능)
효율적인 가상화를 하기 위해서는 OS가 각 프로세스에 대해 제어권을 가지고 있어야 한다. 제어권은 처음 만들어진 프로세스에게 넘겨준 상태이다. 그 프로세스가 끝나기 전에 다시 OS가 제어권을 뺏기 위해서는, 프로세스가 I/O나 Memory access같이 제한된 작업을 할 때 CPU한테 허락받고 하도록 만들어서 그때 뺏으면 된다. (이후 문제점 2의 내용)
이때 이용하는 것이 시스템콜이다.
02. System Call
시스템 콜은 user mode에서 kernel mode에서만 할 수 있는 일을 하고 싶을 때 사용한다.
Kernel -----------------(주요 기능들을 신중히 드러낼 수 있도록 허용해 줌)----------------> User Program
이때 주요 기능들은 아래와 같다.
- File System Access (open / read / creat ...)
- 프로세스 생성 및 삭제
- 다른 프로세스와의 통신
- 더 많은 메모리 할당
위 기능들은 본래 User Program에서 함부로 사용할 수가 없지만 System call을 통해 Kernel에서 안전하게 갖고 오는 셈이다. 따라서 User와 Kernel mode들을 왔다 갔다 하기 위해서 'Trap'이라는 것이 필요하다.
- Trap
- Kernel로 jump 하는 명령어
- 권한 level을 kernel mode로 높임
- Return-from-trap
- user program으로 돌아가는 명령어
- 권한 level을 user mode로 다시 낮춤
이러한 Trap 명령어를 쓸 때마다 OS 내부에서 어떤 코드를 실행하는지 알 수 있는 방법은 다음과 같다.
- Trap table (=interrupt descriptor table = interrupt vector table)
- 부팅 시 초기화, 여러 함수들 들어있음(각 함수는 system call number 정의되어 있음)
- Trap 명령 -> 프로세스는 program counter, flags, register 정보들을 kernel stack에 저장 -> return-from-trap 명령 수행 시 해당 데이터 모두 제거
- Trap handler: 프로그램이 trap 명령어를 실행할 때 코드가 실행되어야 함
- System-call number: 각 시스템 호출에 할당되며, user code는 원하는 시스템 호출 번호를 register에 배치하는 역할을 함
03. System Call Handling
04. Limited Direction Execution Protocol
하나의 프로세스가 끝날 때까지 OS는 아무것도 할 수 없고, 프로세스도 I/O 작업은 못하는 Direct Execution의 문제점을 보완하기 위해, user mode / kernel mode로 구분해서 trap을 통해 전환되게 하는 것이 바로 Limited Direction Execution이었다.
부팅할 때 OS가 trap table을 생성 -> 프로세스 생성 -> user mode에서 코드 수행 -> system call 발생 -> trap -> kernel mode 수행 -> return-from-trap -> user mode 수행 -> ... -> 작업 완료 시 프로세스 제거
Boot
OS @ boot (kernel mode) | Hardware | |
initialize trap table (부팅 시 초기화) |
remember address of ... syscall handler |
Creat Process
OS @ run (kernel mode) | Hardware | Program (user mode) |
creat entry for process list (PCB) Allocate memory for program Load program into memory Setup user stack with argv Fill kernel stack with reg/PC return-from-trap |
restore regs from kernel stack (복원) move to user mode jump to main |
Run main() |
Trap
Handle trap Do work of syscall return-from-trap |
save regs to kernel stack move to kernel mode jump to trap handler restore regs from kernel stack move to user mode jump to PC after trap |
... Call system trap into OS ... return from main trap (via exit()) |
Remove Process
Free memory of process Remove from process list |
위 설명은 하나의 프로세스를 실행할 때의 과정을 나타낸 것이었다. 하지만 여러 개의 프로세스를 실행한다면, 또 다른 문제점이 발생한다.
문제점 2: Switching Betweem Process
CPU 가상화는 time sharing을 통해 빠른 시간 내에 CPU time을 돌아가면서 써서, 사용자가 보기에는 마치 동시에 실행되는 것처럼 보이는 것이다. 이때 여러 프로세스들을 돌아가면서 쓰기 위해서는 OS가 CPU 제어권을 그때마다 프로세스에게서 가져와야 한다. 가져오는 방법은 두 가지가 있다.
- 협력적 방식: wait for system call - 'yield(제한된 작업 시, 또는 divied by 0와 같은 비허용 작업 시 넘어감)'와 같은 시스템콜을 통해 주기적으로 제어권을 양보하도록 할 수 있다. (시스템콜 발생 시 OS로 제어권이 넘어옴, 하지만 프로세스가 시스템콜을 발생시키지 않을 경우 OS에게 제어권이 넘어오지 않을 수 있음. 이는 방법 2로 해결)
- 비협력적 방식: Timer interrupt - 일정 시간마다 interrupt(하드웨어 사건을 OS에게 알림)를 발생시켜서 그걸 처리하기 위해 OS가 제어권을 가지도록 하는 방식이다. (ex: 프로그램이 100초 실행하고 싶은데 time interrupt가 1초마다 발생할 경우, 100번의 time interrupt가 발생했을 때 OS가 제어권을 가지게 됨)
05. Context Switch
위 두가지 방법으로 해결할 수 있을 것 같지만 아직 문제가 남았다. 프로세스들을 전환시킬 때, 해당 프로세스의 정보를 기억했다가 돌아오면 다시 거기서부터 시작하도록 하는 것을 context switch라고 한다.
context == register 값, 정보들
- low-level 어셈블리 코드
- register의 집합
- kernel stack pointer
- 현재 프로세스의 레지스터 값을 kernel stack에 저장함 (context save)
- 곧 실행될 프로세스의 kernel stack에서 값을 복원함 (context restore)
06. Limited Direction Execution Protocol (timer interrupt, context switch)
부팅 시 trap table(+interrupt table같이 처리) 초기화 -> interrupt timer 실행 -> Proc A 실행 -> timer interrupt 발생 -> register에 context 저장 -> Proc B의 context 복구 -> Proc B 실행
Boot
OS @ boot (kernel mode) | Hardware | |
initialize trap table start interrupt timer |
remember address of... syscall handler time handler start timer interrupt CPU in X ms |
Interrupt
OS @ run (kernel mode) | Hardware | Program (user mode) |
timer interrupt save regs(A) to k-stack(A) move to kernel mode jump to trap handler |
process A ... |
context switch
Handle the trap call swich() routine save regs(A) -> proc_struct(A) restore regs(B) <- proc_struct(B) switch to k-stack(B) retrun-from-trap (into B) |
return from interrupt
restore regs(B) from k-stack(B) move to user mode jump to B's PC |
Process B ... |
07. Concurrency
위 방법들을 써도 완전히 문제가 해결된 것은 아니다. 여러 프로세스를 실행할 때, 하나의 interrupt 또는 trap 처리 중에 다른 interrupt가 발생하면 동시성의 문제가 생긴다. 이는 아래와 같은 방법으로 해결할 수 있다.
- Disable interrupt: interrupt 처리 중에 발생하는 다른 interrupt는 무시
- Locking: 여러 정교한 Lock 기법을 사용하여 내부 데이터 구조에 대한 동시 액세스 보호 (하나 실행 중일 땐 다른 거 못 들어오게 함)
'🌙CS > 운영체제' 카테고리의 다른 글
05~12 중간 범위 정리 (0) | 2024.10.26 |
---|---|
[Linux] System Call 추가하기 (0) | 2024.09.23 |
03. Process API (0) | 2024.09.14 |
02. Processes (0) | 2024.09.13 |
01. Operating System (OS) (0) | 2024.09.13 |