2020년 7월 1일 수요일

OS 정리 - 3

1. Process

코드 자체인 프로그램이 돌아가고 있는 것이 프로세스이다.

이는 메인메모리에 있을 것이므로 그 구조를 먼저 파악하자.

프로세스는 위 메모리 주소에서 코드 부분에서 스택까지의 부분이다. 보통 가상메모리 써서 4G 정도 배당받는다.

Text(Code) 는 진짜 프로그램의 코드가 들어간다.

Data 는 전역 변수가 들어간다. static 들어간 변수도 여기 들어간다.

heap 은 동적 할당에 , stack 은 임시 데이터 저장에 쓰인다.

프로세스는 멀티 프로그래밍을 쓰려면 여러 상태를 가지게 된다.

New, Running, Waiting, Ready, Terminated.

New 에서 Admit 되면 Ready, Waiting, Running 의 삼각 순환 고리에 들어간다.

프로세스 스케쥴러에서 대기 큐에 들어가면 Ready 상태가 된다. 여기서 Dispatch 가 되면 Running 상태로 간다.

그럼 스케쥴러 정책에 따라 중간에 멈추게 될 수도 있어서 다시 Ready Queue 에 들어갈 수도 있다.

혹은 I/O 때문에 기다려야 할 수도 있어서 Waiting 에 있다가 끝나면 Ready Queue 로 돌아갈 수 있다.

프로세스 종료시 Terminated .

멀티태스킹을 하려면 기존에 하던 작업을 저장해야할 필요가 있다.

이를 위한 단위가 PCB(Process Control Block) 이다.

Process state, process number, program counter, registers, memory limits, list of open files 등으로 구성된다.

이는 여러 Queue 에 저장된다. CPU 대기열인 Ready Queue 뿐만 아니라, I/O Device Queue 등 PCB 는 이곳저곳을 떠돌아 다닌다.

Long Term Scheduler (Job Scheduler) 는 디스크 등을 포함한 프로그램이 저장된 것들에서 제한된 크기를 갖는 메모리로 올릴 때의 정책이다.

Short Term Scheduler (CPU Scheduler) 는 어떤 순서로 프로세서를 CPU 에 넣어서 돌릴지를 결정하는 정책이다.

Mid Term Scheduling 은 Short Term Scheduler 의 정책에 따라 현재 CPU 에 있는 프로세스를 Ready Queue 로 뺀다. 이를 swaping 이라고 한다.

프로세스는 두가지 종류로 나뉘어진다. 이는 코드를 보고 아는게 아니라 실제로 어떻게 사용되는 지를 보고 판단된다. I/O bound process 와 CPU bound process 이다. 전자는 CPU 를 조금 쓰고 I/O 받으러 간다. 후자는 CPU를 오래 쓴다. Device Queue 와 Ready Queue 가 비지않고 적절히 처리되도록 적당히 프로세스를 메인메모리에 올리는게 Long Term Scheduler 의 임무다. 이는 하드웨어의 지원으로 빨리 처리되어야하는데, 아무 일도 처리하지 않느 순수한 오버헤드기 때문이다.

2. Process Creation

모든 프로세스는 PID(Process Identfier) 를 가진다.

처음 최고 짱짱 아빠 Init Process 가 실행되고 얘가 자식 프로세스를 만들면서 프로세스가 확장된다.

이렇게 부모에게 자식이 탄생하면, 1.Resource 배당 2. 둘다 실행되냐, 3. 어떤 프로그램이 될거냐 의 문제를 다뤄야한다.

1. Resource 는 부모와 자식이 전부 혹은 일부를 공유하거나, 부모가 자식에게 일부를 떼준다.

2. 부모는 자식을 낳고 계속 실행할 수도 있고, Linux 에서 wait() 함수 처럼 자식이 끝날때까지 기다릴 수도 있다. 이는 waiting state 기는 하지만 모든 waiting state 가 이런 경우는 아님에 주의하자.

