A-7: 프로세스와 잡 제어
목차 23
안녕하세요, 홍순구입니다. 지난 시간까지 우리는 파일을 만들고, 옮기고, 권한을 주고, 텍스트를 찾고 바꾸고, vim으로 직접 열어 고치는 것까지 해냈어요. 파일과 텍스트는 이제 제법 손에 익었을 거예요. 그런데 지금까지 다룬 건 전부 디스크에 가만히 멈춰 있는 파일이었어요. 오늘부터는 완전히 달라집니다.
오늘 들여다볼 건 살아 움직이는 시스템이에요. 지금 이 순간에도 여러분 컴퓨터와 서버 안에서는 수십, 수백 개의 프로그램이 동시에 돌아가고 있어요. 웹 서버, 데이터베이스, 백그라운드 작업까지요. 이렇게 "실행 중인 프로그램" 하나하나를 리눅스에서는 프로세스(process)라고 불러요. 오늘은 이 프로세스들을 들여다보고(ps·top), 말 안 듣는 녀석을 멈추고(kill), 무거운 작업을 백그라운드로 돌리는(잡 제어) 법을 배웁니다.
그리고 지난 시간에 예고한 그 장면 — 현업 신입이 또 한 번 깨지는 순간을 오늘 정면으로 풀어드릴게요. 서버를 띄우려는데 "주소가 이미 사용 중(Address already in use)"이라는 에러가 뜨는 상황이요. 어떤 프로그램이 이미 그 포트를 잡고 있는데, 그게 누군지 못 찾아서 결국 서버를 못 띄우는 거죠. 저도 신입 때 이것 때문에 한참 헤맸어요. 근데 오늘 배우는 명령 두세 개면, 누가 그 포트를 잡고 있는지 찾아내서 정리하고 다시 띄울 수 있어요. 하나도 안 어렵습니다.
오늘 걸어갈 길을 한눈에 그려둘게요.
오늘의 여정 — 살아 있는 프로세스를 들여다보고 다스리기
──────────────────────────────────────────────────
Step 1 프로세스란? — 프로그램이 살아 움직이면 프로세스 (PID·부모/자식)
Step 2 누가 일하고 있나 — ps 로 프로세스 명단 보기
Step 3 실시간으로 지켜보기 — top 으로 움직이는 현황판 보기
Step 4 말 안 듣는 프로세스 멈추기 — kill 과 시그널 (포트 잡은 녀석 찾기)
Step 5 백그라운드로 돌리기 — & · Ctrl+Z · jobs · fg/bg · nohup
오늘 실습은 여러분 환경에서 바로 따라 칠 수 있어요. 잠깐 멈춰 있을 프로그램 하나만 띄워서 그걸 들여다보고 멈춰볼 거라, 위험한 건 전혀 없습니다. 빈손으로 따라오세요.
💡 오늘 수업의 핵심 — "실행 중인 프로그램은 모두 프로세스다 — ps·top 으로 들여다보고, kill 로 멈추고, & 로 뒤에서 돌린다"
지금까지 다룬 파일이 "멈춰 있는 명사"였다면, 프로세스는 "움직이는 동사"예요. 그래서 다루는 법도 달라요. 파일은 열어서 읽고 고치지만, 프로세스는 지켜보고(ps/top), 신호를 보내 멈추고(kill), 앞이 아니라 뒤에서 돌게(잡 제어) 만들죠. 오늘 이 셋을 차근차근 손에 익혀요.
🎯 학습 목표
- 프로세스가 무엇인지 이해하고,
ps와top으로 지금 돌고 있는 프로세스를 들여다본다 - 시그널의 개념을 알고,
kill로 멈추지 않는 프로세스를 정중히(SIGTERM) 또는 강제로(SIGKILL) 종료한다 — 포트를 잡은 프로세스를 찾아 정리하는 것까지 - 잡 제어(
&·Ctrl+Z·fg/bg·nohup)로 작업을 백그라운드에서 돌리고 다시 앞으로 불러온다
Step 1: "프로세스란? — 프로그램이 살아 움직이면 프로세스"
먼저 가장 중요한 구분 하나부터 잡고 갈게요. "프로그램"과 "프로세스"는 비슷해 보이지만 전혀 다릅니다. 이걸 헷갈리면 오늘 내내 헷갈려요.
비유로 시작할게요. 요리책에 적힌 레시피 한 장을 떠올려보세요. 그건 종이에 적힌 글자일 뿐, 가만히 있어요. 그게 프로그램이에요 — 디스크에 저장된 파일이죠. 그런데 누군가 그 레시피를 보고 실제로 요리를 시작하면, 불을 켜고 재료를 다듬는 살아 있는 행위가 생겨요. 그게 프로세스예요. 메모리 위에서 실제로 실행되며 CPU를 쓰고 일을 하는 거죠.
레시피 한 장 그 레시피로 실제 요리하는 행위
(프로그램 = 디스크의 파일) (프로세스 = 메모리에서 실행 중)
───────────────────── ─────────────────────────────
/usr/bin/python3 ──실행──▶ python3 (PID 4012)
python3 (PID 4013) ← 같은 레시피라도
python3 (PID 4014) 각각 다른 요리(프로세스)
여기서 핵심 하나. 하나의 프로그램으로 여러 프로세스를 동시에 띄울 수 있어요. 똑같은 레시피로 여러 요리사가 각자 요리하듯, 똑같은 python3 프로그램이 동시에 세 개, 네 개씩 실행될 수 있죠. 그래서 이들을 구분할 이름표가 필요해요. 그게 PID(Process ID) — 프로세스마다 붙는 고유 번호, 사람으로 치면 주민번호예요.
그리고 프로세스에는 부모가 있어요. 모든 프로세스는 다른 프로세스가 띄운 거거든요. 나를 띄운 프로세스를 부모 프로세스라 하고, 그 부모의 번호를 PPID(Parent PID)라고 해요. 이걸 따라 올라가면 결국 부팅할 때 가장 먼저 뜨는 PID 1(systemd)에 닿아요. 모든 프로세스의 조상인 셈이죠.
PID 1 systemd ← 부팅 때 가장 먼저 뜨는 모든 프로세스의 조상
└─ PID 920 sshd ← systemd 가 띄운 SSH 접속 대기 프로그램
└─ PID 3301 bash ← 내가 로그인하자 sshd 가 띄워준 셸
└─ PID 4012 python3 ← 내가 셸에서 직접 실행한 프로그램
내 셸의 PID는 $$라는 특수 변수로 바로 볼 수 있어요. 실제로 확인해볼게요.
echo $$ # 지금 내가 쓰는 셸의 PID
ps -p $$ -o pid,ppid,comm # 그 셸의 PID·부모 PID·이름만 콕 집어 보기
PID PPID COMMAND
3301 920 bash
내 셸(bash, PID 3301)의 부모(PPID 920)가 sshd라는 게 보이죠. 내가 SSH로 서버에 접속하니 sshd가 나를 위한 셸을 하나 띄워준 거예요. 현업에서 "이 프로세스를 누가 띄웠지?"를 추적할 때 이 PPID를 따라 올라가며 범인을 찾습니다.
💡 한 줄 정리
프로그램은 디스크에 멈춰 있는 파일, 프로세스는 그 프로그램이 메모리에서 실행 중인 살아 있는 인스턴스다. 프로세스마다 고유 번호 PID가 있고, 나를 띄운 부모의 번호가 PPID다. 거슬러 올라가면 PID 1(systemd)에 닿는다.
🙋 학생 질문 — "PID는 매번 바뀌나요? 왜 같은 프로그램인데 번호가 달라요?"
네, PID는 프로세스를 새로 띄울 때마다 그때그때 새로 부여돼요. 같은 python3 server.py를 종료했다가 다시 실행하면, 아까와는 다른 PID를 받습니다. PID는 "이 프로그램의 고정된 이름"이 아니라 "지금 실행 중인 이 인스턴스의 일회용 번호표"거든요. 식당에서 대기할 때 받는 번호표를 떠올리면 돼요 — 같은 사람이 다시 와도 그때그때 새 번호를 받죠.
그래서 프로세스를 다룰 때는 "이름"이 아니라 항상 "지금의 PID"를 먼저 확인하는 습관이 중요해요. 다음 Step의 ps가 바로 그 지금의 PID를 찾아주는 명령입니다.
Step 2: "누가 일하고 있나 — ps 로 프로세스 명단 보기"
프로세스가 뭔지 알았으니, 이제 "지금 무슨 프로세스가 돌고 있는지" 명단을 뽑아볼 차례예요. 그 명령이 ps(process status)입니다.
그냥 ps만 치면 너무 적게 나와요 — 지금 이 셸에서 띄운 프로세스만 보여주거든요. 시스템 전체를 보려면 옵션을 붙입니다. 가장 많이 쓰는 형태가 ps aux예요.
ps aux # 모든 사용자(a)의, 터미널 없는 것(x)까지, 자세히(u) 전부 보기
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.2 168400 13056 ? Ss 09:00 0:01 /sbin/init
root 920 0.0 0.1 15852 7340 ? Ss 09:00 0:00 /usr/sbin/sshd -D
alice 3301 0.0 0.1 8460 5120 pts/0 Ss 09:12 0:00 -bash
alice 4012 0.3 1.2 720104 49216 pts/0 Sl 09:30 0:02 python3 server.py
alice 4500 0.0 0.0 9040 1580 pts/0 R+ 09:35 0:00 ps aux
처음 보면 칸이 많아 당황스럽죠. 다 외울 필요 없어요. 실전에서 진짜 보는 칸은 몇 개뿐입니다.
USER 이 프로세스를 누가 띄웠나 (소유자)
PID 프로세스 고유 번호 ← kill 할 때 이 번호를 쓴다
%CPU CPU 를 얼마나 쓰는가 (퍼센트)
%MEM 메모리를 얼마나 쓰는가 (퍼센트)
STAT 프로세스 상태 (아래 표)
COMMAND 실제 실행된 명령 ← 이게 뭐 하는 프로세스인지
특히 STAT(상태)의 첫 글자가 프로세스가 지금 뭘 하고 있는지 알려줘요.
STAT 첫 글자 — 프로세스 상태
R 실행 중이거나 실행을 기다리는 중 (Running)
S 잠자며 무언가를 기다리는 중 (Sleeping — 대부분의 프로세스가 이 상태)
T 멈춰 있음 (Stopped — Ctrl+Z 로 정지시킨 잡)
Z 좀비 (Zombie — 끝났는데 부모가 아직 결과를 안 거둬간 상태)
뒤에 붙는 기호
s 세션 리더 (그 세션의 우두머리 프로세스)
+ 포그라운드에서 실행 중
S가 대부분이라고 놀라지 마세요. 웹 서버도 평소엔 "요청 올 때까지 잠자며 대기" 상태(S)예요. CPU를 100% 쓰며 계속 도는 게 아니라, 일이 들어올 때만 깨어나 처리하죠.
이제 지난 시간들에서 손에 익힌 파이프(|)와 grep을 여기 붙여볼게요. A-4에서 배운 그대로, "긴 출력에서 원하는 줄만 뽑기"예요. 수백 줄의 ps aux에서 내가 찾는 프로세스만 골라내려면:
ps aux | grep python # python 이 들어간 줄만 골라 보기
alice 4012 0.3 1.2 720104 49216 pts/0 Sl 09:30 0:02 python3 server.py
alice 4530 0.0 0.0 9040 980 pts/0 S+ 09:36 0:00 grep --color=auto python
여기서 작은 함정 하나. 결과에 grep python 자기 자신도 한 줄 끼어 있죠. 방금 친 grep 명령도 그 순간 하나의 프로세스라서 자기까지 잡힌 거예요. 무시하면 됩니다. 깔끔하게 빼고 싶으면 이름으로 PID만 찾아주는 pgrep을 써도 돼요.
pgrep -a python # python 프로세스의 PID 와 명령을 바로 (grep 자기 자신 없이)
4012 python3 server.py
현업에선 "어, 서버가 떠 있나?"를 확인할 때 ps aux | grep 서버이름 또는 pgrep -a 서버이름을 가장 먼저 칩니다. PID를 알아내야 다음 Step에서 그걸 멈추거나 다룰 수 있거든요.
💡 한 줄 정리
ps aux로 시스템 전체 프로세스 명단을 보고, PID·STAT·COMMAND만 읽으면 충분하다. 특정 프로세스만 찾을 땐 ps aux | grep 이름 또는 pgrep -a 이름으로 PID를 알아낸다.
🙋 학생 질문 — "ps aux 랑 ps -ef 는 뭐가 달라요? 둘 다 외워야 하나요?"
거의 같은 걸 보여줘요. ps aux는 BSD 스타일, ps -ef는 System V(유닉스) 스타일이라 출신이 다를 뿐, 둘 다 "전체 프로세스 목록"입니다. 미묘한 차이가 있다면 ps -ef는 PPID(부모 PID) 칸을 기본으로 보여줘서 부모-자식 관계를 추적할 때 편하고, ps aux는 %CPU·%MEM을 보여줘서 자원을 많이 먹는 프로세스를 찾을 때 편해요.
둘 다 외울 필요는 없어요. 하나만 손에 익히세요. 저는 ps aux | grep을 가장 많이 씁니다. 어느 서버에 가도 통하거든요. 나중에 -ef를 쓰는 사람을 만나도 "아, 같은 거구나" 하고 알아보기만 하면 됩니다.
Step 3: "실시간으로 지켜보기 — top 으로 움직이는 현황판 보기"
ps는 사진이에요. 친 순간의 모습을 찰칵 찍어 보여주죠. 그런데 "지금 어떤 프로세스가 CPU를 잡아먹고 있나"처럼 실시간으로 변하는 걸 보려면, 사진이 아니라 동영상이 필요해요. 그게 top입니다. 1~3초마다 화면을 새로 그리며 살아 움직이는 현황판을 보여줘요.
top # 실시간 프로세스 현황판 (q 를 누르면 빠져나온다)
처음 켜면 화면이 꽉 차서 압도될 수 있어요. 침착하게, 화면을 위아래 두 덩어리로 나눠 보면 됩니다.
top - 09:40:12 up 40 min, 1 user, load average: 0.15, 0.20, 0.18 ← ① 시스템 요약
Tasks: 142 total, 1 running, 141 sleeping, 0 stopped, 0 zombie
%Cpu(s): 2.3 us, 0.7 sy, 0.0 ni, 96.8 id, ...
MiB Mem : 3924.0 total, 1820.5 free, 980.2 used, 1123.3 buff/cache
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND ← ② 프로세스 목록
4012 alice 20 0 720104 49216 12800 S 1.3 1.2 0:02.45 python3
920 root 20 0 15852 7340 6400 S 0.0 0.1 0:00.30 sshd
1 root 20 0 168400 13056 8200 S 0.0 0.2 0:01.10 systemd
위쪽 ①은 시스템 전체 요약이에요. 그중 꼭 읽을 줄이 load average(부하 평균)입니다. 세 숫자는 각각 최근 1분·5분·15분 동안 시스템이 얼마나 바빴는지를 나타내요. 대략 "CPU 코어 1개당 1.0이면 딱 알맞게 꽉 찬 상태"라고 보면 됩니다. 코어가 4개인데 load average가 0.18이면 한가한 거고, 4.0을 넘어가면 일이 밀리고 있다는 신호예요.
아래쪽 ②는 ps에서 본 것과 비슷한 프로세스 목록인데, 기본으로 CPU를 많이 쓰는 순서로 정렬돼 위에서부터 보여줘요. 그래서 "지금 누가 CPU를 잡아먹나"를 한눈에 잡을 수 있죠. top 안에서 키를 눌러 정렬을 바꿀 수도 있어요.
top 화면 안에서 누르는 키
P CPU 사용률 순으로 정렬 (기본)
M 메모리 사용률 순으로 정렬
k 프로세스에 신호 보내기 (PID 입력 → 종료) — Step 4 에서 배울 kill
q top 종료하고 셸로 복귀
현업에서 "서버가 갑자기 느려졌어요"라는 연락을 받으면, 가장 먼저 서버에 들어가 top을 칩니다. 그리고 맨 위에 올라온 프로세스가 범인이죠 — CPU 100%를 잡고 무한 루프를 도는 코드라든가요.
참고로 top보다 보기 좋은 htop이라는 도구도 있어요. 색깔로 표시되고 마우스로 클릭도 되며 화살표로 프로세스를 고를 수 있죠. 다만 htop은 기본으로 안 깔려 있을 때가 많아서, 설치를 해야 합니다. 프로그램을 설치하는 패키지 매니저(apt 같은)는 다음 시간에 제대로 다룰게요. 우선 오늘은 어느 서버에나 항상 있는 top을 손에 익혀두면 충분합니다.
💡 한 줄 정리
top은 실시간으로 갱신되는 프로세스 현황판이다. 위쪽 load average로 시스템이 얼마나 바쁜지(코어 수 대비)를 읽고, 아래쪽 목록 맨 위에서 CPU·메모리를 많이 쓰는 프로세스를 잡는다. q로 빠져나온다.
🙋 학생 질문 — "top 의 load average 가 1.5면 위험한 건가요?"
그 숫자 하나만으로는 알 수 없어요. CPU 코어 수와 함께 봐야 합니다. load average는 "대기 줄에 선 작업의 평균 개수"에 가까운데, 처리 창구(코어)가 몇 개냐에 따라 의미가 달라지거든요.
코어가 1개인 서버라면 load average 1.0이 "딱 꽉 찬" 상태라, 1.5는 일이 조금 밀리는 거예요. 그런데 코어가 4개인 서버라면 1.5는 오히려 여유로운 편입니다(4.0이 꽉 찬 기준이니까요). 코어 수는 nproc 명령이나 top에서 1을 눌러 코어별로 펼쳐 보면 확인돼요. 그래서 "load 몇이면 위험"이라는 절대 숫자보다, "이 서버 코어 수 대비 지금 부하가 어느 정도인가"로 읽는 습관이 정확합니다.
Step 4: "말 안 듣는 프로세스 멈추기 — kill 과 시그널"
이제 오늘의 클라이맥스예요. 프로세스를 들여다봤으니, 멈추는 법을 배웁니다. 무한 루프에 빠져 CPU를 100% 먹는 프로세스, 응답이 없어 멈춰버린 서버 — 이런 걸 정리하는 명령이 kill입니다.
이름이 무시무시하지만, kill은 사실 "죽여라"가 아니라 "신호를 보내라"는 명령이에요. 프로세스에게 똑똑 노크하며 메시지를 전하는 거죠. 그 신호를 시그널(signal)이라고 해요. 시그널에는 여러 종류가 있는데, 종료와 관련해 딱 두 개만 확실히 알면 됩니다.
kill PID (= kill -15, SIGTERM) "정리하고 마쳐줘"
→ 프로세스가 신호를 받아 저장·정리 후 스스로 종료
kill -9 PID (= SIGKILL) "지금 즉시 종료"
→ 커널이 강제로 죽임 (정리할 틈 없음 — 최후의 수단)
비유로 잡아둘게요. SIGTERM(기본값, 15번)은 일하는 사람의 어깨를 두드리며 "이제 그만 마무리하고 정리해주세요"라고 정중히 부탁하는 거예요. 프로세스는 이 신호를 받고 열어둔 파일을 저장하고, 연결을 닫고, 깔끔하게 끝낼 수 있어요. SIGKILL(9번)은 다릅니다. 부탁이 아니라 그냥 전원 코드를 뽑아버리는 거예요. 프로세스는 한마디도 못 하고 즉시 사라져요. 빠르고 확실하지만, 정리할 틈이 없어 작업하던 데이터가 깨질 수 있죠.
그래서 순서가 중요해요. 항상 정중한 kill(SIGTERM)을 먼저 시도하고, 그래도 안 죽고 버티는 좀비 같은 녀석에게만 kill -9(SIGKILL)를 씁니다. 직접 해볼게요. 잠깐 자고 있을 sleep 프로세스를 띄워서 멈춰볼게요.
sleep 300 & # 300초 자는 프로세스를 백그라운드로 띄운다 (& 는 Step 5에서 자세히)
[1] 4012
[1] 4012에서 4012가 방금 띄운 프로세스의 PID예요. 이제 이걸 종료합니다.
kill 4012 # 정중히 종료 요청 (SIGTERM)
만약 어떤 프로세스가 kill에도 꿈쩍 않고 계속 살아 있다면, 그때 강제 종료를 씁니다.
kill -9 4012 # 강제 종료 (SIGKILL) — 최후의 수단
이름으로 한 번에 정리하는 명령도 있어요. PID를 일일이 안 찾고 프로세스 이름으로 잡는 거죠.
killall sleep # 'sleep' 이라는 이름의 프로세스를 전부 종료
pkill -f server.py # 명령줄에 'server.py' 가 들어간 프로세스를 종료
여기서 A-3에서 배운 권한 이야기가 다시 나와요. 남의 프로세스는 못 죽입니다. 다른 사용자(또는 root)가 띄운 프로세스를 내가 kill하려 하면 이렇게 막혀요.
kill: (1234) - Operation not permitted
A-3에서 "Permission denied"를 만났을 때처럼, 이건 권한이 부족하다는 신호예요. 정말 그 프로세스를 정리해야 하는 관리 작업이라면 sudo를 앞에 붙입니다(sudo kill 1234). 단, A-3에서 강조했듯 sudo는 신중하게 — 시스템 핵심 프로세스를 잘못 죽이면 서버가 흔들릴 수 있으니, 무엇을 죽이는지 PID를 한 번 더 확인하고 칩니다.
자, 이제 오프닝에서 약속한 그 장면을 풀 차례예요. "Address already in use" — 포트를 잡은 프로세스 찾아 정리하기. 서버를 띄우려는데 이미 누가 8080 포트를 쓰고 있어 막힐 때, 그 범인을 찾아 정리하는 흐름입니다.
서버 실행 → "Address already in use" (누군가 이미 8080 포트를 잡고 있다)
│
① 누가 잡고 있나? sudo lsof -i :8080 또는 sudo ss -ltnp | grep :8080
│ → 그 프로세스의 PID 를 알아낸다
│
② 그 PID 를 종료 kill 4012 (안 죽으면 kill -9 4012)
│
③ 다시 서버 실행 → 이제 포트가 비어 정상 기동
①번을 실제로 쳐보면 이렇게 나와요. 포트를 잡은 프로세스의 PID를 콕 집어줍니다.
sudo lsof -i :8080 # 8080 포트를 쓰는 프로세스 찾기 (어디서나 통함)
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 4012 alice 3u IPv4 45123 0t0 TCP *:8080 (LISTEN)
PID 4012가 범인이네요. 또는 현행 권장 도구인 ss로도 같은 걸 찾을 수 있어요(ss는 예전 netstat을 대체한 현대 명령이에요 — 네트워크는 다음다음 시간에 본격적으로 다룹니다).
sudo ss -ltnp | grep :8080 # 리스닝(-l) 중인 TCP(-t) 포트와 그 프로세스(-p)
LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("python3",pid=4012,fd=3))
여기서도 pid=4012가 보이죠. 이제 kill 4012로 정리하고 다시 서버를 띄우면 됩니다. 신입 때 이거 하나 몰라서 30분씩 헤매던 그 상황을, 이제 명령 두 줄로 풀 수 있어요.
💡 한 줄 정리
kill PID는 정중한 종료 요청(SIGTERM)이고, kill -9 PID는 강제 종료(SIGKILL)다. 항상 정중한 쪽을 먼저 쓰고, 안 죽을 때만 -9를 쓴다. 포트를 잡은 프로세스는 lsof -i :포트나 ss -ltnp로 PID를 찾아 정리한다. 남의 프로세스는 sudo가 필요하다.
🙋 학생 질문 — "그냥 항상 kill -9 쓰면 편하지 않나요? 확실하게 죽잖아요."
마음은 이해해요. 한 방에 확실히 죽으니까요. 그런데 kill -9(SIGKILL)는 프로세스에게 "정리할 시간"을 한순간도 안 줘요. 그래서 위험합니다.
예를 들어 데이터베이스가 한창 디스크에 데이터를 쓰는 중에 kill -9를 맞으면, 쓰다 만 상태로 멈춰 파일이 깨질 수 있어요. 웹 서버라면 처리 중이던 요청이 뚝 끊기고, 임시 파일이 정리 안 된 채 남기도 하죠. 반면 정중한 kill(SIGTERM)을 받으면 "아, 이제 끝내라는구나" 하고 저장·정리·연결 종료를 마친 뒤 스스로 빠져나가요.
그래서 현업의 철칙은 "정중히 먼저, 강제는 최후에"입니다. kill을 보내고 몇 초 기다려도 안 죽는, 정말 맛이 간 프로세스에게만 kill -9를 씁니다.
Step 5: "백그라운드로 돌리기 — 잡 제어"
오늘의 마지막 조각이에요. 지금까지는 명령을 치면 끝날 때까지 터미널이 묶여 있었죠. sleep 300을 그냥 치면 300초 동안 프롬프트가 안 돌아와서 아무것도 못 해요. 이걸 푸는 게 잡 제어(job control)입니다. 작업을 "앞(포그라운드)"이 아니라 "뒤(백그라운드)"로 돌려 터미널을 자유롭게 쓰는 거죠.
포그라운드(foreground) — 터미널을 붙잡고 끝날 때까지 기다린다
$ sleep 300 ← 끝날 때까지 프롬프트가 안 돌아옴 (터미널이 묶임)
백그라운드(background) — 명령 뒤에 & 를 붙이면 뒤에서 돌고 터미널은 바로 자유
$ sleep 300 & ← [1] 4012 찍히고 곧장 프롬프트 복귀
$ (그 사이 다른 일을 계속 할 수 있다)
명령 끝에 & 하나만 붙이면 백그라운드로 실행돼요. Step 4에서 sleep 300 &를 띄울 때 이미 써봤죠. 그러면 [1] 4012처럼 찍히는데, [1]은 잡 번호(이 셸 안에서의 순번), 4012는 PID예요.
지금 백그라운드에서 돌고 있는 잡 목록은 jobs로 봐요.
sleep 300 &
jobs # 이 셸의 백그라운드/정지 잡 목록
[1] 4012
[1]+ Running sleep 300 &
그럼 이미 포그라운드로 실행해버린 명령은요? 끝날 때까지 기다리지 않고 뒤로 보낼 수 있어요. 실행 중에 Ctrl+Z를 누르면 그 작업이 일시정지(Stopped)돼요. 그다음 bg로 "정지된 걸 백그라운드에서 계속 돌려"라고 하면 됩니다.
sleep 300 # 포그라운드 실행 (프롬프트가 묶인다)
# 여기서 Ctrl+Z 를 누른다
^Z
[1]+ Stopped sleep 300
bg %1 # 1번 잡을 백그라운드에서 계속 실행
jobs
[1]+ sleep 300 &
[1]+ Running sleep 300 &
반대로, 백그라운드의 잡을 다시 앞으로 불러오려면 fg(foreground)를 써요. 세 상태가 이렇게 오갑니다.
실행 중(앞) ──Ctrl+Z──▶ 정지(Stopped) ──bg──▶ 실행 중(뒤, 백그라운드)
▲ │
└──────────────────── fg ──────────────────────┘
(앞으로 다시 불러오기)
이제 마지막으로 중요한 함정 하나. &로 백그라운드에 돌려도, 터미널 창을 닫거나 SSH 접속을 끊으면 그 프로세스도 같이 죽어요. 터미널이 닫힐 때 자식 프로세스들에게 "나 나간다(SIGHUP, hang up)"는 신호가 가서 함께 종료되거든요. 그래서 SSH로 서버에 들어가 오래 걸리는 작업을 &로 돌려놓고 로그아웃하면, 돌아왔을 때 작업이 사라져 있는 황당한 일이 생겨요.
이걸 막는 임시 처방이 nohup(no hang up)이에요. "끊김 신호(SIGHUP)를 무시하라"는 뜻이죠.
nohup python3 long_job.py & # 터미널이 닫혀도 끊기지 않게 백그라운드 실행
nohup: ignoring input and appending output to 'nohup.out'
이러면 터미널을 닫아도 작업이 살아남고, 화면에 찍힐 출력은 nohup.out 파일에 쌓여요. 다만 솔직히 말하면, nohup은 임시방편이에요. "서버가 부팅될 때마다 자동으로 켜지고, 죽으면 알아서 되살아나며, 터미널과 완전히 무관하게 도는" 진짜 서비스를 띄우는 정식 방법은 따로 있어요. 그게 systemd 서비스입니다. 그리고 "매일 새벽 3시에 백업을 자동 실행" 같은 예약은 cron이 맡고요. 이 둘은 다음 시간에 제대로 다룹니다 — 오늘 잡 제어로 "뒤에서 돌리기"의 감을 잡았으니, 다음 시간엔 그걸 시스템에 정식으로 등록하는 법으로 이어가요.
💡 한 줄 정리
명령 끝에 &를 붙이면 백그라운드 실행, Ctrl+Z로 일시정지(Stopped), jobs로 목록 확인, bg로 정지된 걸 뒤에서 계속, fg로 앞으로 다시 불러온다. &만으로는 터미널이 닫히면 같이 죽으니, 살려두려면 nohup을 쓴다(정식 방법은 다음 시간의 systemd).
🙋 학생 질문 — "& 로 띄웠는데 터미널을 닫으니 프로그램도 죽었어요. 왜죠?"
정상이에요. 그게 리눅스의 기본 동작입니다. 터미널(정확히는 그 셸 세션)이 닫힐 때, 그 셸이 띄운 자식 프로세스들에게 SIGHUP("나 이제 끊는다")이라는 신호가 가요. &로 백그라운드에 돌린 작업도 결국 그 셸의 자식이라, 이 신호를 받고 함께 종료돼요.
해결은 두 가지예요. 지금 당장의 임시 처방은 nohup 명령 &로 그 끊김 신호를 무시하게 하는 것. 그리고 제대로 된 정식 방법은, 그 프로그램을 셸의 자식이 아니라 시스템(systemd)이 직접 관리하는 서비스로 등록하는 거예요. 그러면 터미널을 닫든, 로그아웃하든, 심지어 서버를 재부팅해도 알아서 다시 떠요. 그 systemd가 바로 다음 시간의 주제입니다.
마무리
오늘은 멈춰 있는 파일에서 벗어나 살아 움직이는 프로세스를 다뤘어요. 들여다보고, 멈추고, 뒤로 돌리는 것까지요. 무엇보다 신입이 서버에서 한 번씩 꼭 겪는 "포트를 잡은 프로세스를 못 찾아 서버를 못 띄우는" 그 상황을, 이제 lsof/ss로 찾아 kill로 정리하는 흐름으로 풀 수 있게 됐죠. 핵심을 세 가지로 정리할게요.
오늘 배운 핵심 세 가지
💡 하나 — 실행 중인 프로그램이 프로세스고, 저마다 고유 번호 PID를 가진다. ps aux로 전체 명단을, ps aux | grep 이름이나 pgrep으로 특정 프로세스의 PID를 찾고, top으로 실시간 현황(특히 CPU·메모리·load average)을 지켜본다.
💡 둘 — kill PID는 정중한 종료(SIGTERM), kill -9 PID는 강제 종료(SIGKILL)다. 정중히 먼저, 강제는 최후에. 남의 프로세스는 sudo가 필요하다. 포트를 잡은 범인은 lsof -i :포트나 ss -ltnp로 찾아 정리한다.
💡 셋 — 명령 뒤 &로 백그라운드 실행, Ctrl+Z로 일시정지, jobs/fg/bg로 앞뒤를 오간다. &만으로는 터미널이 닫히면 같이 죽으니, 살려두려면 nohup을 쓴다.
다음 시간 예고
오늘 우리는 프로세스를 손으로 직접 띄우고, 잡고, 뒤로 돌렸어요. 그런데 현업 서버에서 진짜 필요한 건 "내가 터미널에서 손으로 돌리는 것"이 아니라, 시스템이 알아서 챙기는 것이에요. 서버가 부팅되면 웹 서버가 자동으로 켜지고, 죽으면 알아서 되살아나고, 매일 새벽엔 백업이 저절로 돌아야 하죠.
다음 시간엔 바로 그걸 합니다. 프로그램을 systemd 서비스로 등록해 부팅과 함께 자동으로 띄우고(systemctl), 그 서비스의 로그를 들여다보고(journalctl), "매일 3시에 백업" 같은 작업을 cron으로 예약하는 법이요. 그리고 그 모든 프로그램을 설치하는 패키지 매니저(apt·dnf)까지 다룹니다. 오늘 nohup으로 아쉬웠던 "터미널과 무관하게 진짜 살아 있는 서비스"를, 다음 시간에 제대로 띄워드릴게요. 그럼 다음 시간에 만나요.
과제
오늘 과제도 전부 여러분 자신의 리눅스 환경(WSL2·맥 터미널·VM)에서 직접 쳐보는 실습이에요. 프로세스는 띄우고 멈추는 걸 직접 해봐야 손에 익어요. 위험한 명령은 없으니, 잠깐 자는 sleep 프로세스를 띄워놓고 마음껏 들여다보고 멈춰보세요. 막혀도 kill로 정리하면 되니 편하게 해봐도 됩니다. 실습 디렉토리는 따로 안 만들어도 되고, 아무 터미널에서나 시작하면 돼요.
[기초] ps 와 top 으로 프로세스 들여다보기
지금 내 시스템에서 무슨 프로세스가 돌고 있는지 직접 살펴보세요.
ps aux를 쳐서 전체 프로세스 목록을 본다. 맨 윗줄(헤더)에서PID·STAT·COMMAND칸이 어디인지 눈으로 짚어본다ps aux | grep bash로 내 셸 프로세스를 찾아, 그PID와STAT을 확인한다echo $$로 내 셸의 PID를 직접 확인하고, 위grep결과의 PID와 같은지 비교해본다top을 실행해 위쪽의load average세 숫자를 읽어보고, 아래 목록에서 CPU를 가장 많이 쓰는 프로세스가 무엇인지 확인한 뒤q로 빠져나온다
[응용] 프로세스 띄우고 멈추기 + 잡 제어 한 바퀴
sleep 프로세스를 띄워서 종료와 잡 제어를 직접 손에 익혀보세요.
sleep 300 &로 프로세스를 백그라운드로 띄우고, 찍힌[1] PID에서 PID를 확인한다.jobs로 목록도 본다- 그 PID를
kill PID로 정중히 종료한 뒤,jobs로 정말 사라졌는지 확인한다 - 이번엔
sleep 300을 그냥(포그라운드로) 실행해 프롬프트가 묶이는 걸 겪어본다.Ctrl+Z로 일시정지시키고,jobs로 상태가Stopped임을 확인한다 bg %1로 백그라운드에서 다시 돌린 뒤(jobs로Running확인),fg %1로 다시 앞으로 불러와 본다. 마지막엔Ctrl+C나kill로 정리한다
[심화] 포트를 잡은 프로세스 찾아 정리하기 (서버 상황 상상)
서버 상황을 상상해봅시다. "운영 서버에서 웹 서버를 띄우려는데 'Address already in use'가 뜬다. 8080 포트를 이미 누가 잡고 있다. 그게 누군지 찾아 정리해야 한다."
- 먼저 직접 포트를 하나 잡아본다. 파이썬이 있다면
python3 -m http.server 8080 &로 8080 포트를 쓰는 간단한 서버를 백그라운드로 띄운다(파이썬이 없으면nc -l 8080 &등 가능한 방법으로) sudo lsof -i :8080또는sudo ss -ltnp | grep :8080으로 그 포트를 잡고 있는 프로세스의 PID를 찾아낸다- 찾은 PID를
kill로 정리하고, 다시lsof -i :8080을 쳐서 이제 아무것도 안 나오는지(포트가 비었는지) 확인한다 - 정리하며 생각해본다. 만약 그 프로세스가
kill에도 안 죽고 버틴다면 그다음 무엇을 시도하겠는가? 그리고 그 프로세스가 다른 사용자 소유였다면 어떤 명령이 더 필요했겠는가? 두세 문장으로 적어본다
생각해볼 주제
1. 왜 "정중한 종료"를 먼저 시도할까
오늘 우리는 kill(SIGTERM, 정중히)을 먼저 쓰고 kill -9(SIGKILL, 강제)는 최후에 쓴다고 배웠어요. 한 방에 확실히 죽는 -9가 더 편해 보이는데도요. 데이터베이스가 한창 디스크에 데이터를 쓰는 중이라고 상상해보세요. 여기에 강제 종료가 들어오면 어떤 일이 벌어질까요? 반대로 정중한 종료를 받으면 프로세스는 그 짧은 시간에 무엇을 할 수 있을까요? "빠르게 끝내는 것"과 "안전하게 끝내는 것"이 다를 수 있다는 점, 그리고 현업에서 서버를 재시작하거나 배포할 때 왜 "graceful(우아한) 종료"라는 말을 그렇게 강조하는지 연결지어 생각해보세요.
2. 터미널을 닫으면 왜 프로세스가 죽을까
&로 백그라운드에 돌린 작업도 터미널을 닫으면 같이 죽었죠(SIGHUP). 처음엔 불편하게 느껴지지만, 사실 이건 잘 설계된 동작이에요. 만약 터미널을 닫아도 그 안에서 띄운 모든 프로세스가 영원히 살아남는다면, 서버에는 어떤 일이 벌어질까요? 누군가 잠깐 접속해 띄운 프로그램들이 끝없이 쌓인다면요. 반대로, 그럼에도 "터미널과 무관하게 계속 살아야 하는" 프로그램(웹 서버 같은)은 분명히 있죠. 리눅스가 이 둘을 어떻게 구분해 다루는지(임시 처방 nohup과 정식 방법 systemd의 차이), 그리고 왜 "오래 살아야 하는 서비스"는 셸이 아니라 시스템이 직접 관리해야 하는지 생각해보세요.
3. "시스템이 바쁘다"를 어떻게 판단할까
top의 load average를 봤을 때, 같은 숫자 1.5라도 코어가 1개냐 8개냐에 따라 의미가 정반대였어요. 여기서 한 걸음 더 들어가볼게요. 어떤 서버는 load average가 높은데도 멀쩡히 빠르고, 어떤 서버는 load average가 낮은데도 느리게 느껴질 수 있어요. CPU는 한가한데 디스크 입출력(top의 wa, I/O 대기)이나 메모리 부족이 발목을 잡는 경우처럼요. "시스템이 느리다"를 진단할 때 CPU 사용률 하나만 보면 놓치는 게 무엇일지, 그리고 프로세스의 상태(STAT의 R/S/D)나 메모리·디스크까지 함께 봐야 하는 이유를 생각해보세요. 진짜 병목은 종종 가장 먼저 의심한 곳이 아니라 다른 데 있거든요.
✅ 예시 답안정답 보기
과제와 생각해볼 주제의 예시답안입니다. 프로세스는 눈으로 읽으면 다 아는 것 같다가도, 직접 띄우고 멈춰봐야 손에 익어요. 위험한 명령은 없으니 sleep 프로세스를 마음껏 띄워놓고 들여다보고 정리해보세요. PID·출력 숫자가 책과 다른 건 당연하니(여러분 환경의 실제 값이니까요), 숫자 자체보다 흐름이 맞는지를 보면 됩니다.
🎯 [과제 1 예시답안] ps 와 top 으로 프로세스 들여다보기
채점 포인트
| 확인 항목 | 무엇을 보면 되는가 |
|---|---|
| 칸 읽기 | ps aux 헤더에서 PID·STAT·COMMAND 위치를 짚었는가 |
| PID 찾기 | ps aux에 grep bash를 이어 셸 프로세스의 PID·STAT을 확인했는가 |
$$ 대조 |
echo $$의 값과 grep으로 찾은 셸 PID가 같은지 비교했는가 |
| top 읽기 | load average 세 숫자를 읽고, CPU를 많이 쓰는 프로세스를 짚은 뒤 q로 나왔는가 |
풀이 예시
$ ps aux | head -1
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
$ ps aux | grep bash
alice 3301 0.0 0.1 8460 5120 pts/0 Ss 09:12 0:00 -bash
alice 4530 0.0 0.0 9040 980 pts/0 S+ 09:36 0:00 grep --color=auto bash
$ echo $$
3301
grep으로 찾은 내 셸(-bash)의 PID 3301이, echo $$가 알려준 PID 3301과 똑같죠. 둘이 같은 걸 확인하면 "아, $$가 정말 지금 이 셸의 PID구나"가 손에 잡힙니다. 결과에 grep --color=auto bash 줄도 끼어 있는데, 그건 방금 친 grep 자기 자신이 잡힌 거라 무시하면 돼요.
이어서 top으로 실시간 현황을 봅니다.
$ top
top - 09:40:12 up 40 min, 1 user, load average: 0.15, 0.20, 0.18
Tasks: 142 total, 1 running, 141 sleeping, 0 stopped, 0 zombie
...
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4012 alice 20 0 720104 49216 12800 S 1.3 1.2 0:02.45 python3
1 root 20 0 168400 13056 8200 S 0.0 0.2 0:01.10 systemd
load average: 0.15, 0.20, 0.18은 최근 1·5·15분 부하예요. 코어가 여러 개인 보통의 개발 환경이라면 한가한 상태죠. 목록 맨 위(python3)가 지금 CPU를 가장 많이 쓰는 프로세스예요. 다 봤으면 q로 빠져나옵니다.
💡 튜터의 한마디 — 이 과제의 핵심은 "$$와 grep 결과가 같다"를 눈으로 확인하는 거예요. 추상적이던 PID가 "지금 내 셸의 실제 번호"로 와닿거든요. 현업에서 서버에 들어가 가장 먼저 하는 일도 이거예요 — ps aux | grep 서버이름으로 "내 서버가 떠 있나, PID가 몇인가"부터 확인하고, top으로 "지금 뭐가 바쁜가"를 봅니다. 이 두 명령이 서버 점검의 첫 단추예요.
🎯 [과제 2 예시답안] 프로세스 띄우고 멈추기 + 잡 제어 한 바퀴
채점 포인트
| 확인 항목 | 무엇을 보면 되는가 |
|---|---|
| 백그라운드 실행 | sleep 300 &로 띄우고 [1] PID에서 PID·잡 번호를 확인했는가 |
| 종료 확인 | kill PID 뒤 jobs로 정말 사라졌는지(Terminated) 확인했는가 |
| 일시정지 | 포그라운드 sleep에 Ctrl+Z를 눌러 Stopped 상태를 만들었는가 |
| fg/bg 전환 | bg로 백그라운드 재개(Running), fg로 다시 앞으로 불러왔는가 |
풀이 예시
$ sleep 300 &
[1] 4012
$ jobs
[1]+ Running sleep 300 &
$ kill 4012
$ jobs
[1]+ Terminated sleep 300
&로 띄우니 [1] 4012가 찍히고 곧장 프롬프트가 돌아왔죠([1]은 잡 번호, 4012는 PID). kill 4012로 정중히 종료하고 jobs를 다시 보니 Terminated. 깨끗이 정리됐어요.
이번엔 포그라운드 실행을 일시정지시켰다가 백그라운드로 넘겨봅니다.
$ sleep 300
^Z
[1]+ Stopped sleep 300
$ bg %1
[1]+ sleep 300 &
$ jobs
[1]+ Running sleep 300 &
$ fg %1
sleep 300
^C
Ctrl+Z(^Z)로 멈추니 Stopped, bg %1로 다시 돌리니 Running, fg %1로 앞으로 불러오니 다시 프롬프트가 묶이죠. 마지막엔 Ctrl+C(^C)로 정리했어요.
💡 튜터의 한마디 — 잡 제어는 글로 읽으면 헷갈려도 손으로 한 바퀴 돌려보면 단번에 잡혀요. "앞에서 돌다(fg) ↔ 멈추다(Ctrl+Z) ↔ 뒤에서 돌다(bg)"의 삼각형만 그려지면 끝입니다. 현업에선 무거운 작업을 실행했다가 "아, 이거 오래 걸리겠다" 싶을 때 Ctrl+Z → bg로 뒤로 넘기고 다른 일을 계속하는 식으로 자주 써요.
🎯 [과제 3 예시답안] 포트를 잡은 프로세스 찾아 정리하기
채점 포인트
| 확인 항목 | 무엇을 보면 되는가 |
|---|---|
| 포트 점유 | 8080 포트를 쓰는 프로세스를 직접 하나 띄웠는가 |
| 범인 찾기 | lsof -i :8080 또는 ss -ltnp로 그 프로세스의 PID를 알아냈는가 |
| 정리·재확인 | kill로 정리한 뒤, 다시 조회해 포트가 비었는지 확인했는가 |
| 다음 수 고민 | 안 죽을 때(kill -9)·남의 소유일 때(sudo)를 생각해 적었는가 |
풀이 예시
$ python3 -m http.server 8080 &
[1] 4012
$ sudo lsof -i :8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 4012 alice 4u IPv4 45123 0t0 TCP *:8080 (LISTEN)
$ kill 4012
$ lsof -i :8080
$
8080 포트를 잡은 범인이 python3(PID 4012)라는 걸 lsof가 콕 집어주죠. ss로도 똑같이 찾을 수 있어요.
$ sudo ss -ltnp | grep :8080
LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("python3",pid=4012,fd=4))
kill 4012로 정리하고 다시 lsof -i :8080을 치니 아무것도 안 나와요(포트가 비었다는 뜻). 이제 원래 띄우려던 서버를 정상적으로 올릴 수 있죠.
다음 수 고민: 만약 kill 4012에도 안 죽고 버틴다면 kill -9 4012(강제 종료)를 시도합니다. 그리고 그 프로세스가 다른 사용자(또는 root) 소유였다면 kill: Operation not permitted로 막혔을 테니, sudo kill 4012처럼 관리자 권한이 필요했을 거예요(A-3의 권한 이야기와 그대로 이어지죠).
💡 튜터의 한마디 — 이게 오늘의 하이라이트예요. "Address already in use"는 신입이 정말 자주 만나는 에러인데, 오늘 배운 lsof -i :포트 → kill 흐름 하나면 30초면 풉니다. 면접이나 실무에서 "포트 충돌 어떻게 해결하세요?"라고 물으면 바로 이 두 줄을 답할 수 있어요. 그리고 강제 종료(-9)는 최후에, 남의 프로세스는 sudo로 — 이 두 가지 단서까지 함께 챙기면 완벽합니다.
🤔 [생각해볼 주제 1] 왜 "정중한 종료"를 먼저 시도할까
문제 상황 요약
kill(SIGTERM, 정중히)을 먼저 쓰고 kill -9(SIGKILL, 강제)는 최후에 쓴다고 배웠는데, 한 방에 확실히 죽는 -9가 더 편해 보인다. 왜 굳이 정중한 종료를 먼저 시도할까?
튜터의 가이드 및 해설
핵심은 "프로세스에게 끝낼 준비를 할 시간을 주느냐"예요. SIGTERM(정중한 kill)을 받은 프로세스는 그 신호를 가로채(handler) "아, 이제 끝내라는구나" 하고 마무리 작업을 할 수 있어요. 열어둔 파일을 끝까지 저장하고, 데이터베이스 연결을 정상으로 닫고, 처리 중이던 요청을 마저 끝내고, 임시 파일을 치운 뒤 스스로 빠져나가죠. 이걸 "graceful shutdown(우아한 종료)"이라고 불러요.
반면 SIGKILL(kill -9)은 프로세스가 가로챌 수조차 없어요. 커널이 그냥 즉시 끊어버리거든요. 데이터베이스가 한창 디스크에 쓰는 중이었다면 쓰다 만 상태로 멈춰 파일이 깨질 수 있고, 웹 서버라면 처리 중이던 요청이 뚝 끊기고, 정리 안 된 임시 파일·잠금(lock) 파일이 남아 다음 실행을 방해하기도 해요. 빠르고 확실하지만, 그 대가가 데이터 손상일 수 있는 거죠.
그래서 현업의 순서는 늘 "정중히 먼저, 강제는 최후"예요. 서버를 재시작하거나 새 버전을 배포할 때 "graceful 종료"를 그렇게 강조하는 것도 같은 이유고요 — 사용자의 요청을 끊지 않고 안전하게 넘기기 위해서죠. kill을 보내고 몇 초 기다려도 정말 안 죽는, 맛이 가버린 프로세스에게만 kill -9를 씁니다.
💡 핵심을 한마디로
"kill -9는 빠른 게 아니라 위험한 거예요. SIGTERM은 프로세스에게 '저장하고 정리할 시간'을 주지만 SIGKILL은 그 틈도 안 줘서 데이터가 깨질 수 있거든요. 그래서 정중히 먼저, 강제는 정말 안 죽을 때 최후로 — 이게 graceful shutdown의 기본입니다."
🤔 [생각해볼 주제 2] 터미널을 닫으면 왜 프로세스가 죽을까
문제 상황 요약
&로 백그라운드에 돌린 작업도 터미널을 닫으면 같이 죽는다(SIGHUP). 불편해 보이는데, 왜 이렇게 설계됐을까? 그리고 그럼에도 계속 살아야 하는 프로그램은 어떻게 띄울까?
튜터의 가이드 및 해설
먼저 왜 이게 합리적인 설계인지 생각해봐요. 터미널 세션이 닫힐 때 그 안에서 띄운 자식 프로세스를 정리하지 않는다면, 서버에는 "주인 없는 프로세스"가 계속 쌓여요. 누군가 SSH로 잠깐 들어와 이것저것 띄우고 나갔는데 그게 다 살아남는다면, 시간이 지나며 메모리·CPU를 갉아먹는 유령 프로세스가 넘쳐나겠죠. 그래서 "세션이 끝나면 그 세션이 띄운 것들도 정리한다(SIGHUP)"는 건 자원을 깨끗하게 관리하는 합리적 기본값이에요.
그런데 분명히 "터미널과 무관하게 계속 살아야 하는" 프로그램이 있죠 — 웹 서버, 데이터베이스처럼요. 이걸 위한 방법이 두 단계예요. 임시 처방은 nohup 명령 &로 끊김 신호(SIGHUP)를 무시하게 하는 것. 급할 때 쓰지만, 터미널과의 끈을 억지로 끊은 것뿐이라 어설퍼요. 죽으면 되살아나지도 않고, 서버를 재부팅하면 사라지죠.
정식 방법은 그 프로그램을 셸의 자식이 아니라 시스템(systemd)이 직접 관리하는 서비스로 등록하는 거예요. 그러면 부팅과 함께 자동으로 뜨고, 죽으면 알아서 되살아나며, 어느 터미널과도 무관하게 살아요. "오래 살아야 하는 서비스"를 셸이 아니라 시스템이 관리해야 하는 이유가 여기 있어요 — 셸은 사람이 잠깐 쓰는 창구라 언제든 닫히지만, 시스템은 서버가 켜져 있는 한 늘 거기 있으니까요. 이 systemd가 바로 다음 시간의 주제입니다.
💡 핵심을 한마디로
"&로 띄운 작업이 터미널과 함께 죽는 건 버그가 아니라 자원 정리를 위한 합리적 기본값이에요. 잠깐 살리려면 nohup, 진짜 서비스로 오래 살리려면 셸이 아니라 systemd에 맡깁니다 — 셸은 닫히지만 시스템은 늘 거기 있으니까요."
🤔 [생각해볼 주제 3] "시스템이 바쁘다"를 어떻게 판단할까
문제 상황 요약
load average는 같은 숫자라도 코어 수에 따라 의미가 달랐다. 그런데 CPU는 한가한데 느린 서버, 반대로 부하가 높은데 멀쩡한 서버도 있다. "시스템이 느리다"를 진단할 때 무엇을 봐야 할까?
튜터의 가이드 및 해설
먼저 load average부터요. 이건 "처리를 기다리는 작업의 평균 개수"에 가까워서, 처리 창구(CPU 코어)가 몇 개냐와 함께 봐야 의미가 생겨요. 코어 1개에 load 1.0이면 꽉 찬 거지만, 코어 8개에 load 1.0이면 한참 여유롭죠. 그래서 절대 숫자가 아니라 "코어 수 대비"로 읽습니다.
여기서 한 걸음 더. load average가 세는 "대기 중인 작업"에는 CPU를 기다리는 것뿐 아니라 디스크 입출력(I/O)을 기다리는 것도 포함돼요. 그래서 CPU 사용률은 낮은데 load는 높은 이상한 상황이 생길 수 있어요 — 프로세스들이 CPU가 아니라 느린 디스크를 기다리며 줄 서 있는 거죠. top의 %Cpu 줄에서 wa(I/O wait)가 높거나, 프로세스 상태(STAT)에 D(디스크 등을 기다리는 중단 불가 대기)가 많이 보이면 이걸 의심합니다.
결국 "시스템이 느리다"의 진짜 원인은 CPU 하나만으로 안 보여요. CPU(%CPU·load) · 메모리(%MEM·swap 사용) · 디스크 I/O(wa) · 프로세스 상태(R/S/D)를 함께 봐야 병목이 드러나죠. 메모리가 부족해 swap을 쓰느라 느린 건지, 디스크가 발목을 잡는 건지, 진짜 CPU가 모자란 건지는 전혀 다른 처방이 필요하거든요. 진짜 병목은 종종 가장 먼저 의심한 곳이 아니라 다른 데 있어요 — 그래서 한 지표만 보고 단정하지 않는 습관이 중요합니다.
💡 핵심을 한마디로
"load average 하나로 단정하면 위험해요. 코어 수 대비로 읽되, CPU·메모리·디스크 I/O(wa)·프로세스 상태(D)를 함께 봐야 진짜 병목이 보입니다. CPU는 멀쩡한데 디스크나 메모리가 발목을 잡는 경우가 실무에선 정말 흔하거든요."