리눅스 프로세스: 구조부터 제어까지

@Jiu· April 01, 2026 · 17 min read

리눅스에서 실행되는 모든 프로그램은 프로세스 단위로 관리된다.

프로세스는 PID, 부모-자식 관계, 실행 상태, 자원 사용 정보등을 가지며,
운영체제는 이를 기준으로 프로그램의 실행을 추적하고 제어한다.

이 글에서는 리눅스 프로세스가 어떤 구조로 동작하고 관리되는지 다음 순서로 정리한다.


프로세스의 시작: PID 1



리눅스에서 사용자 공간의 모든 프로세스는
부팅 직후 커널이 실행한 첫 번째 프로세스에서 출발한다.
이 첫 프로세스가 바로 PID 1 이다.

커널은 사용자 프로그램을 하나씩 직접 실행하지 않는다.
대신 사용자 공간의 시작점이 되는 프로세스 하나를 먼저 실행하고,
이후의 서비스와 세션 관리는 그 프로세스가 맡는다.


커널은 직접 모든 사용자 프로그램을 실행하지 않고,
사용자 공간의 시작점이 되는 PID 1 을 먼저 실행한다.


부팅 이후 실행 흐름


부팅 직후 실행 흐름은 다음과 같다.

  1. 펌웨어(BIOS/UEFI) 가 하드웨어를 초기화
  2. 부트로더(GRUB 등) 가 리눅스 커널을 메모리에 적재
  3. 커널(kernel) 이 메모리, CPU, 장치 드라이버, 파일시스템 등을 초기화
  4. 커널이 사용자 공간의 첫 프로세스인 PID 1 을 실행
  5. PID 1 이 이후 서비스와 세션을 시작

즉, 사용자 공간은 커널이 실행한 PID 1 에서부터 시작된다.


PID 1의 역할


PID 1은 이후 사용자 공간 전체의 기준점이 된다.

  • 모든 사용자 공간 프로세스의 최상위 부모
  • 서비스 시작 및 관리
  • orphan 프로세스 수용

init과 systemd


이러한 PID 1 역할을 전통적으로는 init 이 맡았고, 현대 리눅스에서는 대부분 systemd 가 맡는다.
둘 다 사용자 공간의 첫 프로세스라는 점은 같지만, 시스템을 관리하는 방식에는 차이가 있다.

왜 init에서 systemd로 넘어갔을까?


전통적인 init은 현대 시스템을 운영하기에는 몇 가지 한계가 있었다.

  • 서비스들을 순차적으로 실행해서 부팅이 느릴 수 있음
  • 서비스 간 의존성 관리가 어려움
  • 로그, 타이머, 소켓 같은 운영 기능이 분리되어 있음

반면 systemd는 이런 한계를 보완하면서, 현대 리눅스 운영 환경에 맞는 기능을 함께 제공한다.

  • 의존성 기반 실행
  • 병렬 부팅
  • 서비스/로그/타이머의 통합 관리

init은 서비스를 순서대로 띄우는 시작점에 가까웠다면

systemd는 서비스들을 관계 기반으로 관리하는 운영 체계에 가깝다.



프로세스의 구조: 계층적 트리



PID 1 이후에 만들어지는 프로세스들은
서로 독립적으로 존재하는 것이 아니라, 부모-자식 관계를 가지는 트리 구조를 이룬다.

그래서 프로세스를 이해할 때는
무엇이 실행 중인지만 보는 것이 아니라, 누가 누구를 생성했는가까지 함께 봐야 한다.

이 구조를 이해하면 다음과 같은 점에서 도움이 된다.

  • 어떤 프로세스가 누가 만든 것인지를 통해 동작 흐름을 이해할 수 있다
  • 장애 분석 시 원인을 추적하기 쉽다
  • 종료나 재시작 시 영향 범위를 판단할 수 있다

트리 구조의 특징


프로세스 트리는 몇 가지 기본 특징을 가진다.

  • 모든 프로세스는 고유한 PID 를 가진다
  • 대부분의 프로세스는 자신을 만든 부모의 PPID(Parent PID) 를 가진다
  • 새로운 프로세스는 기존 프로세스가 생성한다
  • 사용자 공간 프로세스 트리의 최상위 기준점은 PID 1(systemd) 이다

프로세스의 상속


