본문 바로가기

취업과 기본기 튼튼/빽 투더 기본기

빽 투더 기본기 [OS 1편]. 프로세스

이 글에서는 운영체제의 기초가 되는, Process (프로세스)에 대해 정리해본다.

1. Process 란

1.1. Program vs Process

  • Program : 디스크에 저장된 실행가능한 명령어 파일. 수동적인 개체.
  • Process : 메모리에 올려져있는 실행중인 프로그램. 적극적인 개체.

프로그램이 실행되면 (메모리에 로드되면) 프로세스가 된다.

1.2. Process in Memeory

프로세스는 메모리에서 다음과 같은 독립적인 공간을 할당받는다.

  • Stack : 임시 데이터들을 담는다. ex. 리턴 어드레스, 지역 변수
  • Heap : 동적 할당 데이터들을 담는다. ex. malloc()
  • Data : 전역 변수를 담는다.
  • Text : 모든 코드를 담는다.

1.3. Process State

프로세스는 다음과 같은 5가지 상태가 있다.

  • new : 프로세스가 생성된 직후의 상태이다.
  • ready : 프로세스가 실행되기를 기다리는 상태이다.
  • running : 프로세스에 구현된 명령들이 실행되는 상태이다.
  • waiting : 프로세스가 실행 중, 잠시 기다리는 상태이다.
  • terminated ; 프로세스가 실행을 끝낸 상태이다.

1.4. PCB (Process Control Block)

PCB는 프로세스의 정보를 담고있는 블락으로, 하나의 프로세스는 하나의 PCB 를 가진다.
PCB는 다음과 같은 정보들을 담고있는다.

2. Process Scheduling

2.1. Process Scheduling 이란

컴퓨터 공학의 목표 중 하나는, CPU를 100% 활용하게 하는 것이다. 또한, 유저와 프로세스들이 빠르게 상호작용 하도록 하는 것이다. CPU의 자원은 한정되어있고, 같은 시간안에 여러개의 프로세스를 관리해야하는 상황이다.
따라서, 효율적인 CPU 사용을 위해, 프로세스들간의 실행시간을 조정하는 Process Scheduling 이 필요하게 된다.

2.2. Process Scheduling Queue

Process State 각각에 Queue 가 있다. 예를 들어, Ready Queue, Running Queue 식으로.
Scheduling 이란 프로세스들을 이런 Queue 간에 이동시키는 일을 한다.

2.3. I/O Bound vs CPU Bound Process

  • I/O bound Process : 연산보다, I/O 에 더 시간이 들어가는 프로세스다. CPU burst 가 작다.
  • CPU bound Process : 연산에 주로 시간이 들어가는 프로세스다. CPU burst 가 크다.

CPU burst 란, 프로세스에서 CPU 사용 구간이라고 생각하면 된다.

2.4. Context Switching

CPU 할당이 스케쥴링에 의해, 현재 실행중인 프로세스에서, 다른 프로세스로 넘어갈 때, 기존의 실행 중인 프로세스 정보를 저장하고, 실행될 프로세스를 로드하는 일을 말한다.

  • PCB가 프로세스를 저장하고 로드하는데 사용된다.
  • Context Switching 동안, 시스템은 아무일도 하고 있지 않은 idle 상태가 되므로, 비효율적인 상태가 된다.
    즉, overhead가 크다.

3. Process Creation

현재 프로세스에서 새로운 프로세스를 생성하거나 실행시키는 경우, 2가지 구현 방법이 있다.
모두 OS에서 제공해주는 시스템 콜(System call) 로 fork()exec() 이다.

3.1. fork()

fork() 는 OS가 새로운 메모리 공간을 할당하도록 한 후, 현재 프로세스의 코드와 정보를 모두 새로운 메모리 공간에 복사하도록 한다. 즉, 현재 실행중인 프로세스와 동일한 프로세스 하나를 더 만드는 셈이다.
프로세스는 결과적으로 1개에서 2개가 되는 셈이다.

핵심은 다음과 같다.

1) `fork()` 를 하면, 현재 프로세스의 자식 프로세스를 생성한다.  
2) 이 때, 반환 값이 0이면 자식이고, 0보다 크면 부모 프로세스를 의미한다. (즉 자식 프로세스를 pid 로 구분해야함)  
3) 부모 프로세스를 그대로 복사하여 생성하기 때문에, PC(프로그램 카운터)도 그대로 가져간다.
즉 자식 프로세스는 `fork()` 이 시점부터 프로그렘이 실행된다. 한편 저장되어있던 지역변수 등 모두 동일하다.

3.2. exec()

exec() 는 OS가 현재 프로세스의 공간에 새로운 프로세스를 덮어쓰게 한다.
예를 들어, execl("/bin/ls", "ls") 와 같이 했다면, 이 명령어를 담고있는 프로세스에 해당 프로그램 /bin/ls 를 실행시킨다.
즉, /bin/ls 이 성공했으므로, 뒤에 수행되는 코드들은 모두 덮혀버린다.
프로세스는 결과적으로 그대로 1개인 셈이다.

