
Chapter 2에서 상술했듯이, 프로세스는 다음과 같이 정의할 수 있다.
1). 실행중인 프로그램
2). 컴퓨터에서 실행중인 프로그램의 인스턴스
3). 프로세서에 할당되어 실행될 수 있는 개체
4). 일련의 지시사항 실행, 현재 상태, 그리고 관련된 일련의 시스템 자원들로 특징되는 활동의 단위
이때, 프로세스의 두 가지 필수 요소가 존재한다.
1). 프로그램 코드 (Program code) : 디스크에 저장된 명령어의 집합. 이 코드는 메모리로 로드되어 실행된다.
2). 그 코드와 관련된 데이터 세트 (A set of data associated with that code) : 프로그램 실행에 필요한 변수, 파일, 입출력 상태 등. 프로그램 코드와 함께 메모리에 load됨.

프로세스는 여러 구성 요소를 통해 Characterized 될 수 있다.
Process Identifier (PID) : 이 프로세스와 다른 모든 프로세스들을 구별하기 위한 고유 식별자 (PID)
State : 프로세스 생명 주기중, 현재 프로세스의 상태 (State)
Priority (우선순위) : 다른 프로세스들에 대해 갖는 상대적인 우선순위
Program Counter (PC) : 다음에 실행될 프로그램의 명령어 주소
Memory Pointers : 이 프로세스와 관련된 프로그램 코드와 데이터를 가리키는 포인터들, 그리고 다른 프로세스들과 공유하는 메모리 블록들을 포함한다.
Context Data : 프로세스가 실행 중일 때, 프로세서 내 레지스터에 존재하는 데이터
I/O Status Information : 처리중인 I/O 요청들, 혹은 이 프로세스에 할당된 I/O 장치들, 프로세스가 사용중인 파일 목록 등
Accounting Information : 사용된 프로세서 시간과 클럭 시간, time limit, account numbers 등

위에서 언급한 프로세스의 정보들은 Process Control Block(PCB)라고 불리는 Data Structure에 저장된다. PCB는 OS에 의해 생성되고 관리된다.
PCB가 갖는 주요 특징들은 다음과 같다.
1). 충분한 정보가 포함되어 있어 프로세스를 중단했다가 실행을 재개했을 때, 중단 지점부터 시작한다.
2). PCB는 OS가 여러 프로세스를 관리하고 Multi Processing을 제공할 수 있게 하는 핵심 도구이다.
3). 프로세스가 중단될 때, PC (Program Counter)와 Context Data의 현재 값은 해당 PCB의 필드에 저장된다.
4). 프로세스가 다시 실행될 때, 이 프로세스가 중단된 시점의 정보(PC와 Context Data)는 CPU Register에 로드된다.
프로세스는 프로그램 코드, 관련 데이터 + PCB로 구성된다.
PCB 구현 기법에 대해 알아보자.
구현이 간단하고, PCB를 찾아내는 속도 빠름. OS입장에서 안정적인 구조. 미리 할당된 메모리 공간이 모두 사용되지 않을 경우, 메모리 효율성이 떨어질 수 있음. 시스템이 지원할 수 있는 프로세스의 수 및 리소스가 제한적이기 때문에 다소 경직된 구조.
필요에 따라 PCB를 생성 및 해제하므로 메모리 효율성 좋음. 시스템이 지원하는 프로세스의 수가 제한되어 있지 않으므로 상대적으로 유연한 구조. 다만, 여러 메모리 크기를 가진 PCB를 여러번 할당 및 해제하는 과정에서 Memory Fragment가 발생 가능함. 이는 메모리 효율성을 감소시킴. PCB 검색 속도가 느림. 만약 Linkedlist의 중간이 끊어져 있다면, PCB 검색에 어려움이 발생함.
** Memory Fragment (메모리 단편화) : 메모리 공간이 작은
빈 공간들로 나뉘어져 있어, 메모리 용량 자체는 충분하지만
큰 메모리 블록을 할당하기 어려워지는 현상.
** External Fragment (외부 단편화) : 메모리의 총 공간은
충분하지만, 연속적인 공간이 아닌 작은 조각들로 나누어져 있어
요청된 크기의 메모리를 할당하지 못하는 현상. Ex) 100KB의
메모리를 요구하는 프로그램이 있다고 가정해보자. 이때 메모리는
총 100KB의 빈 공간이 있지만, 10KB 단위로 나뉘어져 있는 경우,
외부 단편화가 발생했다고 할 수 있다.
** Internal Fragment : 할당된 메모리 블록 내에 사용하지 않는
공간이 발생하는 현상. Ex) 프로세스가 15KB의 메모리를 요구하고,
시스템이 20KB의 메모리를 할당했다면, 5KB의 내부 단편화가
발생했다고 할 수 있다.
PCB에 대한 빠른 접근 속도와 동적 메모리 할당을 통한 유연성을 동시에 제공. 다만, 구현의 복잡성이 증가할 수 있으며, 테이블과 실제 PCB 저장 영역 모두를 적절히 관리해야함.

