운영체제(OS)

Operating Systems 3. Processes

아뵹젼 2021. 10. 11.

Process Concept

Batch 시스템은 jobs(작업), Time-shared 시스템은 User program, task, process 로 불린다. 여기서 Job, task, process 용어는 거의 같은 의미로 사용된다.

 

프로세스는 실행 상태에 있는 프로그램으로, 능동적 개체이다. storage 에 있는 동적인 개체인 프로그램이 메모리에 올라가는 순간 프로그램 객체 + 데이터 가 된다.

즉, OS 는 실행가능한 파일을 메모리에 불러와서 프로세스화 시켜서 관리하며 GUI 마우스 클릭, 명렁어, 터치, 보이스 명령 등에 의해 프로그램은 실행된다. 

 

만약 Word 파일 하나를 열면, Word Program 객체 1개 + word data 1개가 생성된다.

또 하나의 Word 파일을 열면, 이전의 프로그램 객체는 그대로 사용되고, data 객체가 1개 더 생길 것이다.

또 Word 파일 하나를 더 연다면, 하나의 Program 객체가 data 객체 3개를 관리하는 셈이 된다.

 

 

 

프로세스는 여러 부분으로 구성되어 있다.

- program instance

text - 프로그램 코드 부분 

- data instance

data - static, global 변수

heap - 동적 할당 변수

stack - 지역변수, 함수 매개변수, 리턴 주소

 

 

Process State

프로세스의 현재 활동을 나타내며, 실행하는 동안 상태 변화가 발생한다.

new - 프로세스가 생성 중

running - 명령어가 실행 중

waiting - 어떤 이벤트 (입출력, 신호 수신) 발생을 기다림

ready - 프로세스가 실행할 준비가 됨 (CPU 할당을 기다림)

terminated - 프로세스의 실행이 종료됨

 

 

 

Process Control Block (PCB)

각 프로세스는 자신에 대한 정보를 포함하고 있는 PCB를 가지고 있다. PCB에는 다음과 같은 정보들이 담겨있다.

 

Process state : 프로세스의 상태 (running, waiting..)

Program counter - 프로세스가 다음에 실행해야 할 명령의 주소를 가리키는 카운터

CPU registers - 프로세스가 인터럽트 이후 작업을 복귀하기 위해 참조하는 CPU 레지스터 값

CPU scheduling - 우선순위, 스케쥴링 큐 포인터 

Memory-management information - 프로세스에 할당된 메모리 정보 (base, limit)

Accounting information - CPU 사용량, 클락 시간, 시간 제한 

I/O status information - 프로세스에 할당된 I/O 장치 목록, 열려있는 파일 목록

 

 

 

프로세스 간에 CPU switch 은 다음과 같이 이루어진다.

프로세스 P0 이 실행되다가 인터럽트 또는 시스템 콜이 발생된 순간 PCB0에 모든 상태를 저장하고, PCB1의 상태를 불러온다.

그런 다음 프로세스 P1이 실행되고, 실행 시간이 다 되면 다시 PCB1에 프로세스 P1의 상태를 저장한다. 

 

Process Representation in Linux

리눅스에서 모든 active process 는 task_struct 구조체의 linked-list 로 관리된다.

또 프로세스의 상태를 저장하고 다시 재개하기 위해 저장하고 복원하는 xv6 Registers 는 다음과 같이 구성된다.

 

 

Process Scheduling

CPU 이용률을 극대화하기 위해서, OS 는 빈번한 CPU 스위칭을 해야한다.

따라서 Process Scheduler 는 사용 가능한 프로세스들 중에서 다음에 CPU에서 실행할 프로세스를 선택해야 한다.

프로세스 스케쥴링 큐는 다음과 같이 구성되어 있다.

 

Job queue - 모든 프로세스들의 집합

Ready queue - 메인 메모리에 위치한 ready 상태의 프로세스들의 집합

Device queue - 특정 I/O 장치 사용을 대기하는 프로세스들의 집합

 

