프로세스란 실행 중인 프로그램이다. 프로세스의 현재 활동의 상태는 프로그램 카운터(PC) 값과 프로세서 레지스터의 내용으로 나타낸다.
프로세스의 메모리는 아래의 그림과 같이 배치되어 있다.
프로그램(Program)은 명령어 리스트를 내용으로 가진 디스크에 저장된 파일(실행 파일이라고 불림)과 같은 수동적인 존재(passive entity)이다.
프로세스(Process)는 다음에 실행할 명령어를 지정하는 프로그램 카운터(PC)와 관련 자원의 집합을 가진 능동적인 존재(active entity)이다.
실행 파일이 메모리에 적재될 때 프로그램은 프로세스가 된다.
어느 한 순간에 한 Processor의 코어에서는 오직 하나의 프로세스만이 running 상태이다는 것을 인식하는 것이 중요하다. 그렇지만 많은 프로세스가 ready or waiting 상태에 있을 수 있다.
각 프로세스는 운영체제에서 프로세스 제어 블록(process control block, PCB)에 의해 표현된다.
ㅊ
PCB는 약간의 회계 데이터와 함께 프로세스를 시작시키거나 다시 시작시키는데 필요한 모든 데이터를 위한 저장소의 역할을 한다.
프로세스가 시스템에 들어가면 준비 큐(Ready Queue)에 들어가서 준비 상태가 되어 CPU 코어에서 실행되기를 기다린다.
I/O 완료와 같은 특정 이벤트가 발생하기를 기다리는 프로세스는 대기 큐(Wait Queue)에 삽입된다.
프로세스 스케줄링의 일반적인 표현은 아래와 같은 큐잉 다이어그램이다.
프로세스의 개수를 제어한다.
요즘에는 안 쓰인다.
10ms의 timer마다 scheduler가 호출된다.
Disk와 memory에서 누구를 swap할지 결정한다.
인터럽트는 운영체제가 CPU 코어를 현재 작업에서 뺏어 내어 커널 루틴을 실행할 수 있게 한다.
인터럽트가 발생하면 시스템은 인터럽트 처리가 끝난 후에 Context을 복구할 수 있도록 현재 실행 중인 프로세스의 Context을 PCB에 저장한다.
Context은 CPU 레지스터의 값, 프로세스 상태, 메모리 관리 정보 등을 포함한다.
CPU 코어를 다른 프로세스로 교환하려면 이전의 프로세스의 상태를 보관하고 새로운 프로세스의 보관된 상태를 복구하는 작업이 필요하다.
이 작업은 context switch이라고 하고 아래의 그림에 묘사되어 있다.
실행 중인 프로세스는 여러 개의 새로운 프로세스들을 생성할 수 있다.
이때, 생성하는 프로세스를 부모 프로세스라고 부르고, 새로운 프로세스는 자식 프로세스라고 부른다. 이 새로운 프로세스들은 각각 다시 다른 프로세스들을 생성할 수 있으며, 그 결과 프로세스의 트리를 형성한다.
현대 운영체제들은 유일한 프로세스 식별자(pid)를 사용하여 프로세스를 구분하는데 이 식별자는 보통 정수이다.
새로운 프로세스는 fork() 시스템 콜로 생성되고, 원래 프로세스의 주소 공간의 복사본으로 구성된다.
exec() 시스템 콜은 이진 파일을 메로리로 적재(load)하고 그 프로그램을 실행을 시작한다.
원래의 프로그램의 메모리 이미지를 파괴한다.
부모 프로세스는 자식 프로세스가 실행되는 동안 할 일이 없으면 자식이 종료될 때까지 준비 큐에서 자신을 제거하기 위해 wait() 시스템 콜을 한다.
프로세스가 마지막 문장의 실행을 끝내고, exit 시스템 콜을 사용하여 운영체제에 자신의 삭제를 요청하면 종료한다.
물리 메모리와 가상 메모리, 열린 파일, 입출력 버퍼를 포함한 프로세스의 모든 자원이 할당 해제되고 운영체제로 반납된다.
부모 프로세스는 다음과 같이 여러가지 이유로 자식 중 하나의 실행을 종료할 수 있다.
프로세스가 종료하면 사용하던 자원은 운영체제가 되찾아 간다. 종료되었지만 부모 프로세스가 아직 wait() 호출을 하지 않은 프로세스를 좀비(zombie) 프로세스라고 한다.
부모 프로세스가 wait()을 호출해서 자식 프로세스의 종료를 기다리지 않고 자신을 종료해버린다면 이 상황에 부닥친 자식 프로세스를 고아(orphan) 프로세스라고 한다. UNIX의 경우 고아 프로세스는 init 프로세스(Linux의 systemd와 동일)가 상속하고 종료를 관리한다.
운영체제 내에서 실행되는 병행 프로세스들은 독립적이거나 또는 협력적인 프로세스들 일 수 있다.
프로세스 협력을 제공하는 이유는 다음과 같다.
협력적 프로세스들은 데이터를 교환할 수 있는 프로세스 간 통신(interprocess communication, IPC) 기법이 필요하다.
이에 대해서는 기본적으로 공유 메모리(shared memory)와 메시지 전달(message passing) 두 가지 모델이 있다.
메시지 패싱은 우편이다. 송신 프로세스가 정보를 받는 수신 프로세스에게 커널을 통해 정보를 전달하며, 수신 프로세스도 커널에 접근해 정보를 수신한다. 메시지 패싱은 컨텍스트 스위치가 발생하기 때문에 속도가 느리다. 다만 커널이 기본적인 기능을 제공하므로 공유 메모리 방식에 비해선 구현이 쉽다.
명명(Naming)
통신을 원하는 프로세스들은 서로를 가리킬 방법이 있어야 한다. 이들은 간접 통신 또는 직접 통신을 사용할 수 있다.
직접 통신
send(P, message) - 프로세스 P에 메시지를 전송한다.
receive(Q, message) - 프로세스 Q로부터 메시지를 수신한다.
간접 통신
간접 통신에서는 메일박스(mailbox) 또는 포트(port)로 메시지가 왔다 갔다 한다.
send(A, message) - 메시지를 메일박스 A로 송신한다.
receive(A, message) - 메시지를 메일박스 A로부터 수신한다.
공유 메모리는 게시판이다. 특정 메모리 공간을 두 프로세스가 함께 사용하며 정보를 주고 받는다. 커널을 거치지 않기 때문에 속도가 빠르지만 메모리에 동시 접근하는 것을 방지하기 위해 프로그래머가 따로 구현을 해줘야 한다.
협력하는 프로세스 중 정보를 생산하는 프로세스를 생산자(Producer), 정보를 소비하는 프로세스를 소비자(Consumer)라고 부른다. 생산자-소비자 문제는 두 프로세스가 동시에 동작할 때 일어나는 이슈를 말한다. 보통 정보가 생산되는 속도가 소비하는 속도보다 빠르기 때문에 동기화 문제가 발생하는데, 이를 해결하기 위해 생산된 데이터를 담아두는 버퍼(Buffer)를 사용한다. 크기에 한계가 있는 버퍼를 유한 버퍼(Bounded buffer), 버퍼의 시작과 끝을 이어붙여 크기가 무한한 버퍼를 무한 버퍼(Unbounded buffer)라고 한다.