Logo
Overview

[OS-반효경] Process Management

July 18, 2025
5 min read

저번 시간에는 프로세스에 대해 알아보았습니다. 이번 포스팅에서는 프로세스 생성과 종료, 유닉스 시스템의 시스템 콜, 프로세스 통신에 대해 알아보겠습니다.

프로세스 생성(Process Creation)

A_tree_of_processes_on_a_typical_Linux_system

프로세스 생성은 부모 프로세스(Parent Process)가 자식 프로세스(Children Process)를 생성합니다. 운영체제 내의 프로세스들은 위 사진처럼 계층적인 트리 구조를 형성합니다. 이렇게 새로운 자식 프로세스가 생성되는 과정은 크게 자원 공유, 실행 방식, 주소 공간의 세 가지 관점에서 살펴볼 수 있습니다.

  • 자원 공유 관점
    • 부모와 자식이 모든 자원을 공유하는 모델
    • 자식이 부모 자원의 일부를 공유하는 모델
    • 부모와 자식이 자원을 공유하지 않는 모델 → 일반적인 방식
  • 실행(Execution) 관점
    • 부모와 자식은 공존하며 각자 수행되는 모델
    • 자식이 종료(terminate)될 때까지 부모가 기다리는(wait)모델
  • 주소 공간(Address Space) 관점
    • 자식은 부모 공간(OS Data, Binary)을 그대로 복사하여 자신만의 공간을 생성합니다. 이를 통해 부모와 거의 똑같은 독립적인 프로세스가 만들어집니다. → fork()
    • fork()로 생성된 자식 프로세스의 메모리 공간에 새로운 프로그램을 불러와 덮어씌웁니다. 이를 통해 자식 프로세스는 부모 프로세스와 다른 자신만의 코드를 실행합니다. → exec()

이처럼 fork() 시스템 콜은 유닉스 계열 시스템에서 새로운 프로세스를 생성하고 실행하는 표준적인 방식입니다. fork()로 부모를 복제하고, exec()를 통해 새로운 프로그램을 실행합니다.

UNIX Process Creation

프로세스 생성을 유닉스 시스템의 시스템 콜 함수로 설명드리겠습니다.

fork()

fork() 시스템 콜은 부모 프로세스에서 자식 프로세스를 생성하는 시스템 콜입니다.

int main() {
pid_t pid;
pid = fork();
if(pid > 0)
printf("\nI am parent of pid=%d!\n", pid);
else if(pid == 0)
printf("I am the child!\n", pid);
}

위 예시에서 fork()로 새로운 프로세스가 생성 되었습니다. 만약 PID가 0이라면 해당 프로세스는 자식 프로세스이고, PID가 1이상인 값(자식 프로세스 PID)이면 부모 프로세스 입니다. 부모 프로세스는 위 예시 코드의 처음부터 끝을 실행하지만, 자식 프로세스는 fork() 이후 부분부터 실행됩니다.

fork() 시스템 콜의 특징은 다음과 같습니다.

  • 자식 프로세스는 부모 프로세스의 거의 모든 것을 상속받음
  • 각 프로세스(부모-자식)은 독립적으로 실행되어, 메모리를 공유하지 않음
    • COW(Copy-on-Write)1 기법으로 메모리를 일시적으로 공유하지만, 부모와 자식 프로세스는 별개의 프로세스로 스케줄러에 의해 관리됩니다.
  • fork() 반환값으로 부모 프로세스는 자식의 PID를 반환받음
  • fork() 반환값으로 자식 프로세스는 0을 반환받음

exec()

exec() 시스템 콜은 새로운 실행 파일을 디스크에서 찾아 현재 프로세스의 메모리 공간을 그 실행 파일의 내용으로 교체한 뒤, 해당 프로그램의 정해진 시작점부터 실행합니다. 즉 exec() 호출이 성공하면, 전달된 프로그램의 메모리로 덮어씌워 현재 프로그램 코드는 더이상 실행하지 않습니다.

int main() {
char *arg[] = {"ls", "-l", (char *)0};
printf("before executing ls -l\n");
execv("/bin/ls", arg);
printf("after executing ls –l.\n");
}

위 예시 코드를 실행하면 execv() 호출 이후 printf("after executing ls –l.\n")는 실행되지 않습니다. /bin/ls의 프로세스 메모리로 덮어써버리기 때문에 뒤에 있던 프로그램 내용은 실행할 수가 없습니다.

프로세스 종료(Process Termination)

이렇게 생성된 프로세스는 언젠가 자신의 목적을 이루고 종료됩니다. 이제 프로세스가 어떻게 마무리되는지, 프로세스 종료에 대해 알아보겠습니다.

프로세스 종료에는 exit()abort()가 있습니다. 먼저, 프로세스가 마지막 문맥에 도달하면 운영체제에 종료(exit())를 요청합니다. 프로세스가 종료되면 운영체제는 프로세스가 점유하고 있던 자원을 해제합니다. 그리고 자식 프로세스에게서 부모 프로세스에 결과를 전달합니다. 이때, 부모 프로세스는 대기(wait()) 상태라서 자식 프로세스의 결과를 받을 수 있습니다.