프로세스가 전환될 때 작동 방식은 다음과 같다.
1). 프로세스 전환 (Context Switching) : OS가 스케줄링 알고리즘을 통해 다음에 실행할 프로세스를 결정한다. 현재 실행중인 프로세스의 작업이 중단될 때 (Time out, I/O Interrupt...), 디스패처는 현재 프로세스의 상태를 해당 프로세스의 PCB에 저장한다.
2). 디스패처 : 디스패처는 현재 실행이 완료된 프로세스의 상태를 저장하여 PCB에 저장하고, 실행이 결정된 프로세스 PCB의 상태 정보를 복원하여 프로세서의 상태를 해당 프로세스가 마지막으로 실행되었을 때의 상태로 설정한다.
3). 실행 재개 : 상태 복원이 완료되면, CPU는 새로운 프로세스의 실행을 시작한다. 이 과정을 반복하며 프로세스간 실행 전환이 이루어진다.
실행 흐름의 예시를 보자.


프로세서는 52개의 명령 주기동안 서로 다른 프로세스를 교차하며 명령어를 실행한다.
디스패처에 의해 실행되는 코드 영역은 그림에서 음영 처리되어 있다.
OS는 한 프로세스가 최대 6개의 명령 주기만 실행을 할 수 있도록 Time Quantum을 정했다.
1). 프로세스 A 실행 : 프로세스 A가 6개의 명령어를 실행한다.
2). Time out and Execution of Dispatcher : 디스패처가 6개의 명령어를 실행한다.
3). 프로세스 B로 전환 : 디스패처가 프로세서의 제어를 프로세스 B로 넘긴다. 프로세스 B는 4개의 명령어를 실행한 뒤 I/O Request를 요청하며, 이 작업을 위해 대기한다.
4). Execution of Dispatcher : 프로세스 B의 실행이 끝났기 때문에 디스패처가 실행된다.
5). 프로세스 C로 전환
6). Time out and Execution of Dispatcher : 타임 아웃으로 인해 디스패처가 실행된다.
7). 프로세스 A로 다시 전환
8). Time out and Execution of Dispatcher
9). 프로세스 C로 다시 전환
프로세스 간 실행 전환은 프로세스 실행 완료, I/O 요청, 시간 초과 등과 같은 다양한 요인에 따라 결정됨. 디스패처는 이런 전환을 관리함. 프로세스의 실행 순서는 OS가 스케줄링 알고리즘에 따라 결정함.
프로세스 생성을 유발하는 Event는 크게 4가지이다.
1). 작업 제출에 의한 프로세스 생성 (배치 환경) : 사용자가 작업을 시스템에 제출하면, OS는 이를 처리하기 위한 새로운 프로세스를 생성한다. 이는 일반적으로 Batch process System에서 발생한다.
2). 새 사용자의 로그온 시도 (대화형 환경) : 사용자가 시스템에 로그온하려고 할 때, OS는 사용자 세션을 관리하기 위해 새로운 프로세스를 생성한다.
3). 응용 프로그램을 대신하여 프로세스 생성 : 사용자가 특정한 작업을 요청하면, OS는 해당 작업을 관리할 새로운 프로세스를 생성할 수 있다.
4). 하나의 프로세스가 다른 프로세스의 생성을 유발 : 한 프로세스가 실행되는 과정에서, 필요에 따른 다른 프로세스를 생성할 수 있다 (Process Spawning).
Process Spawning : 한 프로세스가 다른 새로운 프로세스를 생성하는 것을 의미. 이를 통해 프로세스는 자신의 작업을 다른 프로세스에게 위임하거나, 병렬로 작업을 수행하게 할 수 있다.
1). Parent Process (부모 프로세스) : 새로운 프로세스를 생성한 프로세스. 부모 프로세스는 자식 프로세스의 생성, 관리, 종료를 담당할 수 있다.
2). Child process (자식 프로세스) : 부모 프로세스에 의해 생성된 프로세스. 자식 프로세스는 독립적으로 또는 부모 프로세스와 협력하여 작업을 수행할 수 있다.
fork()
1). fork() System call은 호출하는 현재 프로세스(부모 프로세스)의 정확한 복사본(자식 프로세스)을 생성한다. 자식 프로세스는 부모 프로세스와 코드, 데이터, 힙 및 스택 공간이 모두 동일하지만, 독립적인 실행 흐름을 갖는다.
2). 부모 프로세스에게는 새로 생성된 자식 프로세스의 PID를 반환하고, 자식 프로세스에게는 0을 반환한다. 실패할 경우 -1을 반환한 뒤 오류를 나타내는 전역 변수 errno을 설정한다.
3). 주로 병렬 처리를 위해 동일한 프로그램 내에서 다른 작업을 수행하고자 할 때 사용된다.
exec()
1). exec() System call은 현재 프로세스의 메모리 공간에 새로운 프로그램을 로드하고 실행한다. exec()가 호출되면 기존 프로그램의 PID는 변경되지 않고, 코드와 데이터는 새로운 프로그램으로 대체된다. 호출 이후의 코드는 실행되지 않는다.
2). 성공할 경우 반환값이 없고, 실패할 경우 -1을 반환한다. 마찬가지로 errno을 설정하여 오류를 나타낸다.
3). fork()와 함께 사용하여, 부모 프로세스는 계속 실행시키면서, 자식 프로세스에 새로운 프로그램을 실행시킬 수 있다.
wait()
1). wait() System call은 부모 프로세스가 하나 이상의 자식 프로세스가 종료될 때까지 대기하도록 하는 기능을 제공한다. 이는 프로세스간 동기화를 위해 중요하다. wait() 함수를 호출하는 부모 프로세스는 자식 프로세스가 종료될 때까지 실행이 중단된다.
2). 부모 프로세스는 wait() 함수를 사용해 자식 프로세스의 종료 상태를 얻을 수 있다. 이는 프로그램의 로직이 프로그래머의 의도대로 흘러가도록 도울 수 있다.
3). 부모 프로세스가 wait()을 호출해 자식 프로세스의 종료를 처리하면, OS는 이 자식 프로세스를 완전히 삭제한다. 이 과정을 통해 좀비 프로세스의 생성을 방지할 수 있으며, 자원 관리를 보다 효율적으로 할 수 있다.
exit()
1). exit() System call은 프로세스가 자발적으로 종료될 때 사용된다. 프로세스가 exit()을 호출하면, 해당 프로세스는 OS에 자신의 종료를 알리고, 사용하던 모든 자원을 반환한다.
2). P1 - P2 - P3가 부모와 자식 프로세스 관계라고 가정해보자.
2-1). P1은 P2의 부모 프로세스, P2는 P3의 부모 프로세스이다.
2-2). 정상적인 실행 흐름이라면, 자식 프로세스가 종료되어야 부모 프로세스도 종료된다.
2-3). P2에 exit() System call을 호출하면, P3의 종료 여부와 상관없이 P2가 종료된다.
2-4). P3의 부모 프로세스는 init 프로세스가 된다.
3). 부모 프로세스가 더이상 아무 작업도 수행하지 않는 상태이더라도, 자식 프로세스가 종료될 때까지 대기 상태로 존재한다. 이때 exit() 함수 호출을 통해 불필요한 프로세스를 종료시켜 시스템의 자원을 더욱 효율적으로 관리할 수 있다.
프로세스는 종료될 때 OS에 자신의 실행이 완료됐음을 알린다. 이를 통해 회수된 리소스는 다른 프로세스에 할당된다.
프로세스의 종료는 여러 방식으로 이루어질 수 있으며, 이는 OS 환경에 따라 달라진다.
1). 배치 작업의 종료 : 배치 처리 시스템에서는 작업이 완료되었음을 나타내는 특별한 지시어 (HALT)또는 명시적인 OS 서비스 호출을 포함해야 한다.
2). 대화형 응용 프로그램의 종료 : 대화형 응용 프로그램의 경우, 사용자의 행동(로그오프, 응용 프로그램 종료 등)이 프로세스의 완료를 나타낸다. 이는 사용자 인터페이스를 통해 직접적으로 이루어질 수 있다. Ex) 종료, 닫기와 같은 명령을 사용자가 실행
프로세스 종료는 시스템 자원을 효율적으로 관리하기 위해 필수적인 기능이다. 프로세스가 올바르게 종료되지 않으면, 메모리 자원이 회수되지 못하는 문제가 발생하고, 시스템 전반적인 성능에 악영향을 미칠 수 있다

