리눅스 시스템 기초 · 입문편 — 본문의 "N편"은 입문 과정 번호다. 번호별 글과 전체 250편 구성은 통합 로드맵에서 확인할 수 있다.
리눅스 시스템 기초 31 / 50 · Part 4. 프로세스·서비스·리소스
실습 환경: Rocky Linux 9 (10.0.0.200) · 좀비·고아·스레드는 실습 환경에서 재현한 실제 출력
이전 글: 30. Linux 명령어를 활용한 보안 점검
Part 4 「프로세스·서비스·리소스」를 시작한다. 지금까지 프로세스는 여러 번 나왔다. 4편에서 커널 스레드 위장을, 5편에서 fork/execve를, 29편에서 의심 프로세스 분석을 다뤘다. 이번 글은 프로세스라는 개념 자체를 정리한다.
관제에서 프로세스를 정확히 이해해야 하는 이유는 "지금 서버에서 무엇이 실행되고 있는가"가 침해 판단의 핵심 증거 이기 때문이다. 로그는 과거를 보여주지만, 프로세스는 현재 진행 중인 공격 을 보여준다.
이번 글에서 답할 질문:
| 구분 | 정의 | 예 |
|---|---|---|
| 프로그램 | 디스크에 저장된 실행 파일 (정적) | /usr/sbin/sshd |
| 프로세스 | 메모리에 올라가 실행 중인 프로그램 (동적), 자기만의 주소 공간·PID 보유 | PID 812의 sshd |
| 스레드 | 프로세스 안에서 실행되는 흐름 단위, 주소 공간을 공유 | httpd의 작업 스레드 |
같은 프로그램을 여러 번 실행하면 프로세스가 여러 개 생긴다. 한 프로세스는 스레드를 여러 개 가질 수 있다.
| 비교 | 프로세스 | 스레드 |
|---|---|---|
| 메모리 | 독립된 가상 주소 공간 (4편) | 같은 프로세스의 메모리를 공유 |
| 식별자 | PID | TID(LWP). 같은 프로세스의 스레드는 같은 TGID(=PID) |
| 생성 비용 | 큼 (fork) | 작음 (clone + 공유 플래그) |
| 하나가 죽으면 | 다른 프로세스는 영향 없음 | 보통 프로세스 전체가 종료 |
| 확인 | ps -ef | ps -eLf, /proc/PID/task/ |
Linux 커널 내부에서는 프로세스와 스레드 모두 task 라는 같은 구조로 관리된다. 차이는 메모리·파일 등을 얼마나 공유하느냐다.
| 정보 | 의미 | 확인 위치 |
|---|---|---|
| PID | 프로세스 번호 | ps, /proc/PID |
| PPID | 부모 프로세스 번호 | /proc/PID/status |
| UID / EUID | 실행 계정 / 권한 검사 계정 (16편) | /proc/PID/status |
| 상태 | R, S, D, T, Z | ps STAT |
| 실행 파일 | 실제 경로 | /proc/PID/exe |
| 명령줄 | 실행 인자 | /proc/PID/cmdline |
| 열린 파일 | 파일·소켓 | /proc/PID/fd/ |
| 작업 디렉터리 | cwd | /proc/PID/cwd |
| STAT | 이름 | 의미 |
|---|---|---|
R | Running | 실행 중이거나 CPU를 기다리는 중 |
S | Sleeping | 이벤트를 기다리는 중 (깨울 수 있음) — 대부분의 프로세스 |
D | Uninterruptible sleep | 디스크·NFS I/O 대기 — 시그널로도 깨울 수 없음 |
T | Stopped | SIGSTOP, Ctrl+Z로 정지 |
Z | Zombie | 종료됐지만 부모가 종료 상태를 회수하지 않음 |
I | Idle | 유휴 커널 스레드 |

