여행다니는 펭귄

왜 Direct Execution을 하지 않을까? 본문

컴퓨터/운영체제

왜 Direct Execution을 하지 않을까?

핑구힝구 2021. 10. 8. 03:06
1. CPU Virtualization
2. Limited Direction Execution
3. Context Switch

 


1. CPU Virtualization

Mechanism vs Policy
Process States
Data Structures
How to Efficiently Virtualize CPU?
Direct Execution

 

저희 저번시간에 CPU Virtualization에 대해 간단하게 소개했었죠?

우리가 CPU virtualization을 어떻게 적용 할 수 있을지 한번 봅시다.

 

Mechanism VS Policy

policy라고 하는것은 뭘 할지를 정의하는 것입니다, mechanism이란 어떻게 할지 정의하는 것이죠.

CPU virtualization에서의 저 둘은 뭘까요?

 

Mechanism : Context Switch

Policy : Scheduling Policy

 

입니다.

 

policy와 mechanism을 구분하는 것이야 말로 System 디자인에 있어서 매우 중요한 부분이니, 이 개념을 기억해 두시길 바래요.

Virtual machine and Policy

Policy가 다른 Operating System을 돌리려 하면 어떻게 될까요? (ex : Cache Policy)

원칙적으로 응용은 Policy를 건드릴 권한이 없습니다.  그래서 Operating System 위에서 한번 더 page cahce 정책을 펼치기 때문에 Overhead가 심하죠.

이것을 해결하는 방법을 바로 pass through 라고 합니다.

pass through를 통해 Direct로 OS를 제치고 hardware와 통신 할 수 있게 됩니다.
이를 Full virtualization이라고 하며 이의 반댓말은 반가상화 입니다.

Process State

프로세스의 상태는 세개로 구분됩니다.

 

Running, Ready, Blocked 이죠.

Running 프로세서 위에서 돌아가고 있는 상태
Ready 돌아가기를 기다리고 있는 상태이지만, OS가 돌리지 않기로 결정한 상태
Blocked 프로세스가 I/O 에 요청을 해서, 실행 될 수 없는 상태로 그 동안엔 다른 프로세스가 프로세서를 차지함

이 프로세스의 상태는 계속 Transition 되니 기억해 주세요

Data Structures

Operating System은 모든 Process에 대한 정보를 알아야 합니다. (Ready ,Block, Current running Process)

Register context도 전부 기억해야하죠. (Data Stack register, Program counter ..)

그래서 이 모든것을 저장하기 위한 data structures가 존재합니다.

 

Process Control Block (PCB) 은 이 data structure을 부르는 말입니다.

 

In XV6

// the information xv6 tracks about each process
// including its register context and state
struct proc {
    char *mem; // Start of process memory
    uint sz; // Size of process memory
    char *kstack; // Bottom of kernel stack// for this process
    enum proc_state state; // Process state
    int pid; // Process ID
    struct proc *parent; // Parent process
    void *chan; // If non-zero, sleeping on chan
    int killed; // If non-zero, have been killed
    struct file *ofile[NOFILE]; // Open files
    struct inode *cwd; // Current directory
    struct context context; // Switch here to run process
    struct trapframe *tf; // Trap frame for the current interrupt
};
// the registers xv6 will save and restore
// to stop and subsequently restart a process
struct context {
    int eip; // Index pointer register
    int ebx; // Called the base register
    int esi; // Source index register
    int edi; // Destination index register
    int ebp; // Stack base pointer register
}; 
// the different states a process can be in
enum proc_state { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };

How to Efficiently Virtualize CPU?

OS는 하나의 CPU를 여러개인 거처럼 Process가 쓸 수 있게 Time Share을 해야합니다.

여기에는 여러가지 문제점이 있지만, 두개만 다뤄보도록 할게요.

 

첫번째로 프로세스의 제어 권한 문제입니다.

만약 실행중인 프로세스가 OS에게 해로운 프로그램이면 어떡할까요? 또 CPU를 잡고 놔주지 않는다면 어떡할까요?