3. Linux 에서는 fork() 로 자식이 탄생하고 부모의 메모리를 복사받는다. 그리고 exec() 로 메모리를 바꿔서 다른 프로그램을 실행한다.

Linux 를 공부하므로 fork() 는 매우 중요하다.

실패시 -1, 바로 그 코드로 성공시 자식의 경우 0, 부모의 경우 child 의 pid 를 return 한다.

부모의 메모리를 모두 복사하지는 않고 포인터만 주는데, 수정시 복사시킨다.

wait() 의 경우 자식이 끝날 때 호출하는 exit() 의 status value 와 pid 값을 처리한다.

pid_t pid; int status; pid = wait(&status);

abort() 로 부모는 자식 프로세스를 강제 종료 시킬 수 있다.

waiting() 이 호출되지 않고 종료된 자식은 Zombie Process 라고 한다.

waiting() 이 호출되지 않고 종료된 부모의 자식은 orphan 이라고 한다.

3. IPC

프로세스에는 두가지 종류가 있다. Independent / cooperating.

후자를 위해선 IPC (Interprocess communication) 가 필요하다.

Cooperation process 를 위한 Producer-Consumer problem.

여기엔 두가지 종류가 있는데, Message Passing, Shared Memory 가 그것이다.

Message Passing 은 Shared Memory 보다 간단하다. 왜냐하면 기본적으로 프로세스간은 메모리가 공유되지 않기 때문이다. 게다가 후자는 caches coherency 문제에 시달려 더 느리다고 알려져 있다. 대신 적은 크기의 정보에 최적화 되어 있다. 이런 점에선 Shared Memory 가 기본적으로 더 빠르다고 판단된다.

Message Passing 은 가지 방식이 있다.

1. Direct Communication : 기본적으로 한쪽이 send() 를 다른쪽이 receive() 를 해야지 통신 가능. 이건 공통인데 중간에 거치는게 없이 그냥 가버림 링크는 자동으로 생기고 딱 두 프로세스 사이에 하나가 생긴다.

2. Indirect Communication : 고유 식별자가 있는 메일박스를 이용함. 이는 프로세스 밖에 생성됨. 이 메일박스를 생성자, 사용자 모두 가지고 있어야 링크가 생성되고, 프로세스 쌍마다 여러 링크가 있을 수 있다. 단 링크당 메일박스는 하나. 공유 시 메시지 대상 특정이 애매해지는데 이땐 어떤 폴리시에 따라 결과가 다르다.

메일박스를 프로세스 내에 생성 할 수도 있다. 그럼 메일박스 소유자, 사용자로 나뉜다.

메시지를 보내고 받아지기까지, 혹은 메시지가 올 때까지 대기하거나 그냥 넘어갈 수 있다. 대기하면 Blocking 이다. receive 의 경우 후자는 null 을 받을 수도 있다.

메시지 박스가 큐로 구현되면, 수용량에 따라 버퍼링이 있다 없다로 구분한다.

Shared Memory 방식은 프로세스 외부에 공유 메모리 버퍼를 만든다. 크기가 bound 될 수도 있고 아닐 수도 있다.

아래 코드는 BUFFER_SIZE - 1 개 만큼 들어갈 수 있다. 여기선 concurency 를 고려하지 않아서 producer 먼저 하고 consumer 를 시작하는 것을 고려하자.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

#define BUFFER_SIZE 10

typedef struct

{

...

} item;

item buffer[BUFFER_SIZE];

int in = 0;

int out = 0;

// producer

while(1)

{

while(((in + 1) % BUFFER_SIZE) == out)

;

buffer[in] = next_produced;

in = (in + 1) % BUFFER_SIZE;

}

// consumer

while(1)

{

while(in == out)

;

next_consumed = buffer[out];

out = (out + 1) % BUFFER_SIZE;

}

댓글 없음:

댓글 쓰기

List