터미널을 이용하는 중에 ctrl + c를 눌러 작업을 종료해본 경험이 있을 것이다. 어떤 과정을 거쳐서 프로그램이 종료되는지 살펴보고자 한다.
try:
input("Ctrl+C를 누르세요: ")
except KeyboardInterrupt:
print("\nKeyboardInterrupt 발생")
위와 같이 짧은 python 파일을 만들고 terminal에서 이걸 실행해보자. python hello.py로 실행하면 다음처럼 입력을 기다리는 상태가 된다.
$ python3 hello.py
Ctrl+C를 누르세요:
여기서 ctrl + c를 입력하면 예상한 대로 KeyboardInterrupt가 발생하여 출력을 하고 종료가 된다.
Ctrl+C를 누르세요: ^C
KeyboardInterrupt 발생
우선 어떤 요소들로 이루어져 있는지부터 살펴보자. 터미널 앱과 터미널 안에서 실행한 Python 프로그램이 있다. 그리고 운영체제가 있을 것이다. 맥에선 터미널을 실행하면 기본으로 zsh를 사용하는데 zsh와 같은 Shell 프로그램도 별도의 요소이다. 간단히 이런 구조로 보인다.
Terminal Emulator
↕
Shell / Python
터미널 앱에서는 ls 같은 명령어의 의미를 이해하지 못한다. Shell이 명령의 의미를 해석하고 그에 맞는 동작을 운영체제에 요청하는 구조이다. Python 프로그램이 실행되면 Shell은 그대로 존재하면서 대기하고, 터미널의 foreground process group이 Shell 그룹에서 Python 작업 그룹으로 변경된다. SIGINT가 하나의 프로세스가 아니라 foreground process group 전체에 전달되기 때문에 파이프라인으로 여러 프로세스를 실행한 경우에도 Ctrl+C로 해당 작업 전체를 중단할 수 있다.
터미널과 Shell/Python은 어떻게 상호작용할까? 결론은 직접 소통하지 않고 PTY(pseudo-terminal)를 통해 소통한다. 운영체제가 관리하는 PTY는 파일 디스크립터 인터페이스를 제공하여 입출력을 위해 사용할 수 있다. 터미널 앱과 Shell/Python은 이 파일 디스크립터를 이용하여 데이터를 주고받는다. 대략 다음 구조가 된다.
사용자 공간 커널
────────────────────
터미널 앱 ── master FD ──→ PTY master
↕
TTY line discipline
↕
Shell/Python ── slave FD ──→ PTY slave
예를 들어 사용자가 ls를 입력하면 터미널 앱이 master FD에 write하고, Shell은 slave FD를 가리키는 fd를 통해 이를 read한다. master/slave FD는 프로세스가 커널의 PTY master/slave endpoint에 접근하기 위해 사용하는 파일 디스크립터다.
정리하면, 우리가 ls라는 명령어를 입력할 때 Shell에 직접 ls를 전달하는 것이 아니다. 터미널 앱이 입력을 받아 master FD에 write하면 TTY를 거쳐 Shell이 slave FD를 통해 이를 읽는 것이다.
Shell이 ls를 읽으면 /bin/ls 프로세스를 실행하고 결과를 PTY slave에 직접 write한다. 터미널 앱은 PTY master에서 이를 읽어 화면에 표시한다.
- TTY line discipline: 터미널 입력을 프로그램에 전달하기 전에 해석하는 커널 코드이다.
- Master FD: 터미널 앱이 PTY master에 접근하는 파일 디스크립터
- Slave FD: Shell, CLI 프로그램이 PTY slave에 접근하는 파일 디스크립터
일반 입력과 달리, 기본 TTY 설정에서 Ctrl-C는 TTY line discipline 디시플린에 의해 인터럽트(VINTR) 문자로 인식된다. stty -a 를 Shell에서 실행해보면 intr = ^C 를 확인할 수 있다. line discipline은 해당 문자를 slave 입력으로 전달하지 않고, 커널의 시그널 처리 계층을 통해 foreground process group에 SIGINT를 보낸다.
키보드 Ctrl+C
↓
Terminal Emulator
↓
write() PTY master
↓
TTY line discipline
↓
Ctrl+C == VINTR Kernel
↓
SIGINT Foreground Process Group
↓
Python
↓
KeyboardInterrupt
즉, 우리가 ctrl + c를 하면 터미널 앱에서 입력을 받아 master FD에 write 한다. 이를 TTY line discipline이 Ctrl+C를 VINTR 문자(인터럽트 종류)로 인식하고, 커널이 해당 TTY의 foreground process group에 SIGINT를 발생시킨다.
Python이 foreground process group안에서 실행되고 있었다면 SIGINT에 대한 처리는 Python에서 하게 된다. Python에서 기본적으로 SIGINT를 받으면 KeyboardInterrupt 예외를 발생시키도록 처리한다.
이상으로 ctrl c를 눌렀을 때 어떤 요소들을 거쳐 Python 코드까지 전달되는지 살펴보았다.
'개발' 카테고리의 다른 글
| 부동소수점은 왜 필요할까? (0) | 2026.08.23 |
|---|---|
| vscode에서 파이썬 프로그램을 디버깅 하는 방법 (0) | 2026.08.16 |
| docker 컨테이너의 프로세스 구조 간단히 알아보기 (1) | 2026.08.02 |
| python 배열 slicing(item[2:])의 성능은 왜 O(n)일까 (0) | 2026.07.26 |
| 오픈소스에 기여하기(스프링 프레임워크) (0) | 2026.01.15 |