자세한 내용은 아래 링크 참조.

fork() 와 exec() : https://channelofchaos.tistory.com/55

4. Process Termination

일반적으로, 프로세스가 마지막 명령어(코드) 를 수행하면 OS에게 삭제해달라는 exit() 요청을 하게된다.
다만, 이 때 자식 프로세스가 아직 실행중인 경우, 부모 프로세스는 OS에 의해 wait 명령을 받아 wait 상태에 있게 된다.
이후, 모두 완료되면 OS가 할당했었던 메모리를 회수해간다.

보통 부모 프로세스가, 자식 프로세스보다 먼저 삭제되는 경우, OS는 이를 비정상적인 상황이라 인지하고 해당 부모의 자식 프로세스도 모두 삭제시키는데, 이를 Cascading Termination 이라 한다.

4.1. Zombie vs Orphan Process

Zombie Process
  • 실행이 완료된 프로세스임에도, 여전히 프로세스 테이블에 남아있는 (지워지지 않은) 프로세스를 말한다.
  • 이러한 프로세스가 발생하는 이유는 다음과 같다.
    • 부모 프로세스는 자식 프로세스의 실행이 끝난 뒤의 상태(status) 를 받기 위해 계속 wait(&status) 하고 있는다.
    • 자식 프로세스는 실행이 끝나고 exit status 로 상태를 바꾼 뒤, 이를 부모 프로세스에게 전달하고, 부모가 전달받을 때까지 기다린다.
    • 이 기다리는 동안, (부모 프로세스가 자식 프로세스를 회수해가지 않는 동안) 메모리 공간을 잡아먹지는 않지만, 프로세스 테이블에는 남아있으므로 일종의 Zombie 상태가 된다.
  • 여하튼, 종료된 프로세스임에도 프로세스 테이블에 남아있는 것은 좋지않으므로,
    부모 프로세스에서 반드시 wait(&status) 를 통해, 자식 프로세스의 종료상태를 읽어야 한다.
Orphan Process
  • 자식 프로세스가 종료되기 전에, 부모 프로세스가 먼저 지워진 자식 프로세스를 말한다.
  • 이러한 프로세스가 발생하는 이유는 부모 프로세스 실행 중, 비정상적인 종료 혹은 wait() 을 하지 않았기 때문이다.
  • Orphan Process 는 OS에 의해 Init Process 가 부모로 할당된다.

5. IPC (Inter-Process Communication)

프로세스간 통신하는 방법에는 다음과 같은 방법이 있다.

5.1. Shared Memory

일반적으로 프로세스들은 메모리에서 각자의 독립된 공간을 보장받는다.
즉 어떤 프로세스가 다른 프로세스의 메모리 영역에 접근할 수 없도록 OS가 설계되어져 있다.
하지만, Shared Memory 를 OS 에게 요청하면, 프로세스의 해당 영역은 다른 프로세스와 공유할 수 있는 공간이 된다.
프로세스간 통신에서 가장 빠르게 작동하는 방법이다.
다만, 동기화를 직접 해줘야 한다는 것, 메모리 공간을 직접 제어해줘야한다는(생성, 삭제) 단점이 있다.

5.2. Message passing

위 그림에서도 보이듯, 기본적으로 큐 형태를 취하기 때문에, 동기화 제어가 쉽게 가능하다.
다만, 동시에 Shared Memory 보다 통신 속도가 느리다.
대표적으로 아래와 같이 3가지 구현 형태가 있다.

1) Pipe

OS 에서 제공하는 pipe buffer 로, 한 프로세스에서 다른 프로세스로 통신하는, 단 방향 통신 방법이다.
ex. ps -aux | grep root | tail 에서 | 가 pipe 다.

2) Named Pipe

mkfifo() 라는 시스템 콜로 이름이 있는 파이프를 만들어 통신한다.
이전의 Pipe (익명 Pipe 라고 한다.) 는 통신을 할 프로세스를 명확히 아는 경우(이를 테면 부모와 자식 프로세스 간)에만 사용 가능하지만, Named Pipe의 경우, 파이프의 이름을 통해 통신에 접근하기 때문에, 모든 프로세스 간에 통신이 가능하다.

하지만 여전히 Pipe 의 특성상, 읽기/쓰기가 동시에 가능하지 않으며, 따라서 일반적으로 읽기용 Pipe, 쓰기용 Pipe, 이렇게 2개를 사용한다.

3) Message Queue

커널에서 관리하는 큐를 통해 프로세스간 통신한다.
Named Pipe 처럼 일종의 식별자를 통해 각 프로세스마다 필요한 자료에 접근하게 된다.
Named Pipe 와 다른 점은, Pipe 는 단방향인데 반해, Queue는 양방향이라는 점, 메모리 공간에 할당된다는 점 등이 다르다.

Named Pipe와 Message Queue의 차이점
What are all the differences between pipes and message queues?

4) 그 외

그 외로 Socket, Semaphore 등이 있다.