프로세스 계층구조

리눅스의 프로세스 계층구조는 부모-자식 관계를 기반으로 형성되며, 트리 구조로 표현된다. 

 

부모 자식 관계

  • 새로운 프로세스는 기존 프로세스가 fork()를 호출하여 생성한다.
  • 프로세스가 생성되면 부모 프로세스의 자식 프로세스로 연결된다.
  • 모든 사용자의 최상위(최초의) 프로세스는 init 또는 systemd이다.

 

프로세스 계층 구조

https://www.researchgate.net/figure/Process-Hierarchy-within-Linux_fig4_220623906

 

Process 0 (Idle Process)

리눅스 시스템 부팅 시 커널이 가장 먼저 생성하는 프로세스로, 메모리 관리 및 시스템 초기화 같은 커널 수준의 작업을 담당한다.

  • 시스템 부팅 중 커널의 초기화를 수행.
  • 이후 사용자 영역의 첫 프로세스인 프로세스 1(init)을 생성.
  • 모든 초기화가 완료되면, Idle Process로 전환되어 CPU의 유휴 상태를 관리.
  • PID 0이며, 사용자 영역에서는 보이지 않으며 커널 내부에서만 존재.
PID (Process ID) : 프로세스를 고유하게 식별하는 번호, 부모 프로세스와 자식 프로세스를 연결하는 데 사용.
PPID (Parent Process ID) : 부모 프로세스의 PID를 나타냄.
UID (User ID) : 프로세스를 실행한 사용자의 ID.
GID (Group ID): 프로세스를 실행한 사용자가 속한 그룹의 ID.

 

Process 1 (Init 또는 Systemd)

프로세스 0이 사용자 공간에서 실행할 첫 프로세스로 생성하는 프로세스이다.

리눅스 시스템에서 다른 모든 사용자 프로세스의 최상위 부모 프로세스가 된다.

  • 파일 시스템 마운트, 네트워크 설정, 데몬 프로세스 실행.
  • 자식 프로세스 생성 및 종료 상태 처리.
  • 시스템 종료와 재부팅 관리.
  • PID 1이며 사용자 영역의 첫 번째 프로세스.
  • 현대 리눅스에서는 init 대신 systemd가 이 역할을 수행.

모든 프로세스는 최초 프로세스(init 또는 systemd)를 기점으로 계층적으로 연결된다.

 

고아 프로세스와 좀비 프로세스

부모 프로세스의 역할

부모 프로세스는 fork() 시스템 호출을 통해 자식 프로세스를 생성하는 프로세스이다.

모든 프로세스는 부모 프로세스를 가지며, 최상위 부모는 init(systemd, PID 1)이다.

  • 부모는 자식 프로세스의 상태를 추적하여 종료 시점에 대한 정보를 확인.
  • 자식 프로세스가 종료되면 부모는 wait() 또는 waitpid()를 호출하여 자식의 종료 상태(exit code)를 수집.
  • 자식 프로세스가 종료되면 부모는 자식의 PCB(Process Control Block) 및 기타 자원을 회수해야 한다.

 

프로세스 종료 상태(Exit Status)

프로세스 종료 상태(Exit Status)는 리눅스 및 유닉스 운영체제에서 프로세스가 종료되었을 때, 부모 프로세스에게 반환하는 값이다. 

이 값은 프로세스의 성공 또는 실패 여부와 종료 원인 등을 나타내며, 디버깅 및 시스템 관리에 중요한 정보를 제공한다.

  • 종료 상태는 0~255 사이의 정수값으로 반환된다.
  • 0: 정상 종료(Success).
  • 0이 아닌 값: 비정상 종료(Failure) 또는 특정 오류 코드.
  • 시그널(Signal)을 받아서 종료되었는지 여부 및 시그널의 종류, 코어 덤프(core dump)를 생성했는지 여부도 포함
