딩굴댕굴

운영체제의 기초 - 6. Processes and Threads 3

by jennysgap

BOX

Context Switching : Abstraction and Decomposition

구현하기 위해 생각해야 할 것

Program = Data Structure + Algorithm


OS를 이해하기 위해 이해해야 할 것

Complexity = Abstraction + Decomposition


Abstraction을 도입해서 S/W 시스템을 설계해보면 자연스럽게 Layered Architecture가 나오게 됩니다.

Layered Architecture 바닥에는 H/W가 있고 그 위에 OS 있고 그 위에 Middleware가 있고 그 위에 Application들이 수행하게 됩니다.


이 Layered Architecture에서 각 Layer의 역할은 뭘까요?

OS의 역할은? 아래 복잡한 H/W를 보이지 않게 해서 꼭 필요한 인터페이스만 위로 제공해 주는 것


Layer와 Layer가 만나는 계층엔 API가 존재함

API(Application Programming Interface): 계층(Layer)과 계층 사이의 통신을 위해 정의된 호출 규약


지난 시간에 커널모드로 crossing하는 방법을 배웠음

커널 안에 있는 함수를 호출하는 방법!System call이라고 말했음

OS의 API는 system call들임

이와같이 아래 계층의 복잡함을 추상화시켜서 단순하게 보여진 인터페이스를 위 계층에 제공하는 것 (Abstraction)

그 결과로 Layered Architecture가 존재하게 됨


아래계층은 위계층에게 인터페이스를 제공해 줌 (추상화를 제공해 줌)

위계층은 아래계층에서 제공한 인터페이스를 사용함 

Layering Principle: 위의 계층은 아래 계층이 제공하는 기능만 사용할 수 있으며 반대의 경우는 발생하지 않아야 함


Layering principle이 필요한 이유

OS이 있으면 그 위에 여러 타입의 application들 여러 타입의 middleware가 올 수 있게 허용해줘야 하기 때문

그런데 OS이 위 계층에 종속적이라면 그 종속적인 것 외에는 받아들일 수 없기 때문에 이것이 Layering principle이다.


정리

Abstraction을 사용하게 되면 Layered Architecture나오게 되고

그 Abstraction이 완벽하게 구현이 되려면 Layering principle을 따라야 된다는 것이 기본 핵심이다


Layered Architecture의 아래 Layer로 뚫고 들어가는 것

이것이 synthesis(설계), implementation(구현) 

즉, 컴퓨터 시스템 설계에서 Layered Architecture를 만나게 되면 제일 이해하기 쉬운 윗 layer에서 시작해서 그 아래 layer로 점점 구체화 시켜서 내려가야 됨


구체화작업은 어떻게 하느냐 그것은 Decomposition임


OS는 전체를 이해하려면 어렵지만

OS는 5조각으로 나눠져 있다

1. CPU를 관리하는 Process Scheduler

2. File System

3. Memory

4. I/O

5. Network



Context Switching : Process Creation and Termination 1

요약

한 프로세스에서 다른 프로세스로 넘어갈 때 커널이 개입한다고 했죠?

그 커널이 개입을 해서 수행되는 코드가 Dispatcher코드였고 이것은 Context Switching을 한다는 것이었습니다.

Dispatcher가 수행되기 위해서 하드웨어적 메커니즘인 interrupt가 필요함

그런데 interrupt 타입에는 두가지가 있습니다.

H/W interrupt와 S/W interrupt

H/W interrupt에 의해서 CPU를 빼앗기게 되면 그것을 우리가 preemptive Scheduling이라고 했음

자기의 의지와 상관없이 겸손하게 CPU를 빼앗기는 것 preemptive Scheduling이 발생하려면

OS 로직이 돌아야하기 때문에 asynchronous하게 H/W interrupt가 필요함

S/W interrupt에 의해서 발생하는 스케줄링은 Non-preemptive Scheduling임

어떻게 해야 발생하느냐? I/O를 기다리는데 I/O가 끝나려면 오래 걸릴 거 같다 

그렇다면 CPU를 양보해야 겠죠. 그럴때 오히려 OS에게 S/W interrupt를 걸어서 CPU를 가져와라라고 얘기하는 것


