
[ 글의 목적: 리눅스 OS 에서 process 를 kill (IPC 중 signal 방식) 할때 SIGTERM, SIGKILL 동작에 대한 기록 ]
습관적으로
pkill -9 ...을 때리다가 어떻게 OS 는 process를 "safe" 한 방식으로 죽일 수 있을까 부터 시작해 UNIX/Linux 의 Signal IPC 까지 올라가서 러프하게 정리한 글이다.
한 번 상상해보자. python 으로 작성한
a.py에while True가 있어도,Ctrl + C한 방이면KeyboardInterrupt발생하면서 stop 되고 결국 프로세스는 죽는다. 이 흐름을 정말 A to Z 까지 알고 있는가? 이게 왜 중요한가? 우린 OS 의 "프로세스" 와 "쓰레드" 의 생명주기와 IPC 는 기억하지만, 이 흐름은 놓치고 있는 경우가 많다.(근데 나도 몰랐다.)
KeyboardInterrupt 이어서..위에 얘기한 흐름을 다시 정리해보자면, 우리는 shell 에서 python 을 실행한다. python a.py 와 같이.
해당 a.py 는 python 이라는 S/W 를 기반으로 runtime 이 구성되고, python interpreter 가 실행되며 foreground 프로세스로 실행 한다.
이 상태에서 Ctrl + C를 누르면, 터미널은 이를 특수 제어 문자(ETX - End of Text, 0x03)로 인식한다. (이는 python 에서 인식하는게 아니라 OS 에서 인식한다!!)
터미널은 이 키 입력(0x03)을 감지하면 foreground에서 실행 중인 프로세스(예: Python)에 SIGINT 시그널을 보낸다. 이건 POSIX 표준이자 리눅스/유닉스/macOS에서 동일하게 작동하는 규칙 이다.
이때 Python 인터프리터는 자체 등록한 SIGINT 핸들러를 통해 KeyboardInterrupt 예외를 발생시킨다.
Python은 인터프리터(ex - CPython 구현체)가 시작될 때 signal 모듈을 통해 자체적으로 SIGINT 에 대한 핸들러를 기본으로 등록해 두고 있다.
이 예시외에도 nginx 같은 S/W level 의 web-server process 를 죽일때, gunicorn 이나 apache & tomcat 으로 돌아가는 spring server 를 죽일때도 비슷한 흐름이다.
이제부터 Signal 에 대해서 좀 더 자세하게 알아보자. 가장 먼저 종료 signal 의 대표적인 예시인 SIGTERM 과 SIGKILL 차이점은 아래와 같다.
SIGTERM, SIGKILL 은 "프로세스 종료" 에 더 가깝다. 여기서는 사실 SIGTERM, SIGKILL 에 대해 더 자세히 알아보고자 한다. | 구분 | SIGINT (2) | SIGTERM (15) | SIGKILL (9) |
|---|---|---|---|
| 의미 | 사용자 중단 요청 (Interrupt) | 정상 종료 요청 (Termination Request) | 강제 종료 (Forced Kill) |
| 발생 시점 | 주로 사용자가 Ctrl+C 입력 | 일반적으로 kill 명령 사용 시 기본값 | kill -9 또는 시스템 강제 종료 시 |
| 기본 동작 | 프로세스에 인터럽트 요청 → 정상적인 종료 처리 실행 | 종료 핸들러 실행 기회 제공 | 즉시 강제 종료 |
| 프로세스 대응 | 무시 가능, 시그널 핸들러로 처리 가능 | 무시 가능, 시그널 핸들러로 처리 가능 | 무시 불가능 (non-maskable, non-catchable) |
| 우선순위 | 사용자 개입 수준 | 일반 종료 시퀀스 | 최우선, 무조건 종료 |
| 리소스 정리 | 가능 (시그널 핸들러에서 정리 가능) | 가능 (핸들러 내부에서 리소스 정리 수행 가능) | 불가능 (리소스 누수 위험 있음) |
| 커널 개입 | 커널이 시그널 전달 → 프로세스 핸들링 | 커널이 시그널 전달 → 프로세스 핸들링 | 커널이 직접 종료 (컨텍스트 무시하고 즉시 종료) |
| 사용 예시 | 사용자가 Ctrl+C 입력 | kill PID (기본값) | kill -9 PID |
| 시스템 콜 | kill(pid, SIGINT) | kill(pid, SIGTERM) | kill(pid, SIGKILL) |
| 프로세스 상태 전이 | Running → Signal Handling → (계속/종료) | Running/Waiting → Terminating → Zombie → Removed | Running/Waiting → Zombie → Removed |
| 자식 프로세스 처리 | 일반적으로 전파되지 않음 | 애플리케이션 설정에 따라 자식에게도 전파 가능 | 자식 프로세스 포함 전체 강제 종료 (kill -9 트리 구조 시) |
| 비동기성 | 처리 핸들링 가능 (시그널 핸들러 등록) | 비동기적으로 처리 가능 | 즉시 처리됨, 핸들링 불가 |
SIGTERM 은 "정중한 종료 요청"으로, 프로세스에게 "작업을 마무리하고 종료해달라"는 의미이며, SIGKILL 은 "즉각적인 강제 종료 명령"으로 프로세스에게 어떤 기회도 주지 않고 즉시 종료시킨다.파이프(Pipe)와 명명된 파이프(Named Pipe)
| 연산자를 사용한 파이프라인메시지 큐(Message Queue)
msgget(), msgsnd(), msgrcv() 시스템 콜 사용공유 메모리(Shared Memory)
세마포어(Semaphore)
소켓(Socket)
시그널(Signal)