* 코어 덤프(Core Dump)는 프로그램이 비정상적으로 종료되었을 때, 해당 시점의 메모리 상태, CPU 레지스터 값, 프로세스의 실행 정보를 저장한 파일이다.

 

자식 프로세스가 종료되면 종료 상태는 부모 프로세스에 전달된다.

부모는 wait() 또는 waitpid()를 호출하여 자식의 종료 상태를 수집한다.

 

고아 프로세스(Orphan Process)

고아 프로세스는 부모 프로세스가 종료되었음에도 자식 프로세스가 계속 실행 중인 상태를 말한다.

즉, 프로세스가 종료되면 PCB 및 기타 자원을 회수해주어야할 부모 프로세스가 있어야하는데, 자식 프로세스가 종료되기 전에 부모 프로세스가 먼저 종료된 상태를 뜻한다.

부모가 먼저 종료되면 자식은 부모 프로세스를 init 프로세스(PID 1)로 재설정하게 된다.

예시
1. A 프로세스가 B 프로세스를 생성했으나, A 프로세스가 예상보다 빨리 종료됨.
2. B는 여전히 작업 중이며, 이 상태에서 B는 고아 프로세스가 됨.
3. 커널은 B의 부모를 init 프로세스로 변경하여 관리.

 

 

좀비 프로세스(Zombie Process)

좀비 프로세스는 프로세스 실행이 종료되었지만, 부모 프로세스가 자식의 종료 상태를 수집하지 않아 PCB 및 기타 시스템 자원이 시스템에 남아 있는 상태를 말한다.

  • 프로세스가 종료되면 커널은 종료 코드를 보존하고, 부모가 이를 회수(wait() 호출)할 때까지 PCB를 유지한다.
  • 부모가 회수하지 않으면 좀비 프로세스가 계속 남아 있어 시스템 리소스를 낭비하게 된다.

 

<해결 방법>

  • 부모 프로세스가 wait() 또는 waitpid()를 호출하여 자식의 종료 상태를 회수해야 함.
  • 부모 프로세스가 좀비 상태를 방치하면, 부모 프로세스를 강제 종료하여 자식을 고아 프로세스로 만들어 init 프로세스가 회수하게 함.

 

표준 스트림과 파일 디스크립터

리눅스는 모든 입출력 작업을 파일로 추상화하여 처리한다. 

표준 스트림은 프로그램 실행 시 기본적으로 제공되는 입출력 경로를 의미하며, 파일 디스크립터는 이러한 입출력 대상(파일, 소켓, 파이프 등)을 식별하기 위한 정수이다.

 

표준 스트림(standard streams)

표준 스트림은 프로세스가 입출력 작업을 수행하기 위해 기본적으로 제공되는 세 가지 스트림이다. 

프로세스 실행 시 자동으로 생성되며, 각각 고유의 역할을 가진다.

from 위키피디아

이름 파일 디스크립터 번호(FD) 설명
표준 입력 (stdin) 0 사용자의 입력을 받아들이는 스트림
표준 출력 (stdout) 1 프로그램의 실행 결과를 출력하는 스트림
표준 에러 (stderr) 2 에러 메시지를 출력하는 스트림
  • 표준 입력 : 기본적으로 키보드로 입력하는 것을 뜻함
  • 표준 출력 : 실행 결과가 모니터로 출력되는 것을 뜻함
  • 표준 에러 : 에러 메시지가 출력되는 것을 뜻함

 

파일 디스크립터(file descriptor)

파일 디스크립터는 리눅스와 같은 운영체제에서 파일이나 입출력 장치(예: 키보드, 모니터)를 다루기 위한 숫자 식별자이다. 

이 숫자는 프로세스가 열어놓은 파일이나 장치에 접근할 때 사용되며, 일종의 ID 번호라고 생각하면 된다.

컴퓨터는 모든 입출력을 파일로 취급한다. 예를 들어, 키보드 입력이나 화면 출력도 사실상 파일처럼 다룬다. 파일 디스크립터는 프로그램이 이러한 파일(혹은 입출력 장치)을 가리키고 사용하는 번호표이다.

 

  • 파일에 대한 작업(읽기/쓰기)을 하기 위해서는 파일 디스크립터를 열어야 한다.
  • 프로세스로 열린 파일 디스크립터 목록을 관리한다.