두번째는 성능 문제입니다. Context Switch가 일어날때 오버헤드가 걸리는데 이를 어떻게 해결할까요?

 

일단 저희는 첫번째 이슈에 대해 다뤄보도록 하겠습니다.


Direct Execution

OS Program
프로세스의 리스트를 만들기
메모리 할당시켜주기
프로그램 메모리에 올려서 프로세스로 만들기
argc/ argv 스택 만들기
레지스터 청소하기
main() 부르기




  main() 실행하기
main 에서 Return 받기
프로세스에서 메모리 자원 반환하기
프로세스 리스트에서 제거하기
 

프로세스가 모든 코드를 직접 실행하도록 하면 어떤 문제가 생길까요?

OS는 Context Switch도 못할 것이고 어떤 제어권한도 프로세스가 넘겨 주기 전에는 받을 수 없습니다.

이러면 악의적인 프로그램일 경우에 저희가 이를 대처할 방안이 없죠.

 

 

 

그럼 OS는 어떻게 해야 할까요?


2. Limited Direct Execution

Restricted Operation
System Call
Trap, Trap example
User Stack and Kernel Stack
Limited Direction Execution Protocol

 

Restricted Operation

만약 process가 위험한 operation을 시도하면 어떡할까요?

예를 들어 제가 master boot record에 뭔가를 쓰려고 한다면, 이건 컴퓨터 자체를 망가트릴 수도 있겠죠?

아니면 cpu 의 제어 권한을 얻어서 timer를 끈 후 정지시켜버린다던지 등의 기본 시스템을 망가트리는 아주 위험한 연산을 시도한다고 합시다.

 

제가 하는거면 모르겠지만, 타인의 프로그램이 제가 의도하지 않은 이런 일을 하면 어떡할까요?

고로 우리는 그 제어 권한을 완전히 넘겨주지 않고 Operating System이 갖도록 해야 합니다.

 

이는 바로 Kernel modeUser mode를 구분하는 것으로 이루어지죠.

 

System Call

System Call 이란 위에서 예시를 든 위험한 연산들을 명령어로 정의 내려 둔 것입니다.

이 System Call은 아래 그림과 같이 System Call Interface와 OS Kernel을 통해 사용이 되는데, 이러면 어떤 장점이 있을까요?

Process가 바로 Hardware에게 명령을 내리는 것이 아니라 Process가 OS kernel에게 System Call을 통해서 연산을 요청하고, Hardware에게는 OS Kernel만이 명령을 내릴 수 있기에 부적절한 명령이라고 판단이 된다면 OS차원에서 끊어낼 수 있습니다.

 

또한, SysCall에 위험한 명령어를 Reserve하지 않으면 아에 사용할 수 없죠.

 

그렇다면 System Call은 어떤 방식으로 실행이 될까요?

 

Trap(User -> Kernel), Return-from-Trap(Kernel -> User)

System Call이 실행되면 Trap을 통해 권한을 옮길 수 있습니다. 이 옮기는 과정은 Hardware(=CPU)에 reserved된 function을 통해서 실행이 되구요.

 

이렇게만 말해서는 Process에서 Trap이 뭔지 이해가 잘 안 되죠? 실제 Code를 통해서 보도록 합시다.

Trap Examples

file open()/ write() / exit()

 

파일열기를 수행하려면 int라는 System Caller를 불러와야 하는데 이게 구체적으로 어떻게 될까요?

 

User Process   OS
int (Syscall number) trap  
    open file
pop ( 그 다음 연산 ) return  

좀 더 자세한 example을 볼까요?

 

main:
    // First , call write (1, "hello World\n" , 13)
    movl $4, %eax // Syscall number 4
    movl $1, %ebx // stdout has descriptor 1
    movl $string, %ecx // Hello world string
    movl $len, %edx // String length
    int $0x80 // System call code
    
    // Next, call exit(0)
    movl $1, %eax // Systemcall number 1
    movl $0, %ebx // Argument is zero
    int $0x80 // System call code

eax에 syscall number을 넣어주고 나머지 register(6개) 에  arbitary arguments를 넣어줍니다.

 