프로세스들은 실행 동안 여러 queue 들 사이에서 이동하며, 프로세스가 종료되면 queue 에서 제거되고 할당된 자원과 PCB도 deallocate 해야 한다.

 

ready queue 에 있는 프로세스들(PCB7, PCB2) 은 현재 메모리에 올라와 있으므로, 언제든지 CPU에 할당되어 동작가능하다.

 

 

새로운 프로세스는 초기에 ready queue 에 놓인 후 실행을 위해 선택될 때 까지 ready queue 에서 기다린다.

그러다가 프로세스에게 CPU 가 할당되면, 다음 이벤트 중 하나가 발생할 때까지 해당 프로세스가 CPU에서 실행된다. 

 

실행중인 프로세스를 중단시키는 이벤트는 다음과 같다.

- 프로세스가 입출력 요청 시 입출력 큐에 넣어진다.

- 프로세스가 새로운 자식 프로세스를 생성하고, 자식 프로세스의 종료를 기다리는 동안 대기 큐에 놓일 수 있다.

- 프로세스가 인터럽트 발생을 기다린다.

- 인터럽트 또는 할당된 시간 간격이 만료되어 프로세스가 강제로 제거되어 ready queue 에 갈 수 있다.

 

 

 

Schedulers

Short-term scheduler (CPU scheduler) - 다음에 실행시킬 프로세스를 선택하여 CPU 를 할당해 준다. (빠른 속도)

 

Long-term scheduler (job scheuduler) - 레디 큐에 넣을 수 있는 프로세스를 확인하고 넣어준다. 레디 큐에 프로세스를 넣어 주지 않는다면 멀티프로그래밍이 힘들어지므로, Long-term 스케쥴러는 멀티프로그래밍의 정도를 제어한다. 또한 프로세스를 레디큐 올릴 때 연산 process(CPU-bound process) 와 I/O 프로세스(I/O-bound process) 의 적절한 비율을 고려하여 선택해야 한다. (느린 속도)

 

Medium-term scheduler - 레디 큐에 프로세스가 너무 많을 때, 아직 실행하지 않아도 되는 프로세스들을 제거하기 위해서 프로세스들을 메모리에서 제거하고 disk 에 저장해둔다. 그리고 disk 에서 실행되야할 프로세스들을 가져오는 swapping 역할을 담당한다. 즉, 새로 적재될 프로세스를 위한 메모리 공간을 확보한다. 

 

 

 

Context Switch

프로세스가 실행되다가 인터럽트가 발생했을 때 다른 프로세스로 스위칭이 필요하다.

이때 시스템은 작업중이던 프로세스의 상태를 저장하고, 새로운 프로세스의 상태를 로드하는데 이를 context switch 라고 한다.

프로세스 입장에서 Context 는 PCB를 의미한다.

만약, 너무 많은 context switch 이 일어날 경우 오버헤드가 발생해 시스템 성능이 저하된다.

 

short-term 스케쥴러로 프로세스가 cpu 에 할당되는 순간, PCB 의 값들이 CPU 에 각각 mapping 된다. context switching 이 발생하여 다른 프로세스가 할당된다면, 현재 프로세스의 CPU 값들을 다시 PCB로 백업하고, 새 프로세스의 PCB의 값들을 CPU 로 매핑한다.

 

 

 

Process Creation

프로세스는 트리 구조로 되어 있다.

즉, 부모 프로세스가 시스템 콜의 fork() 함수를 호출하면 자신과 똑같은 자식 프로세스를 생성할 수 있다.

프로세스는 PCB에 저장된 process identifier(pid) 값으로 식별할 수 있는데, 이는 OS 가 정해준 고유 번호이다.

부모와 자식 프로세스는 모든 자원을 공유할 수도, 부모 자원의 부분집합을 사용할 수도, 아예 자원을 공유하지 않을 수도 있다.

또한, 부모와 자식 프로세스는 동시에 병렬적으로 실행되며, 부모 프로세스는 자식이 종료할 때 까지 기다린다.

 

 