* “프로세스로 열린 파일 디스크립터 목록을 관리한다”는 말은, 각각의 프로세스(즉, 실행 중인 프로그램)가 자신이 열어놓은 파일이나 장치의 목록을 운영체제에서 따로따로 관리한다는 뜻이다.

* 프로그램(프로세스)이 실행되면서 여러 파일을 열거나 장치에 접근할 때, 운영체제는 각 프로세스마다 어떤 파일을 열었는지 목록을 기억해둔다.
이를 통해 프로세스가 필요할 때 열어둔 파일에 쉽게 접근할 수 있고, 프로그램이 끝날 때 열린 파일을 자동으로 닫아주는 관리가 가능하다.

 

 

Foreground, Background, Daemon Process

Foreground Process

Foreground Process는 사용자가 터미널에서 직접 실행하고 제어하는 프로세스이다. 

사용자가 실행을 시작하면, 이 프로세스는 터미널에 입력과 출력을 독점하며, 사용자가 해당 프로세스가 종료되거나 중단될 때까지 터미널은 다른 명령을 받지 않는다.

  • 쉘의 표준 입력이 연결된 프로세스
  • 표준 출력과 표준 에러도 쉘과 연결되어 있음
  • 사용자가 직접 실행하고, 종료되기 전까지 터미널은 해당 프로세스에 점유된다.
  • 프로세스의 입력과 출력이 모두 터미널을 통해 이루어진다.
  • 사용자는 한 번에 하나의 전경 프로세스만 제어할 수 있다.

 

Background Process

Background Process는 사용자가 터미널에서 실행했지만, 실행되는 동안 터미널을 점유하지 않는 프로세스이다. 

즉, 프로세스가 실행되는 동안에도 사용자는 터미널에서 다른 명령을 입력하고 작업을 수행할 수 있다. 

백그라운드 프로세스는 일반적으로 사용자와 상호작용하지 않으며, 필요할 경우 백그라운드에서 계속 작업을 진행한다.

  • 쉘의 표준 입력이 연결되지 않은 프로세스
  • 표준 출력과 표준 에러는 쉘과 연결되어 있음
  • 터미널에 입력과 출력이 독립적으로 이루어진다.
  • 프로세스는 터미널에서 독립적으로 실행되며, &를 붙여 실행하거나 bg 명령으로 백그라운드로 전환할 수 있다.
  • 여러 개의 백그라운드 프로세스를 동시에 실행할 수 있다.

 

Daemon Process

Daemon Process는 백그라운드에서 지속적으로 실행되며, 시스템 서비스나 특정 작업을 수행하는 프로세스이다. 

일반적인 백그라운드 프로세스와 달리, 데몬 프로세스는 터미널과 완전히 분리되어 시작되며, 시스템 시작 시 자동으로 실행되거나 특정 서비스의 요청을 처리하기 위해 상시 대기한다. 

주로 서버, 네트워크 관리, 시스템 유지 보수 등의 기능을 수행한다.

  • 백그라운드 프로세스로 동작하기 위해 만들어진 프로세스
  • 표준 스트림을 갖고 시작하지만 모두 닫아버리기 때문에 쉘과 입출력 교환이 불가하다.
  • 부모 프로세스를 init(또는 systemd) 프로세스로 변경

 

 

시그널(Signal)

시그널은 비동기(Asynchronous) 이벤트를 처리하기 위한 프로세스간 통신이라고 말할 수 있다.

쉽게 말하면, 시그널(Signal)은 리눅스와 유닉스 시스템에서 프로그램에 특정 이벤트가 발생했음을 알리기 위한 알림 메시지(인터럽트)라고 생각하면 된다. 