이제, 부모 프로세스가 자식 프로세스의 실행을 종료시키는 경우(abort())에 대해 알아보겠습니다. 부모 프로세스가 자식 프로세스의 실행을 종료 시키는 경우는 다음과 같습니다.

  • 자식 프로세스에게 할당된 자원의 한계치가 넘어선 경우
  • 자식 프로세스에게 할당된 작업이 더 이상 필요하지 않은 경우
  • 부모 프로세스가 종료(exit())하는 경우 → 운영체제는 부모 프로세스가 종료하는 경우 자식이 더 이상 수행되지 않도록 하기 때문에 단계적으로 자식 프로세스가 먼저 종료되고 부모 프로세스가 종료 됩니다.

프로세스와 관련한 시스템 콜

이처럼 프로세스와 관련한 시스템 콜은 fork(), exec(), wait(), exit()이 있습니다.

fork()는 프로세스를 만드는 시스템 콜이고, exec()은 현재 실행 중인 프로세스의 메모리 공간을 완전히 새로운 프로그램으로 덮는 시스템 콜입니다.

wait() 시스템 콜에 대해 자세히 설명 드리겠습니다. wait() 시스템 콜은 부모 프로세스가 자식 프로세스의 실행이 끝날 때까지 기다리도록 하는 함수입니다. 부모 프로세스에서 wait()이 호출되면, 자식 프로세스가 종료될 때까지 부모 프로세스는 실행을 멈추고 대기 상태가 됩니다.

자식 프로세스가 작업을 마치고 종료하면, 운영체제는 해당 자식 프로세스의 3가지 반환 코드(PID, Exit Status, CPU Usage)를 부모 프로세스에게 전달하고, 대기하던 부모 프로세스는 다시 실행을 재개합니다. 이를 통해 부모는 자식의 작업이 정상적으로 완료되었는지 등을 확인할 수 있습니다.

만약 자식 프로세스가 먼저 종료되었지만 부모 프로세스가 wait()을 호출하여 종료 상태를 정리해주지 않으면, 해당 자식 프로세스는 ‘좀비(Zombie) 프로세스’ 가 됩니다. 좀비 프로세스는 자식 프로세스의 실행이 끝났음에도 부모 프로세스가 해당 자식 프로세스를 정리하지 않아 자식 프로세스가 시스템의 자원을 계속 차지하는 상태입니다. 따라서 부모 프로세스는 자식 프로세스가 좀비 프로세스가 되지 않기 위해 wait()을 통해 종료 상태를 정리해야 합니다.

마지막으로 exit() 시스템 콜은 해당 프로세스의 자원 할당을 해제하고 부모 프로세스에 알리는 시스템 콜입니다.

Process_Creation_System_Call

이러한 프로세스의 생성과 종료를 나타낸 과정이 위 그림에 나와 있습니다.

Inter Process Communication(IPC)

지금까지 프로세스 생성과 종료 그리고 관련된 시스템 콜에 대해 알아보았습니다. 마지막으로 프로세스 통신에 대해 알아보겠습니다.

프로세스는 독립적인 프로세스와 협력 프로세스가 있습니다. 독립적인 프로세스(Independent Process)는 프로세스 각자의 주소 공간을 가지고 실행됨으로, 원칙적으로 하나의 프로세스는 다른 프로세스 실행에 영향을 미치지 못합니다. 협력 프로세스(Cooperating Process)는 프로세스 협력 메커니즘을 통해 하나의 프로세스가 다른 프로세스 실행에 영향을 미칠 수 있습니다. 프로세스 간 협력하는 매커니즘을 IPC(Inter Process Communication)라 하는데, IPC에는 메세지를 전달하는 방법과 주소 공간을 공유하는 방법이 있습니다.

Message_Passing

메세지를 전달하는 방법(Message Passing)은 공유 변수를 사용하지 않고 커널을 통해 메세지를 전달합니다. 메세지를 전달하는 방법에는 직접 통신과 간접 통신이 있는데, 직접 통신(Direct Communication)은 통신하려는 프로세스의 이름을 명시적으로 표시하여 전달하는 방식입니다. 간접 통신(Indirect Communication)은 포트를 통해 메세지를 간접 전달하는 방식입니다. 각 포트는 고유한 ID가 있고, 프로세스는 같은 포트를 공유할 때만 통신할 수 있습니다.

Shared_Memory

주소 공간을 공유하는 방법(Shared Memory)은 위 그림처럼 일부 주소 공간을 공유하여 통신하는 방법입니다.

Conclusion

이번 포스팅에서는 프로세스 생성과 종료를 알아보고 그와 관련된 시스템 콜을 살펴보았습니다. 또한, 프로세스가 어떻게 협력하는지 협력하는 방법에 대해 알아보았습니다. 다음 포스팅에서는 어떤 프로세스에게 CPU를 할당할 것인지 결정하는 CPU 스케줄링에 대해 알아보겠습니다.

References

[1] KCOW 운영체제 반효경 강의

[2] Operating System Concepts(Silberschatz, Galvin and Gagne)

Footnotes

  1. COW(Copy-on-Write): 부모나 자식 중 누군가 메모리에 쓰기(Write) 작업을 시도할 때, 그제야 해당 메모리 페이지가 복사되어 독립적인 공간을 갖게되는 기법