프로세스 생성 과정은 다음과 같다.

storage 에 있는 실행가능한 Binary 형식이 메모리에 로드되고, heap, stack, data, code과 기본적으로 3개의 파일 descriptor 가 할당된다. 그런 다음 Main() 함수가 실행된다.

 

UNIX 에선 fork 와 exec 시스템 콜을 사용한다.

fork() 는 새로운 프로세스를 생성하는 것으로, 부모 프로세스의 주소공간의 복제본으로 구성된다. exec() 는 프로세스 메모리 공간을 새 프로그램으로 대치하여 새로운 프로그램을 실행하는 것이다.

 

 

 

Program Termination

exit() 를 호출하면 프로세스를 종료할 수 있다.

자식 프로세스는 종료할 때 부모에게 상태 값(보통 정수)을 반환할 수 있으며, 부모 프로세스는 wait() 시스템 호출을 하여 이 값을 기다리고 있는다.

그리고 프로세스 종료시 OS 는 프로세스가 사용한 자원을 deallocate 한다. 

 

자식 프로세스가 종료되었지만, 부모가 아직 wait() 를 호출하지 않아서 남아있는 프로세스를 zombie 상태라고 부른다. 

그리고 부모가 wait 를 호출하기 전에 자식 프로세스보다 먼저 종료한 경우, 자식 프로세스는 orphan 상태가 되며, 자동으로 init 프로세스가 새로운 부로 프로세스로 지정된다.

 

 

Android 프로세스 계층

모바일 운영체제는 제한된 시스템 자원을 회수하기 위해 종종 프로세스를 종료시키는데,

가장 낮은 순위의 프로세스부터 종료된다. 즉 Empty 프로세스부터 차례로 종료되는 것이다.

 

 

 

Interprocess Communication

프로세스는 독립적으로 동작하거나 서로 협력하여 동작할 수 있다.

협력하는 프로세스들은 통신하면서 서로에게 영향을 미치고, 공유된 자원을 사용한다.

프로세스 협력을 하는 이유는 정보 공유, 연산 가속화, 모듈성을 들 수 있다.

또한 협력 프로세스들은 interprocess communication(IPC) 를 필요로 하는데 IPC에는 두가지 모델이 존재한다.

 

(a) 는 Message Passing 이다. 이 모델은 적은 양의 데이터 전달에 유용하고, 구현이 용이하다.

프로세스끼리 커널을 통해 정보를 전달하여 속도가 느리다.

 

(b) 는 Shared Memory 이다. 이 모델은 특정 메모리 공간을 두 프로세스가 함께 사용하여 정보를 주고 받는다. 커널을 거치지 않기 때문에 속도가 빠르지만 메모리가 동시 접근하는 것을 방지 하기 위하여 동기화가 필요하며 구현이 복잡하다.

 

 

(a) Message Passing 시스템은 같은 주소 공간을 공유하지 않고, 통신을 하고 동작을 동기화한다.

이를 위해 메시지 전달 IPC 함수를 이용하는데, 함수는 send(message)receive(message) 를 제공한다.

통신 프로세스가 통신을 하기 위해선 통신 연결이 설정되어야 한다.

물리적인 구현으로는 공유 메모리, 하드웨어 버스, 네트워크가 있으며, 논리적인 구현으로는 direct / inderect , 동기식 / 비동기식, 버퍼 와 같은 통신 방법이 있다.

 

 

-Direct communication 은 서로의 이름을 명시하여야 한다.

send(P, message) 와 같이 프로세스 P에게 메시지를 전송한다고 명시해야 한다. 이러한 통신은 두 프로세스 사이에서만 가능하고, 두 프로세스 사이에 정확히 1개의 링크만 존재한다. 

 

-Indirect communication 은 mailbox 또는 port 를 경유하여 메세지를 송수신한다.

각 메일박스는 고유의 ID를 가지는데, 프로세스들은 메일박스를 공유하여 통신할 수 있다.