가장 중요한

Interrupt와 Interrupt service routine을 이용해서 Context가 교체되는 것을 배웠음


새로운 프로세스가 생성이되면 OS가 그 프로세스의 Context를 다 build해 줘야된다.

어떤 프로세스가 Context Switching을 하려면 Context Switching에 필요한 게임의 법칙을 만족하도록 fake stack 을 만들어 줘야 함

이렇게 새로운 프로세스를 만드는 작업이 Creation이 됨



process를 creation 되려면

- 파일시스템에서 대상이 되는 실행가능한 파일의 경로가 전송되어야 함

- OS가 code segment에 읽어들이고

- 글로벌 변수들의 선언과 정보들을 기반으로 data segment의 각 영역을 잡음 (여기까지가 Load)

- memory segment의 stack, heap도 생성

- 그 프로세스의 정보를 담은 PCB를 malloc해서 필요한 정보를 채움

- 생성된 PCB를 Ready queue에 담는다


유닉스계열은 0번만 이렇게하고 나머지 Process는 전부 다른 방법으로 생성함

두번째 이후로는 복제(cloning)를 이용해 프로세스 생성 -> fork() system call



유닉스계열은 프로세스가 생성이 되려면 그 프로세스를 생성하라고 명령을 내리는 다른 프로세스가 있어야 함

그래서 나를 만들어준 프로세스를 Parent Process라 하고 만들어진 프로세스를 Child Process라고 함


Parent ProcessProcess Cloning을 초래하는 기존의 Process

Child Process: Parent Process로부터 만들어지는 새로운 Process


Child Process가 생성되는 과정

- Parent Process가 Child Process를 만들기 위해서는 fork() system call을 호출함

- OS이 fork()를 호출한 Parent Process를 완전히 딱 중단시킴

- 그리고 걔가 가지고 있는 Context들을 snapshot(사진) 찍음

- 사진 찍은 Context를 그대로 copy함 


물론 PCB(Process Control Block)에 들어가는 Process 고유번호 (PID)는 다른 값을 갖음

그렇지만 PID를 제외한 모든 다른 값을 그대로 복제하기 때문에 

Parent Process와 Child Process가 똑같은 형태로 생성이 됨


Child Process에 PCB을 Ready queue로 보내게 되면

fork() system call의 역할은 끝나고 다시 Parent Process로 return이 됨


이런식으로 해서 딱 1개의 프로세스 zero만 hand-crafting을 하고 나머지는 전부 복사에 의해서 프로세스 Creation을 하게 됨


Q. fork() System Call이 가지는 문제점은?

A. 매번 모든 Context의 복사본을 만드는 것은 매우 비효율적이다.

- non functional requirement - 성능, 오버헤드

A. 맨 처음 만든 Process와 다른 Process는 수행할 수 없다.

- functional correctness 문제: 맨 처음 만든 프로세스와 다른 프로세스는 수행할 수 없다


그래서 유닉스계열은 fork()를 한다음에 exec() another system call를 호출합니다.

exec()의 변수는 생성된 프로그램의 코드와 데이터를 이미 가지고 있는 코드와 데이터를 overwrite해서 새로운 프로그램으로써 출발하게 함

그러나 이것은 성능적으로 문제가 많음 (다 복사해 왔는데 무시하고 덮어쓰기 때문)



- Parent Process가 fork()를 함

- fork()를 하는 시점에 Child Process는 Parent Context를 그대로 copy하고 exec()을 수행함

- exec()을 수행함으로써 Parent와 Child가 다른프로그램을 수행하는 게 됨

- Parent와 Child는 서로 wait()에서 한번 만남 


- 부모는 fork한 후 wait() system call함 

- wait() system call의 argument는 자기가 생성한 자식프로세스의 ID

- wait()은 파라미터로 준 프로세스가 수행을 종료할 때까지 나를 기다리게 하라라는 의미임


- 자식은 exec()해서 끝나면 exit() system call을 함

- 어떤 프로세스가 exit()을 한다는 의미는 그 프로세스가 가지고 있는 data structure, resource를 OS가 걷어가는 것을 뜻함 (exit이 잘됐는지 확인하는 code값 빼고)