| 신호 이름 | 번호 | 설명 | 기본 동작 | 원인 |
|---|---|---|---|---|
| SIGHUP | 1 | 행업(Hangup) | 종료 | 제어 터미널이 종료될 때 |
| SIGINT | 2 | 인터럽트 | 종료 | Ctrl+C 키 입력 |
| SIGQUIT | 3 | 종료 및 코어 덤프 | 코어 덤프와 함께 종료 | Ctrl+\ 키 입력 |
| SIGILL | 4 | 잘못된 명령어 | 코어 덤프와 함께 종료 | 잘못된 CPU 명령 실행 |
| SIGTRAP | 5 | 트랩 | 코어 덤프와 함께 종료 | 디버깅용 트랩 |
| SIGABRT | 6 | 중단 | 코어 덤프와 함께 종료 | abort() 함수 호출 |
| SIGFPE | 8 | 부동 소수점 예외 | 코어 덤프와 함께 종료 | 0으로 나누기 등 연산 오류 |
| SIGKILL | 9 | 강제 종료 | 종료 (무시 불가) | 관리자 권한으로 강제 종료 |
| SIGUSR1 | 10 | 사용자 정의 1 | 종료 | 사용자/애플리케이션 정의 |
| SIGSEGV | 11 | 세그멘테이션 오류 | 코어 덤프와 함께 종료 | 잘못된 메모리 참조 |
| SIGUSR2 | 12 | 사용자 정의 2 | 종료 | 사용자/애플리케이션 정의 |
| SIGPIPE | 13 | 파이프 깨짐 | 종료 | 닫힌 파이프에 쓰기 시도 |
| SIGALRM | 14 | 알람 | 종료 | alarm() 함수로 설정된 타이머 만료 |
| SIGTERM | 15 | 종료 | 종료 | kill 명령의 기본값 |
| SIGCHLD | 17 | 자식 상태 변경 | 무시 | 자식 프로세스가 종료되거나 중지될 때 |
| SIGCONT | 18 | 계속 실행 | 중단된 프로세스 재개 | 중단된 프로세스를 계속 실행 |
| SIGSTOP | 19 | 중지 | 프로세스 중지 (무시 불가) | 프로세스 중지 |
| SIGTSTP | 20 | 터미널 중지 | 프로세스 중지 | Ctrl+Z 키 입력 |
| SIGTTIN | 21 | 터미널 입력 | 프로세스 중지 | 백그라운드 프로세스가 터미널에서 읽기 시도 |
| SIGTTOU | 22 | 터미널 출력 | 프로세스 중지 | 백그라운드 프로세스가 터미널에 쓰기 시도 |
SIGUSR1 로 재미있는 걸 해볼 수 있지 않을까?SIGTERM 과 SIGKILL 의 차이정확하겐 POSIX 표준에 정의된 시그널 관련 시스템 콜과 라이브러리 함수를 해당 레포에서 직접 확인할 수 있다. - https://github.com/torvalds/linux?tab=readme-ov-file
시그널 전송 과정
kill PID를 실행하면, 커널의 kill() 시스템 콜이 호출된다.시그널 전달 및 큐잉
task_struct 내의 시그널 마스크와 핸들러 정보를 확인한다.SIGTERM 시그널이 해당 프로세스의 시그널 큐(sigqueue)에 추가된다.컨텍스트 스위칭과 시그널 처리
SIGTERM 이 발견되면, 커널은 다음을 확인한다:sigprocmask()로 블록했는지)sigaction()으로 설정)핸들러 실행
SIGTERM 핸들러가 등록되어 있다면close())프로세스 종료
exit() 시스템 콜을 호출하여 정상 종료한다.좀비 프로세스 정리
wait() 또는 waitpid()를 호출하면, 자식의 종료 상태를 회수하고 프로세스 테이블 엔트리가 완전히 제거된다.wait()를 호출하지 않으면, 좀비 프로세스가 남게 된다.시그널 전송 과정:
kill -9 PID를 실행하면, 역시 kill() 시스템 콜이 호출된다.특수 처리
SIGKILL 은 특별한 시그널로 커널은 이를 프로세스의 시그널 큐에 넣지 않는다.강제 종료 프로세스:
do_exit() 커널 함수가 호출되어 프로세스 종료 절차를 진행한다:EXIT_ZOMBIE 로 변경한다.SIGCHLD 시그널을 보낸다.사용자 공간 코드 실행 없음
SIGKILL 은 프로세스의 사용자 공간 코드가 실행될 기회를 전혀 주지 않는다.SIGTERM call 이 더 안전한 종료 접근법이다!!좀비 프로세스와 후속 처리:
SIGTERM 과 마찬가지로, 프로세스는 부모가 wait()를 호출할 때까지 좀비 상태로 남는다.실행 컨텍스트
SIGTERM: 프로세스의 사용자 공간 코드(시그널 핸들러)가 실행된다.SIGKILL: 전적으로 커널 공간에서 처리되며, 사용자 코드는 실행되지 않는다.리소스 정리
SIGTERM: 애플리케이션이 자신의 리소스(임시 파일, 네트워크 연결, 데이터베이스 트랜잭션 등)를 정리할 수 있다.SIGKILL: 애플리케이션 수준의 리소스 정리가 불가능하며, 커널 수준의 리소스만 정리된다.실행 시간
SIGTERM: 핸들러 실행과 정리 과정으로 인해 종료에 시간이 걸릴 수 있다.SIGKILL: 즉시 종료되어 지연이 최소화된다.안전성
SIGTERM: 정상적인 종료 절차를 통해 데이터 무결성을 보장할 가능성이 높다.SIGKILL: 데이터 손실이나 불일치, 네트워크 연결 문제 등을 야기할 수 있다.