시그널을 통해 시스템은 프로세스(프로그램)에게 이벤트를 알려주고, 프로그램은 이 신호를 받아서 특정 작업을 하거나 종료할 수 있다.

 

시그널과 비동기 방식은 프로그램에서 예기치 않게 발생하는 일을 즉각적으로 처리하는 데 사용된다. 

각각 다른 개념이지만, 둘 다 작업의 흐름을 방해하지 않으면서 알림이나 작업을 처리할 수 있도록 돕는다.

 

비동기 방식은 작업을 요청한 후 그 작업이 끝날 때까지 기다리지 않고 다음 일을 계속하는 방식이다. 

비동기 방식에서는 결과를 기다리지 않고 다른 작업을 먼저 처리할 수 있다.

 

https://velog.io/@cyongchoi/%EB%B9%84%EB%8F%99%EA%B8%B0asynchronous-%EA%B0%9C%EB%85%90

시그널이 발생하는 것은 그림에서 클라이언트가 서버로 요청을 보내는 것과 비슷하다.

시스템(클라이언트)은 프로세스(서버)에게 이벤트 내용을 시그널을 통해 비동기적으로 보낸다.

비동기 방식이기 때문에 프로그램은 시그널 요청을 받고도 계속해서 원래 하던 작업을 유지할 수 있다.

그러나 시그널이 발생하면 이 요청에 대한 응답(즉, 시그널 처리를 위한 코드 실행)을 예약해 두거나 준비하게 된다.

시그널이 발생한 후, 프로그램이 준비가 되었을 때 시그널 핸들러(Signal Handler)를 통해 시그널을 처리한다.

이 과정에서 프로그램은 시그널을 처리하면서 “필요한 일을 수행하고”(예: 로그 작성, 작업 중단 등) 다시 원래 작업으로 돌아가게 된다.

 

시그널의 비동기적 방식 요약
1. 시그널은 프로세스가 어떤 작업을 하고 있는지와 상관없이 발생할 수 있다.
2. 시그널은 프로세스가 언제든지 받을 수 있고, 시그널이 발생하면 프로세스는 즉시 시그널을 받게 된다. 하지만 비동기적 방식이기 때문에, 시그널을 받은 후에도 기존 작업을 중단하지 않고 필요에 따라 시그널을 처리할 시점을 결정할 수 있다.
3. 이후 프로그램은 시그널에 대한 응답(시그널 핸들러 실행)을 통해 시그널을 처리하고 필요한 동작을 수행하게 된다.

 

주요 시그널

Signal Table
시그널 이름 의미 기본 처리
SIGABRT abort()에서 보냄. 의도적인 중단을 의미 코어 덤프
SIGALRM alarm()에서 보냄. 정해진 시간이 됐다는 의미 종료
SIGBUS 하드웨어 버스 에러 코어 덤프
SIGCHLD 자식 프로세스 종료 무시
SIGSTOP 프로세스 중지 정지
SIGCONT 중지된 프로세스 재시작 시작
SIGHUP 프로세스의 제어 터미널이 닫힘 종료
SIGILL 프로세스가 부적절한 명령을 실행 코어 덤프
SIGINT 사용자가 인터럽트(Ctrl + C)를 생성 종료
SIGQUIT 사용자가 종료 문자(Ctrl + \) 생성 코어 덤프
SIGTSTP 사용자가 일시 중지 문자(Ctrl + z) 생성 정지
SIGIO 비동기 입출력 종료
SIGPIPE 프로세스가 잘못된 파이프에 쓰기 작업을 시도 종료
SIGSEGV 메모리 세그먼트 접근 위반하는 프로그램 오류 코어 덤프
SIGFPE 산술 연산 예외가 발생하는 프로그램 오류 코어 덤프
SIGKILL 프로세스 종료 명령 종료
SIGTERM 프로세스 종료 명령(조건에 따른 처리 가능) 종료
SIGUSR1 사용자 정의 시그널 1 종료
SIGUSR2 사용자 정의 시그널 2 종료