State
1). Ready : 프로세스가 메인 메모리에 있으며, 실행이 가능한 상태
2). Blocked : 프로세스가 메인 메모리에 있으며, 이벤트를 기다리는 상태
3). Blocked/Suspend : 프로세스가 메인 메모리에서 저장장치로 밀려나 있는 상태로, 이벤트를 기다리고 있음.
4). Ready/Suspend : 프로세스가 저장장치에 존재하지만, 메인 메모리로 로드되는 즉시 실행이 가능한 상태.
Swapping : 메인 메모리에서 디스크로 프로세스의 일부 또는 전체를 이동시키는 것을 의미한다. 메인 메모리의 공간을 적절히 확보하고 관리하기 위해 사용되는 기법으로, 메인 메모리에 있는 프로세스중 어느 것도 준비 상태가 아닐 때, OS는 차단된 프로세스 중 하나를 디스크로 옮겨 일시 중단 큐에 넣는다. 그 후 OS는 일시 중단 큐에서 다른 프로세스를 가져오거나 새 프로세스 요청을 수락한다.
1). Blocked Blocked / Suspend : Ready 상태의 프로세스가 없고, 모든 프로세스가 Block 상태일 때, OS는 메모리 공간 확보 혹은 메모리 관리를 위해 한 개 이상의 Blocked Process를 저장 장치로 Swapped out한다. 이는 Ready 상태의 프로세스가 있더라도, OS가 메모리 공간 확보가 더 중요하다고 판단하면 이런 전환이 발생할 수 있다.
2). Blocked/Suspend Ready/Suspend : Blocked/Suspend 상태의 프로세스가 기다리고 있던 이벤트가 발생하면 Ready/Suspend 상태로 이동한다. 이를 위해서는 OS가 중단된 프로세스에 대한 상태 정보에 접근할 수 있어야 한다.
3). Ready/Suspend Ready : 메인 메모리에 준비 상태의 프로세스가 없을 때, 혹은 Ready/Suspend 상태의 프로세스가 Ready 상태의 프로세스보다 높은 우선순위를 갖고 있을 때 OS는 Ready/Suspend 상태의 프로세스를 Ready상태로 가져올 수 있다.
4). Ready Ready/Suspend : OS가 충분히 큰 메인 메모리 블록을 확보하기 위해 혹은 더 높은 우선순위의 Ready/Suspend 상태의 프로세스를 Ready상태로 불러오기 위해 등 여러 상황에서 발생할 수 있다. 이때 Ready 상태이던 Process는 Ready/Suspend 상태로 이전할 수 있다.
5). New Ready/Suspend or New Ready : 새 프로세스가 생성될 때, Ready 큐 혹은 Ready/Suspend 큐에 추가될 수 있다. 어느 경우든 OS는 PCB를 생성하고 프로세스에 메모리 공간을 할당해야 한다. 메인 메모리에 새 프로세스를 위한 충분한 공간이 없을 때, Ready/Suspend 큐에 추가된다.
6). Blocked/Suspend Blocked : 높은 우선 순위를 가진 프로세스가 곧 실행될 것으로 예상되며, 메모리 공간이 충분할 때 이 프로세스를 메모리로 가져와 대기시킬 수 있다.
7). Running Ready/Suspend : 일반적으로 실행중인 프로세스는 Time-out을 통해 Ready 상태로 전환된다. 그러나 OS가 더 높은 우선 순위의 프로세스를 실행하기 위해 이런 상태 전환이 발생할 수 있다.
Suspend 상태에 있는 프로세스는 현재 메인 메모리가 아닌 저장 장치에 위치해 있다.
Zombie Process (좀비 프로세스) : 좀비 프로세스는 실행 순서상에 문제가 있을 때 발생할 수 있는데, 자식 프로세스는 종료되었지만, 부모 프로세스가 자식 프로세스의 종료 상태를 OS에 알리지 않아 완전히 사라지지 않은 상태이다. 실행중인 코드가 있거나 메모리를 소유하고 있지 않지만, 프로세스 테이블에는 존재하므로 OS 리소스를 소모한다.
Orphan Process (고아 프로세스) : 부모 프로세스가 종료되었지만, 자식 프로세스가 아직 실행중인 상태이다. OS는 일반적으로 부모가 없는 자식 프로세스를 init 프로세스의 자식으로 지정한다. init 프로세스는 주기적으로 wait() System call을 호출해 자식 프로세스의 종료 상태를 회수한다. 따라서 고아 프로세스는 init 프로세스에 의해 관리된다.
좀비 프로세스와 고아 프로세스의 차이점
1). 좀비 프로세스 : 자식 프로세스가 종료되었지만, 부모 프로세스가 자식 프로세스의 종료 상태를 적절히 회수하지 않아 완전히 시스템에서 제거되지 않은 상태. 실행 코드, 메모리를 소유하지 않지만 프로세스 테이블에 존재한다.
2). 고아 프로세스 : 부모 프로세스가 종료되었지만, 자식 프로세스가 실행중인 프로세스이다. OS는 이를 init 프로세스가 새로운 부모 프로세스가 되도록 하고, init 프로세스가 고아 프로세스의 종료 상태를 관리하도록 한다. 고아 프로세스는 실행중인 프로세스이므로 실행 코드나 메모리 등이 존재한다.
좀비 프로세스 생성 방지 : 부모 프로세스에 wait() System call 호출을 통해 부모 프로세스가 자식 프로세스의 종료를 기다리도록 하여, 부모 프로세스와 자식 프로세스의 종료가 정상적인 순서로 이루어지도록 해야한다.
** 좀비 프로세스가 많이 쌓이면 시스템 관리 및
리소스 관리, 등에서 문제가 생길 수 있으므로
프로그래머는 이런 비정상적인 프로세스의 생성을 방지하고
관리해야한다.
Init Process : (UNIX 및 UNIX 기반 OS 기준) init 프로세스는 최정상에 위치한 프로세스이다. 이 프로세스는 시스템 부팅 과정을 관리하고 시스템의 다양한 작업을 총괄하는 역할을 수행한다.
1). 시스템 부팅 : 시스템이 부팅되면, 커널이 초기화된 이후 최초의 사용자 공간 프로세스로 init 프로세스가 실행된다. init 프로세스는 일반적으로 PID가 1이다.
2). 실행 레벨 설정 및 스크립트 실행 : init 프로세스는 시스템 실행 레벨을 설정한다. 이 실행 레벨에 따라 필요한 시스템 구성 작업을 수행한다.
3). 로그인 프로세스 관리 : 시스템이 초기화되고 실행 레벨에 따른 서비스가 시작되면, init 프로세스는 로그인 프롬프트를 제공하는 프로세스를 실행한다. 이 프로세스들은 사용자가 시스템에 로그인할 수 있도록 터미널이나 그래픽 인터페이스를 제공한다.
4). 쉘 실행 : 사용자가 로그인에 성공하면, 로그인 프롬프트를 제공하는 프로세스는 사용자의 쉘을 실행한다. 이 쉘은 사용자가 명령어를 입력하고 실행할 수 있도록 돕는다.
5). 명령어 실행 : 사용자가 터미널에 명령어를 입력하면, 쉘이 이 명령어를 해석하고 실행한다. 이때 init 프로세스는 직접적으로 관여하지 않지만, 모든 프로세스의 조상으로서 시스템의 기본적인 프로세스 관리와 같은 역할을 계속해서 수행한다.
요약하자면, 사용자가 터미널에서 명령어를 수행하기까지 init프로세스는 시스템 부팅 및 초기화, 실행 레벨 설정, 로그인 프로세스 실행, 이후에는 시스템의 기본적인 프로세스 관리 등의 역할을 수행한다.