데이터 무결성 보장
클라이언트 영향 최소화
분산 시스템 일관성
SIGTERM 핸들러를 직접 추가해서 처리하는 방향으로 접근해보자!Kubernetes의 종료 프로세스
SIGTERM 을 받은 후 terminationGracePeriodSeconds 동안 기다린다 (기본 30초).SIGKILL 을 보내 강제 종료한다.Systemd의 종료 프로세스
TimeoutStopSec 설정으로 종료 타임아웃을 지정 (기본 90초).SIGKILL 을 보낸다.Docker의 종료 프로세스
docker stop 명령은 컨테이너에 SIGTERM을 보낸다.SIGKILL 을 보낸다.docker stop --time=<seconds> 옵션으로 타임아웃 조정 가능.적절한 타임아웃 설정 전략
그러니 이제
SIGTERM을 위해kill PID을 기본으로 사용하되, 경우에 따라kill -9 PID를 사용하는게 어떨까?to. 스스로에게...
# 1. 먼저 SIGTERM으로 정상 종료 시도
kill <PID>
# 2. 일정 시간 대기 (5-10초)
sleep 10
# 3. 프로세스가 여전히 실행 중인지 확인
if ps -p <PID> > /dev/null; then
echo "Process still running, sending SIGKILL..."
kill -9 <PID>
else
echo "Process terminated gracefully."
fi
#!/bin/bash
terminate_with_timeout() {
local pid=$1
local timeout=${2:-30} # 기본 30초 타임아웃
# 프로세스가 존재하는지 확인
if ! ps -p $pid > /dev/null; then
echo "Process $pid does not exist."
return 0
fi
# SIGTERM 전송
echo "Sending SIGTERM to process $pid..."
kill $pid
# 타임아웃 내에 종료되는지 확인
local count=0
while ps -p $pid > /dev/null && [ $count -lt $timeout ]; do
sleep 1
count=$((count + 1))
echo "Waiting for process to terminate: $count/$timeout seconds"
done
# 여전히 실행 중이면 SIGKILL 전송
if ps -p $pid > /dev/null; then
echo "Process still running after $timeout seconds, sending SIGKILL..."
kill -9 $pid
return 1
else
echo "Process terminated gracefully."
return 0
fi
}
# 사용 예: terminate_with_timeout <PID> [timeout_in_seconds]
SIGKILL 의 남용이 초래할 수 있는 문제들!데이터 일관성 문제
리소스 누수
분산 시스템 문제
실제 사례 분석
다중 계층 애플리케이션
리더-팔로워 시스템
장기 실행 작업이 있는 시스템
데이터베이스 종료
man 7 signalman 2 killman 2 sigactionman 3 signalkernel/signal.cinclude/linux/signal.h