6개 이상이면 Stack에 저장하여 사용하지만, 이는 그렇게 추천되지 않으므로

Syscall maximum parameter은 6개인것으로 기억을 해 줍시다.

 

Trap table이란 System Call Code가 저장되는 주소를 기록한 표 입니다.

그렇게 중요하지 않으므로 넘어갑시다.

 

User Stack and Kernel Stack

 

Stack 역시도 User Space와 Kernel Space가 구분되어 있습니다.

Kernel Stack 에서는 모든 Process의 User State를 저장하여 준 상태로 둡니다.

추후 로드 할때 제어 흐름을 유지할 수 있도록요.

 

Limited Direction Execution Protocol

그럼 전체적으로 이 모든 움직임이 어떻게 일어나는지 한번 정리하여 봅시다.

 

 


3. Context Switch

Switching Between Processes
A cooperative Approach
Non cooperative Approach
Context Switch
Limited Direction Execution Protocol (Timer interrupt)

Switching Between Processes

Context Switch를 하려면 어떻게 해야 할까요?

OS가 프로세스를 멈추고, 다른걸 돌리는걸 마음대로 할 수 있어야겠죠. 그런데 아까 보면, main을 돌리는 와중에 System Call을 부를때가 아니면 Process가 권한을 가지고 있던데 이를 어떻게 제어할까요?

 

A cooperative Approach

일단 모든 Process가 ideal 하다고 가정을 해 봅시다. 

이럴때 Context Switch를 하려면, 각 프로세스 제작자들이 yield()open()같은 System Call을 사용해서 주기적으로 CPU 점유를 다른 Process에게 양보하면 됩니다.

 

yield() 는 OS에게 제어 권한을 넘기는 명령어 입니다. 이것 이외에도 Divide by zero, 나 memory acess 권한이 없는 곳에 접근하면 OS에게 역시 제어 권한이 넘어갑니다.

A Non-cooperative Approach

그런데 모든 Process가 ideal 할 가능성이 높을까요? 아닐 겁니다.

그리고 그렇도록 짜려면, 프로그래머의 피로도가 굉장히 높게 올라가겠죠?

고로 OS 차원에서 관리하는 방법을 사용해야 합니다.

 

A timer interrupt 는 강제로 권한을 뺏어오는 방법입니다.

일단 OS가 boot 되자마자, timer 를 실행합니다. 여기서 이 timer은 매 밀리초마다 interrupt를 발생시킵니다.

이 interrupt가 발생하면 어떻게 될까요?

 

일단 현재 돌고 있는 프로세서가 정지합니다. -> program의 state가 Kernel Stack에 저장됩니다. ->  OS안에 미리 구성된 interrupt handler가 동작합니다 -> OS가 권한을 얻습니다.

 

이 방식으로 권한을 얻고 난 이후, OS 내부의 SchedulerContext Switch를 할지 말지 결정하는거죠.

Context Switch

Context Switch는 사실 별게 아닙니다. 매우 낮은 level의 assembly code일 뿐이죠.

다만 이것이 세개의 일을 하는데 이 부분은 기억해 두어야 합니다.

 

1. 현재 실행되고 있는 프로그램의 register value를 Kernel Stack에 저장

2. Kernel Stack에 곧 실행될 Process의 정보를 불러옵니다. 

3. Kernel stack을 실행될 프로세스를 위해 Switch 합니다.

 

Limited Direction Execution Protocol (Timer interrupt)

전체적으로 어떻게 굴러갈지 정리해볼까요?


와 같이 구동합니다.

 

오늘은 Mechanism에 대해 배웠으니 다음시간에는 Scheduling Policy에 대해 배워보도록 하겠습니다.

 

더보기

더보기

 

 

'컴퓨터 > 운영체제' 카테고리의 다른 글

Concurrency는 뭘까요?  (0) 2021.10.17
Scheduling Policy는 어떻게 짜야 할까요?  (0) 2021.10.17
Process 란 무엇일까?  (0) 2021.09.28
Operating System 이란?  (0) 2021.09.28
Operating System : 운영체제  (0) 2021.09.27