자식 프로세스는 부모가 가진 실행 문맥 일부를 이어받은 상태에서 시작한다.

대표적으로 다음과 같은 것들이 상속된다.

  • 환경 변수
  • 현재 작업 디렉터리
  • 파일 디스크립터
  • 사용자 및 권한 정보
  • 일부 리소스 제한값

이런 상속 구조 덕분에, 셸에서 환경 변수를 export 한 뒤 명령어를 실행하면
그 명령어가 실행된 프로세스도 같은 환경을 사용할 수 있다.


orphan과 zombie 프로세스


프로세스 트리에는 일반적인 부모-자식 관계만 있는 것이 아니라,
운영 중 자주 언급되는 예외적인 상태도 있다.

orphan process

  • 부모 프로세스가 먼저 종료되어 고아가 된 자식 프로세스
  • 자식 프로세스는 보통 PID 1(systemd)가 수용한다
  • 부모가 종료되었다고 해서 자식이 즉시 함께 종료되지는 않는다

zombie process

  • 자식 프로세스는 이미 종료되었지만, 부모가 종료 상태를 아직 회수하지 않아
    프로세스 테이블에 남아 있는 상태
  • 실제로 실행 중인 것은 아니며, 부모가 wait() 계열 호출로 상태를 회수하면 정리된다

orphan은 살아 있는 자식이 부모를 잃은 상태이고,
zombie는 이미 종료된 자식의 상태 정보가 아직 회수되지 않은 상태다.



프로세스의 생성과 실행



프로세스 트리는 기존 프로세스가 새로운 프로세스를 만들면서 형성된다.

리눅스에서는 이 과정을 보통 fork() 와 exec()로 설명한다.

  • fork(): 새 프로세스를 만든다
  • exec(): 현재 프로세스를 다른 프로그램으로 바꾼다

리눅스에서 프로그램 실행은
프로세스 생성(fork) 과 프로그램 교체(exec) 가 이어지는 구조로 이해할 수 있다.


fork(): 프로세스 생성


fork() 는 현재 프로세스를 복제하여 자식 프로세스를 만든다.

pid = fork();

fork() 가 호출되면, 부모와 자식은 거의 동일한 실행 문맥을 가진 상태에서 각각 실행을 이어간다.

반환값은 다음과 같다.

  • 부모 프로세스: 자식의 PID 반환
  • 자식 프로세스: 0 반환
  • 실패 시: -1

부모와 자식은 같은 코드에서 시작하지만, 반환값이 다르기 때문에 이후 서로 다른 동작으로 분기할 수 있다.


exec(): 프로그램 교체


exec() 계열 함수는 현재 프로세스의 메모리 공간을 새 프로그램으로 덮어쓴다.

exec("/bin/ls", ...);

exec() 는 새 프로세스를 만들지 않고, 이미 존재하는 프로세스를 다른 프로그램으로 교체한다.

따라서 다음의 특징이 있다.

  • 기존 PID 유지
  • 기존 프로세스 자리에 새 프로그램이 올라감
  • 성공하면 원래 코드로 돌아오지 않음

왜 fork() 와 exec() 를 나눴을까


두 과정이 분리되어 있어서 프로세스를 실행하기 전에 필요한 준비를 먼저 할 수 있다.

예를 들면 자식 프로세스에서 exec() 전에 다음과 같은 작업이 가능하다.

  • 표준 입력/출력 리다이렉션
  • 파일 디스크립터 정리
  • 환경 변수 설정
  • 권한 변경

즉 실행 환경을 먼저 준비한 뒤 원하는 프로그램으로 교체할 수 있기 때문에
fork() 와 exec() 가 나뉘어 있다.


Shell에서 명령어가 실행되는 흐름


터미널에서 ls 를 입력하면 개념적으로 다음과 같이 동작한다.
(실제 구현에서는 clone이나 posix_spawn 같은 최적화가 사용될 수 있다.)

bash → fork → exec(ls)
  • bash 가 자식 프로세스를 하나 만든다
  • 자식 프로세스가 exec() 를 호출해 ls 프로그램으로 바뀐다
  • 부모 셸은 자식이 끝날 때까지 기다리거나, 상황에 따라 백그라운드로 넘긴다


프로세스 관찰 및 제어