fork()로 자신을 복제한다 (새 PID, PPID = 부모).execve()로 다른 프로그램으로 바뀐다 (1·5편).exit()로 끝나면 커널은 종료 코드를 보관하고 부모에게 SIGCHLD 를 보낸다.wait()로 종료 코드를 회수하면 프로세스 정보가 완전히 사라진다.| 구분 | 발생 조건 | 상태 | 해결 |
|---|---|---|---|
| 좀비 (Zombie) | 자식은 끝났는데 부모가 wait()를 안 함 | Z, 자원은 거의 없고 PID 항목만 남음 | 부모가 회수하거나, 부모가 종료되면 PID 1이 회수 |
| 고아 (Orphan) | 부모가 먼저 종료 | 자식은 계속 실행, PPID가 1(systemd)로 바뀜 | 정상적으로 계속 실행 |
실습 환경에서 두 상황을 재현한 실제 결과다.
== 좀비: 자식은 종료, 부모(659)는 wait() 안 함
PID PPID STAT COMMAND
661 659 Z python3
== 고아: 부모 bash가 먼저 종료 → PID 1이 입양
PID PPID STAT COMMAND
664 1 S sleep
좀비는 kill -9로도 없앨 수 없다. 이미 죽은 프로세스이기 때문 이다. 부모를 종료시키면 PID 1이 입양해서 회수한다.
# 1) 프로세스 개수, 스레드 수 포함 개수
ps -e --no-headers | wc -l
ps -eL --no-headers | wc -l
# 2) 상태별 분포
ps -eo stat --no-headers | cut -c1 | sort | uniq -c
# 3) 특정 프로세스 상세
P=$(pgrep -o sshd)
grep -E '^(Name|State|Tgid|Pid|PPid|Uid|Threads)' /proc/$P/status
ls -l /proc/$P/exe /proc/$P/cwd
# 4) 스레드 보기
ps -o pid,nlwp,comm -C httpd # NLWP = 스레드 수
ps -L -o pid,lwp,comm -p $P
ls /proc/$P/task
# 5) 좀비·D 상태 찾기
ps -eo pid,ppid,stat,comm | awk '$3 ~ /^[ZD]/'
# 6) 고아처럼 PID 1이 부모인 사용자 프로세스
ps -eo pid,ppid,user,comm | awk '$2==1 && $3!="root"'
① 스레드 확인 (실습 환경 실제 결과)
$ ps -o pid,nlwp,comm -p 668
PID NLWP COMMAND
668 4 python3
$ ps -L -o pid,lwp,comm -p 668
PID LWP COMMAND
668 668 python3
668 670 python3
668 671 python3
668 672 python3
$ grep -E '^(Tgid|Pid|PPid|Threads)' /proc/668/status
Tgid: 668
Pid: 668
PPid: 656
Threads: 4
PID는 668 하나지만 스레드(LWP)는 4개다. 첫 번째 스레드의 LWP는 PID와 같다(메인 스레드). ps -ef에는 한 줄로, ps -eLf에는 네 줄로 나온다.
② 상태별 판단
| 관찰 | 해석 |
|---|---|
대부분 S | 정상 (이벤트 대기) |
R이 CPU 수보다 많이 지속 | CPU 부족, 채굴 악성코드 가능성 (33·39편) |
D가 오래 지속 | 디스크·NFS 장애, 시그널로 종료 불가 |
Z가 계속 쌓임 | 부모 프로그램의 버그 — 대량이면 PID 고갈 위험 |
모르는 T | 누군가 SIGSTOP으로 멈춰 둔 프로세스 (분석 중일 수도) |
| 관찰 | 보안 의미 |
|---|---|
| 고아 프로세스(PPID 1) + 사용자 계정 + /tmp 실행 | 공격자가 부모를 끝내고 백그라운드로 남긴 프로세스 (29편 사례: PPID 1, /tmp/.cache/kw) |
| 이중 fork(double fork) | 데몬화 기법 — 부모를 끊어 세션 종료 후에도 살아남게 함. 정상 데몬도 쓰지만, 웹 서버 아래에서 발생하면 의심 |
| 스레드 수 급증 | 채굴·DDoS 도구는 CPU 코어 수만큼 스레드를 만드는 경우가 많음 |
| 대량 fork (fork bomb) | 프로세스를 무한 생성해 서비스 거부 → ulimit -u, systemd TasksMax로 제한 |
| 프로세스 이름 위장 | 이름은 바꿀 수 있음 → exe·PPID로 확인 (4·29편) |
# PID 1이 부모인 비root 프로세스 (고아·데몬화된 사용자 프로세스)
ps -eo pid,ppid,user,etimes,args | awk 'NR==1 || ($2==1 && $3!="root")'
# 스레드가 많은 프로세스 상위
ps -eo pid,nlwp,user,comm --sort=-nlwp | head
# 좀비와 부모
ps -eo pid,ppid,stat,comm | awk '$3 ~ /^Z/ {print "zombie", $1, "parent", $2}'
etimes는 프로세스가 실행된 지 몇 초 지났는지다. 로그인 성공 시각(21편)과 비교하면 그 로그인 직후 시작된 프로세스 를 골라낼 수 있다.
[Event] 28편 리포트: 02:19 devuser 로그인 성공
↓
[프로세스] ps -eo pid,ppid,user,etimes,args
→ devuser 소유, PPID 1, 02:20 시작, /tmp 실행
↓
[판단] 로그인 세션이 끝나도 남도록 부모를 끊은 프로세스 → 지속 실행 목적
↓
[다음] 29편 방식으로 /proc/PID/exe · ss · lsof 분석
ps -eLf, /proc/PID/task로 확인한다.kill로 없앨 수 없다.etimes로 프로세스 시작 시각을 로그인 시각과 비교한다.다음 글 「32. ps로 프로세스 분석」 에서는 이번 글의 정보를 실제로 뽑아내는 ps를 자세히 다룬다. BSD 형식(aux)과 UNIX 형식(-ef)의 차이, 각 컬럼의 의미, -o로 필요한 필드만 골라 헌팅용 출력을 만드는 방법 을 정리한다.