🦖 Operating System Concepts 10th
PART TWO PROCESS MANAGEMENT
Chapter 3 Processes
초기의 컴퓨터 시스템은 한 번에 하나의 프로그램만을 실행하도록 허용했다. 반면 오늘날의 컴퓨터 시스템들은 메모리에 다수의 프로그램이 적재되어 동시에 실행되는 것을 허용한다. 다양한 프로그램을 보다 견고하게 제어하고 보다 구획화해야 했고, 이러한 필요성에 따라 프로세스의 개념이 등장했다.
💡 프로세스
프로세스란 실행중인 프로그램을 말하며, 운영체제의 기본 작업 단위이다.
프로세스의 현재 활동 상태는 프로그램 카운터(PC) 값과 프로세서 레지스터의 내용으로 나타낸다.
Figure 3.1 프로세스 메모리 배치
- 텍스트 섹션 — 실행 코드
- 데이터 섹션 — 전역 변수
- 힙 섹션 — 프로그램 실행 중에 동적으로 할당되는 메모리
- 스택 섹션 — 함수를 호출할 때 임시 데이터 저장소 (e.g. 함수 매개변수, 리턴 주소, 지역 변수)
Cf. 스택에 활성화 레코드(activation record)가 push 되며 메모리가 동적으로 할당됨에 따라 힙이 커지고, 메모리가 시스템에 반환되면 축소된다. 스택 및 힙 섹션이 서로의 방향으로 터지더라도 운영체제는 서로 겹치지 않도록 해야 한다.
프로세스는 실행되면서 그 상태(state)가 변한다. 프로세스의 상태는 현재의 활동에 따라서 정의되며, 다음 상태 중 하나에 있게 된다.
![]() |
---|
Figure 3.2 Diagram of process state. |
한 프로세서 코어에서는 오직 하나의 프로세스만이 실행(running)된다. 따라서 많은 프로세스가 준비완료(ready) 및 대기(waiting) 상태에 있을 수 있다.
프로세스 제어 블록(process control block, PCB)(a.k.a task control block)은 운영체제의 프로세스를 나타내는 커널 데이터 구조이다.
프로세스 제어 블록은 특정 프로세스와 연관된 여러 정보를 수록하며, 다음과 같은 것들을 포함한다.
![]() |
---|
Figure 3.3 Process control block (PCB). |
프로세스 제어 블록은 프로세스를 시작시키거나 다시 시작하는 데 필요한 모든 데이터와 함께 약간의 회계 데이터를 위한 저장소 역할을 한다.
단일 제어 스레드는 프로세스가 한 번에 단지 한 가지 일만 실행하도록 허용한다.
대부분의 현대 운영체제는 한 프로세스가 다수의 실행 스레드를 가질 수 있도록 허용한다. 즉, 프로세스가 한 번에 하나 이상의 일을 수행할 수 있도록 허용한다. 이 특성은 특히 여러 스레드를 병렬로 실행할 수 있는 멀티코어 시스템에 유용하다. 다수의 스레드를 지원하는 시스템에서는 PCB가 각 스레드에 관한 정보를 포함하도록 확장된다. 스레드를 지원하기 위해서는 시스템 전반에 걸친 다른 수정도 필요한데, 이는 다음 글인 스레드와 동시성에서 상세하게 살펴보자.
멀티 프로그래밍의 목적은 CPU 이용을 최대화하기 위하여 항상 프로세스가 실행되도록 하는 데 있다.
시분할(time sharing)의 목적은 각 프로그램이 실행되는 동안 사용자가 상호 작용할 수 있도록 프로세스들 사이에서 CPU 코어를 빈번하게 교체(switch)하는 것이다.
멀티 프로그래밍과 시분할의 목적을 달성하기 위해 프로세스 스케줄러(process scheduler)는 코어에서 실행 가능한 여러 프로세스 중에서 하나의 프로세스를 선택한다. 각 CPU 코어는 한 번에 하나의 프로세스를 실행할 수 있다. 멀티 코어 시스템은 한 번에 여러 프로세스를 실행할 수 있다. 코어보다 많은 프로세스가 있는 경우, 초과 프로세스는 코어가 사용 가능해지고 다시 스케줄 될 때까지 기다려야 한다. 현재 메모리에 있는 프로세스 수를 다중 프로그래밍 정도(degree of multiprogramming)라고 한다.
![]() |
---|
출처: https://www.geeksforgeeks.org/process-schedulers-in-operating-system/ |
장기 스케줄러는 job 스케줄러라고도 한다. 이 스케줄러는 프로그램을 제어하고 큐에서 프로세스를 선택하여 실행을 위해 메모리에 로드한다. 또한 멀티프로그래밍의 정도(degree)를 제어한다.
Cf. 멀티프로그래밍의 정도란 현재 메모리에 있는 프로세스 수다.
중기 스케줄링은 swapping에서 중요한 부분으로, 실행 중인 프로세스가 I/O 요청을 하면 일시 중단될 수 있다. 프로세스를 메모리에서 디스크로 “swap out”하여 다른 프로세스를 위한 공간을 확보한다.
핵심 아이디어는 때로는 메모리에서 (and from active contention for the CPU) 프로세스를 제거하여 멀티 프로그래밍의 정도(degree)를 감소시키는 것이 유리할 수 있다는 것이다. 프로세스를 메모리에서 디스크로 “swap out”하고 현재 상태를 저장하고, 이후 디스크에서 메모리로 “swap in”하여 상태를 복원한다. swapping은 일반적으로 메모리가 초과 사용되어 가용공간을 확보해야 할 때만 필요하다.
단기 스케줄러는 CPU 스케줄러라고도 한다. 이 스케줄러의 주요 목표는 설정된 기준(criteria)에 따라 시스템 기능을 향상시키는 것이다. 준비(ready) 큐에 있는 프로세스 중에서 하나의 프로세스를 선택해서 CPU를 할당하며, 디스패처는 단기 스케줄러가 선택한 프로세스에 CPU 제어권을 부여한다.
Cf. https://www.guru99.com/process-scheduling.html
프로세스가 시스템에 들어가면 준비 큐(ready queue)에 들어가서 준비(ready) 상태가 되어 CPU 코어에서 실행되기를 기다린다. 이 큐는 일반적으로 PCB의 연결 리스트로 저장된다. 준비 큐 헤더에는 리스트의 첫 번째 PCB에 대한 포인터가 저장되고 각 PCB에는 준비 큐의 다음 PCB를 가리키는 포인터 필드가 포함된다.
프로세스에 CPU 코어가 할당되면 프로세스는 잠시 동안 실행되어 결국 종료되거나 인터럽트 되거나 I/O 요청의 완료와 같은 특정 이벤트가 발생될 때까지 기다린다. 이러한 프로세스는 대기 큐(waiting queue)에 삽입된다.
![]() |
---|
Figure 3.4 The ready queue and wait queues. |
프로세스 스케줄링의 일반적인 표현은 큐잉 다이어그램(queueing diagram)이다. 준비(ready) 큐와 대기(waiting) 큐 집합의 두 가지 유형의 큐가 제시되어 있다. 원은 큐에 서비스를 제공하는 자원을 나타내고 화살표는 시스템 프로세스의 흐름을 나타낸다.
![]() |
---|
Figure 3.5 Queueing-diagram representation of process scheduling. |
새 프로세스는 처음에 준비(ready) 큐에 놓인다. 프로세스는 실행을 위해 선택되거나 dispatch될 때까지 기다린다. 프로세스에 CPU 코어가 할당되고 실행 상태가 되면, 여러 이벤트 중 하나가 발생할 수 있다.
처음 두 경우에는 프로세스가 결국 대기(waiting) 상태에서 준비(ready) 상태로 전환된 다음 준비(ready) 큐로 다시 들어간다. 프로세스는 종료될 때까지 이 사이클을 계속한다. 종료(terminate) 시점에 모든 큐에서 제거되고 PCB 및 자원이 반환된다.
💡 문맥 교환 (context swtich)
문맥 교환(context swtich)은 CPU 코어를 다른 프로세스로 교환(switch)하는 작업이다.
Figure 3.6 Diagram showing context switch from process to process.
- Context Switch는 interrupt 또는 system call에 의해 실행된다.
- Context Switch가 일어나면, 커널은 과거 프로세스의 문맥을 PCB에 저장하고, 실행 예정인 새로운 프로세스의 저장된 문맥을 복구한다.
- Context Switch가 일어날 동안 시스템이 유용한 일을 못하기 때문에 문맥 교환 시간은 순수 오버헤드다.
대부분의 시스템 내의 프로세스들은 동시에 실행될 수 있으며, 동적으로 생성 및 삭제될 수 있다. 그러므로 운영체제는 프로세스 생성 및 종료를 위한 기법을 반드시 제공해야 한다.
프로세스는 실행되는 동안 여러 개의 새로운 프로세스들을 생성할 수 있다.
생성된 새로운 프로세스들도 각각 새로운 프로세스들을 생성할 수 있으며, 그 결과 프로세스의 트리를 형성한다.
프로세스는 고유한 프로세스 식별자(process identifier or pid)를 사용하여 구분되고, 이 식별자는 보통 정수다. pid은 커널이 유지하고 있는 프로세스의 다양한 속성에 접근하기 위한 index로 사용된다.
![]() |
---|
Figure 3.7 A tree of processes on a typical Linux system. |
일반적으로 프로세스가 자식 프로세스를 생성할 때, 그 자식 프로세스는 태스크 완수를 위해 특정 자원(CPU 시간, 메모리, 파일, 입출력 장치)이 필요하다. 자식 프로세스는 이 자원을 운영체제로부터 직접 얻거나, 부모 프로세스가 가진 자원의 부분 집합만을 사용하도록 제한될 수 있다.
프로세스가 새로운 프로세스를 생성할 때, 두 가지 실행 가능성이 존재한다.
새로운 프로세스들의 주소 공간(address-space) 측면에서도 두 가지 가능성이 았다.
💻 UNIX의
fork()
시스템 콜을 사용한 프로세스 생성
Figure 3.9 Process creation using the fork() system call. #include <sys/types.h> #include <stdio.h> #include <unistd.h> int main() { pid_t pid; /* fork a child process */ pid = fork(); if (pid < 0) { /* error occurred */ fprintf(stderr, "Fork Failed"); return 1; } else if (pid == 0) { /* child process */ execlp("/bin/ls","ls",NULL); } else { /* parent process */ /* parent will wait for the child to complete */ wait(NULL); printf("Child Complete"); } return 0; }
Cf.
fork()
시스템 콜의 return 값
- original process에서는 child process의 PID
- child process에서는 0
프로세스가 마지막 문장의 실행을 끝내고, exit()
시스템 콜을 사용하여 운영체제에 자신의 삭제를 요청하면 종료된다. 이 시점에서, 프로세스는 자신을 기다리고 있는 부모 프로세스에(wait()
시스템 콜을 통해) 상태 값(통상 정수)을 반환할 수 있다.
물리 메모리와 가상 메모리, 열린 파일, 입출력 버퍼를 포함한 프로세스의 모든 자원이 할당 해제되고 운영체제로 반납된다. 그러나 프로세스의 종료 상태가 저장되는 프로세스 테이블의 해당 항목은 부모 프로세스가 wait()
를 호출할 때까지 남아 있게 된다.
wait()
호출을 하지 않은 프로세스wait()
를 호출하면 좀비 프로세스의 프로세스 식별자(pid)와 프로세스 테이블의 해당 항목이 운영체제에 반환된다.wait()
를 호출하는 대신 종료하는 경우의 자식 프로세스운영체제에서 동시에 실행되는 프로세스들은 독립적(independent)이거나, 협력적(cooperating)인 프로세스일 수 있다.
협력적(cooperating) 프로세스들은 데이터를 교환할 수 있는, 즉 서로 데이터를 주고 받을 수 있는 프로세스 간 통신(interprocess communication, IPC) 기법이 필요하다.
프로세스 간 통신에는 기본적으로 두 가지 모델인 공유 메모리(shared memory)와 메시지 전달(message passing)이 있다.
![]() |
---|
Figure 3.11 Communications models. (a) Shared memory. (b) Message passing. |
소켓(socket)은 클라이언트 서버 통신의 endpoint를 뜻하며, 두 프로세스가 네트워크상에서 통신을 하려면 양 프로세스마다 하나씩, 총 두 개의 소켓이 필요해진다.
각 소켓은 IP 주소(IP address)와 연결된 포트(port) 번호를 통해 구별한다.
![]() |
---|
Figure 3.26 Communication using sockets. |
Java는 세 가지 종류의 소켓을 제공한다.
소켓은 스레드 간에 구조화되지 않은 바이트 스트림만을 통신하기 때문에, 원시적인 바이트 스트림 데이터를 구조화하여 해석하는 것은 클라이언트와 서버의 책임이 된다. 이에 대한 대안으로 더욱 높은 수준의 통신 기법인 원격 프로시저 호출(remote procedure call, RPC)이 있다.
RPC는 원격 서비스와 관련한 가장 보편적인 형태 중 하나다. 네트워크에 연결된 두 시스템 사이의 통신에 사용하기 위하여 프로시저(함수) 호출 기법을 추상화하는 방법으로 설계되었다. 프로세스들이 서로 다른 시스템 위에서 돌아가기 때문에 원격 서비스를 제공하기 위해서는 메시지 기반 통신을 해야 한다.
RPC는 클라이언트가 원격 호스트의 프로시저 호출을 마치 자기의(local) 프로시저 호출처럼 해준다.
Cf. 매개변수 정돈(parameter marshalling)은 클라이언트와 서버 기기의 데이터 표현 방식의 차이 문제를 해결한다. 어떤 기계는 big-endian 방식을, 어떤 기계는 little-endian 방식을 사용하더라도 XDR (external data representation)과 같은 중립적인 데이터 표현 방식으로 데이터를 바꾸어서 보낸다.
RPC는 분산 파일 시스템(distributed file system, DFS)을 구현하는 데 유용하다.