이제 실행 중인 프로세스를 어떻게 확인하고, 상태를 바꾸고, 자원 사용을 조정할 것인지 알아보자.

리눅스에서 프로세스 제어는 크게 세 가지로 나뉜다.

  • 조회: ps, top
  • 상태 변경: kill
  • 자원 사용 제어(우선순위): nice, renice

ps: 현재 프로세스 조회


*ps: process status의 약자

현재 시점의 프로세스 상태를 정적인 스냅샷으로 보여준다.


자주 쓰는 옵션

  • a: 다른 사용자 프로세스도 포함
  • u: 사용자 중심 포맷
  • x: 터미널 없는 프로세스도 포함
  • e: 환경 변수까지 함께 표시
  • f: 부모-자식 관계를 포함한 full format 표시

자주 쓰는 형태

# 가장 많이 쓰이는 형태
ps aux

# 시스템 전체 프로세스를 계층/관계 중심으로 보기 좋다.
ps -ef

# 특정 PID만 조회
ps -p <PID>

# 프로세스 트리 형태로 보기  
ps -ef --forest

top: 실시간 프로세스 모니터링


시스템의 CPU, 메모리, 로드 평균, 프로세스 상태를 실시간으로 갱신해서 보여준다.


확인하는 것

  • CPU 사용률
  • 메모리 사용량
  • load average
  • 어떤 프로세스가 자원을 많이 먹는지
  • 프로세스 상태 (R, S, D, Z 등)

자주 쓰는 형태

# 특정 PID만 집중 모니터링
top -p <PID>

# 특정 사용자의 프로세스만 보기
top -u <user_name>

# 갱신 주기 지정
top -d <second>

# 전체 커맨드라인 표시
top -c

실행 중 자주 쓰는 키

  • P: CPU 사용량 기준 정렬
  • M: 메모리 사용량 기준 정렬
  • k: 프로세스 종료 신호 보내기
  • r: nice 값 변경
  • 1: CPU 코어별 보기
  • q: 종료

kill: 프로세스에 제어 시그널 전달


프로세스를 종료하는 명령어가 아니라, 프로세스에 시그널을 보내는 명령어다.
종료뿐 아니라 재로드, 일시 정지, 재개 같은 제어에도 사용할 수 있다.


자주 쓰는 형태

# 기본 종료 요청 - SIGTERM(15)
kill <PID>

# SIGTERM 명시
kill -TERM <PID>

# 재시작 없이 설정 재로드 용도로 자주 사용
kill -HUP <PID>

# 시그널 목록 확인
kill -l

자주 쓰는 시그널

시그널 의미 용도
SIGTERM (15) 정상 종료 요청 가장 먼저 시도
SIGKILL (9) 강제 종료 최후 수단
SIGINT (2) 인터럽트 Ctrl+C 유사
SIGHUP (1) hangup/reload 설정 재적용 용도
SIGSTOP 일시 정지 즉시 멈춤
SIGCONT 재개 중단 후 다시 실행

운영 주의사항

  • 무조건 kill -9 부터 쓰지 않는다
  • 먼저 SIGTERM 으로 정상 종료를 시도한다
  • 종료가 안 될 때만 SIGKILL 고려한다.
  • 서비스라면 kill 보다 systemctl stop/restart 가 우선이다

nice / renice: CPU 우선순위 조정


리눅스 스케줄러는 모든 프로세스에 동일하게 CPU를 주지 않는다.
각 프로세스는 우선순위를 가지며, 사용자는 nice 값을 통해 이를 조정할 수 있다.


기본 규칙

  • 값 범위: -20 ~ 19
  • 숫자가 낮을수록 우선순위 높음
  • 숫자가 높을수록 우선순위 낮음

nice: 새 프로세스를 특정 우선순위로 시작

nice -n 10 python app.py

renice: 이미 실행 중인 프로세스의 우선순위 변경

renice -n 5 -p 1234

nice와 renice는 프로세스를 종료하지 않고도
CPU 경쟁에서의 상대적 우선순위를 조정할 수 있게 해준다.


정리



  • 모든 프로세스는 PID 1에서 시작된다
  • 프로세스는 트리 구조로 연결된다
  • 실행은 fork → exec으로 이루어진다
  • 운영자는 이를 조회(ps/top)하고 제어(kill/nice) 한다
@Jiu
잘 기록하기