- 그 코드값을 시널로 부모프로세스에 전달해서 깨움

- 깨어난 부모프로세스는 나머지를 수행

- Zombie State: exit()을 마치고 Parent Process가 자신의 Exit Status를 읽어가기를 기다리는 Process의 상태



Context Switching : Process Creation and Termination 2


Shell 또는 CLI (Command Line Interpreter)

사용자가 입력한 명령어를 입력으로 받아들여 새로운 프로세스를 수행시키는 프로그램


- readcmd(): 사용자에게 prompt를 보내주고 명령어를 넣어라는 함수

- fork(): 그 프로세스를 수행시킬 Child 프로세스를 만들게 됨

- system call을 호출해도 다른 함수들 처럼 return value가 있음. 그 값이 음수면 에러상황임

- pid: fork() system call에 의해서 생겨난 child 프로세스의 ID를 말함

- wait(pid): //부모프로세스가 자식프로세스 종료될 때까지 대기


- pid == 0: //자식프로세스가 수행되는 부분(부모와 동일한 코드 수행하게 됨(복제했으니까) fork()다음부터 수행

- OS가 자식프로세스 리턴값을 0으로 셋팅 (그래서 else if 로 빠짐)

- Child Process에게 전달되는 fork() system call의 리턴값이 0이고 Child의 pid는 1이상의 값


Q. Child Process의 pid는 0이 아니고 다른 값인가?

A. 그렇다. Child Process에게 전달되는 fork() system call의 리턴 값이 0이다.


Q. fork() 함수로 받는 리턴 값이 0인 것은 Process 0번의 PID를 받는다는 의미인가?

A. 아니다. 앞에서 설명했듯이 Child Process에게 전달되는 리턴 값이 0일 뿐이다.


Q. Parent Process가 항상 wait()를 하게 되면 시스템에는 항상 한 개의 Process만 동작하는 것이 아닌가?

A. Parent Process가 언제나 fork() 후에 wait()를 호출해야 하는 것은 아니다


Q. Child Process들이 다 종료될 때까지 Parent Process가 wait()하고 있어야 하는가?

A. 아니다. Parent Process가 Child Process의 종료 상태에 관심이 있는 경우에만 wait()를 호출한다



- Parent와 Child가 "pipe"파일을 연다음에

- Parent는 거기에 데이터를 쓰고

- Child는 데이터를 읽어가는 것

- 커뮤니케이션이 성립이 됨 (이런 경우 wait()가 필요없어짐)

- wait()는 논리적으로 child의 termination(종료)를 기다릴 때만 쓰는 것임


초창기 유닉스에서는 프로세스와 프로세스가 커뮤니케이션할 수 있는 방법은 이 fork()방법밖에 없었고 

당연히 Parent Process와 Child Process만 서로 통신을 할 수 있었음

이런 이유로 IPC문제를 간단하게 해결하기 위해 fork() exec() 메커니즘을 개발하게 됨


Parent의 코드와 데이터를 copy했는데 바로 exec()으로 overwrite가 되는 문제

이런 문제를 해결하는 방법은? 리눅스나 유닉스 하는 사람들이 새로운 메커니즘을 개발했는데 그것이

COW (Copy on Write): Process Context를 fork()시점에서 복사하지 않고, Data Segment에 새 값이 쓰여질 때 복사하는 기법

불필요한 카피, 불필요한 성능저하는 사실상 없어짐



프로세스 수행을 종료하는 방법 (2가지)

1. 정상적인 exit()을 호출해서 죽는 방법

2. 비정상적으로 자기가 뭔가 잘못했을 때 Parent가 kill 시키는 방법. abort() 하는 방법임

   아니면 signal을 보내서 죽이는 방법











출처 - http://snui.snu.ac.kr/ocw/index.php?mode=view&id=639

출처 - http://snui.snu.ac.kr/ocw/index.php?mode=view&id=640

출처 - http://snui.snu.ac.kr/ocw/index.php?mode=view&id=2937

반응형

블로그의 정보

jennysgap

jennysgap

활동하기