예로 send(A, message) 와 같이 메일박스 A로 메시지를 송신하는 것이다. 

 

 

-동기식 통신은 Blocking 송수신이다. 송신 프로세스는 수신자가 메시지를 받을 때까지 block 되어 있으며, 수신 프로세스는 수신 메시지가 존재할 때까지 block 되어 있다.

즉, 상대편의 동작에 영향을 받는 통신이다.

 

-비동기식 통신은 Non-Blocking 송수신이다. 송신 프로세스는 메시지를 보내고 바로 리턴하여 수신 여부와 관계없이 작업을 계속 수행한다. 수신 프로세스는 유효한 메시지나 null 을 받고 바로 리턴하여 작업을 계속 수행한다. 즉, 상대편의 동작에 영향을 받지 않는다. 이는 구현이 편하지만 오버헤드가 생기기 쉽다.

 

 

-버퍼링은 메세지 큐로 구현된다.

프로세스 간에 교환되는 메시지가 링크에 연관된 메세지 큐(버퍼) 에 저장되어 전송되는 것이다. 버퍼 큐의 구현 방법은 다음과 같다.

 

1. 무용량 - 링크에 버퍼가 없어서, sender는 receiver 가 수신할 때까지 기다려야 한다.

2. 유한용량 - 유한한 길이 n 의 버퍼이다. 큐가 full 이면 sender 는 기다려야 한다.

3. 무한용량 - 무한한 길의의 버퍼로, sender 는 결코 기다리지 않는다.

 

 

-Mach 의 message passing 은 메일박스 기반 메시지 전송으로 다음과 같은 시스템 호출을 사용한다.

msg_send() – 메일박스 전송

msg_receive() – 메일박스 수신

msg_rpc() – remote procedure call

port_allocate() – 메일박스 생성

 

 

-Local Procedure Calls in Windows

윈도우에서 사용하는 IPC 는 메일박스와 같은 message passing이다. 

 

 

 

(b) Shared Memory 시스템은 공유 영역에 있는 버퍼를 사용하는 것으로, 생산자-소비자 문제가 공유 메모리를 사용하는 전형적인 예시이다.

생산자 프로세스는 소비자 프로세스가 소비하는 정보를 생산한다. 이 프로세스들은 동시에 수행되기 때문에, 동기화 문제가 발생할 수 있다.

이를 해결하기 위해 생산된 데이터를 담아두는 버퍼(buffer) 를 사용한다.

생산자는 항목을 생산하고 이를 버퍼에 저장하고, 소비자는 버퍼에 있는 항목을 소비하는 것이다.

즉, 가용한 항목이 있을 때만 소비자 프로세스가 소비할 수 있도록 동기화 되어야 한다.

 

 

-Shared Memory IPC 의 방법은 다음과 같다.

한 프로세스가 먼저 공유 메모리 객체를 생성한다.

그리고 객체의 크기를 바이트 단위로 설정하고, 메모리 관리 파일을 구축하고 포인터를 반환한다.

프로세스들은 공유 메모리에 읽거나 쓸 수 있고, 더 이상 공유가 필요 없으면 메모리를 떼어낸다.

이는 마치 파일처럼 관리된다.

 

 

Pipe - 두 개의 프로세스가 서로 통신이 가능하도록 전달자 역할을 수행한다.

 

-일반 파이프(Ordinary pipes)는 생성한 프로세스만 접근 가능하고, 부모 프로세스가 파이프를 생성하고 자식 프로세스와 통신한다. 이는 생산자-소비자 형태로 통신을 허용하는데, 생산자는 한쪽 끝으로 쓰기를 하고 소비자는 다른 쪽 끝에서 읽기를 한다. 일반 pipe는 단방향 통신만 가능하다.

 

-지명 파이프(Names pipes) 는 부모-자식 관계가 필요하지 않다. UNIX 에서는 mkfifo() 를 사용하여 FIFO 파일을 생성할 수 있다.

댓글