이번 문서의 목표: 이 파일을 다 읽으면 ps·top의 출력을 줄 단위로 해석하고, 시그널 번호로 프로세스를 제어하며, nice 값으로 우선순위를 올바른 방향으로 조정할 수 있다.
왜 명령어로 프로세스를 들여다봐야 하는가
13편에서 프로세스가 무엇이고 어떤 관계(부모-자식)를 맺는지 배웠다. 하지만 눈에 보이지 않는 프로세스를 직접 확인하고 통제하려면 도구가 필요하다. 지금 시스템에 어떤 프로세스가 몇 개나 떠 있는지, 어떤 프로세스가 CPU를 얼마나 먹고 있는지, 말을 안 듣는 프로세스를 어떻게 멈추게 하는지를 알아야 실제로 시스템을 운영할 수 있다. 이 편은 그 확인·제어 도구들을 다룬다.
쉽게 말하면: ps·top은 “지금 무슨 일이 벌어지고 있는지 보는 도구”, kill·nice는 “그 일에 개입하는 도구”다.
ps — 지금 실행 중인 프로세스를 스냅샷으로 보기
ps(process status)는 명령을 실행한 그 순간의 프로세스 상태를 한 번 찍어서 보여 주는 명령이다. 옵션 없이 ps만 실행하면 지금 로그인한 셸에서 실행 중인 프로세스만 간단히 보여 주지만, 시험과 실무에서 훨씬 자주 쓰는 형태는 ps aux다.
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.2 128612 7372 ? S 09:00 0:01 systemd
root 2 0.0 0.0 0 0 ? S 09:00 0:00 kthreadd
ihd 2000 0.1 0.5 22344 5344 pts/0 Ss 09:05 0:00 bash
ihd 2050 12.4 3.1 812340 63120 pts/0 R+ 09:12 0:08 vi memo.txt
ihd 2100 0.0 0.0 0 0 pts/0 Z 09:20 0:00 [sleep] <defunct>ps aux의 각 열(column)이 무엇을 뜻하는지 하나씩 뜯어보자.
| 열 | 의미 |
|---|---|
| USER | 이 프로세스를 실행(소유)한 사용자 |
| PID | 프로세스 식별 번호 |
| %CPU | 이 프로세스가 사용 중인 CPU 사용률(퍼센트) |
| %MEM | 이 프로세스가 사용 중인 실제 메모리 사용률(퍼센트) |
| VSZ | 이 프로세스에 할당된 가상 메모리 크기(virtual memory size, 킬로바이트 단위) |
| RSS | 이 프로세스가 실제로 물리 메모리에 올려 쓰고 있는 크기(resident set size) |
| TTY | 이 프로세스가 연결된 터미널. 터미널이 없는 데몬류는 물음표(?)로 표시됨 |
| STAT | 프로세스의 현재 상태 코드(아래 표에서 자세히 다룸) |
| START | 프로세스가 시작된 시각 |
| TIME | 이 프로세스가 지금까지 실제로 CPU를 사용한 누적 시간 |
| COMMAND | 실행된 명령어와 인자 |
옵션 이름 aux는 세 글자가 각각 별도 의미를 갖는 조합이다. a는 터미널에 연결된 모든 사용자의 프로세스를 보여 주고(자기 것만이 아니라), u는 사용자 중심의 상세 형식(USER, %CPU, %MEM 등을 포함하는 형식)으로 출력하며, x는 터미널이 아예 없는 프로세스(데몬 등)까지 포함해서 보여 준다. 반면 ps -ef는 유닉스 계열 전통 표기법으로, PPID 열을 포함해 부모-자식 관계를 확인하기에 더 편리한 형식이다. 시험에서는 ps aux와 ps -ef가 형식만 다를 뿐 결국 비슷한 정보(현재 실행 중인 전체 프로세스 목록)를 보여 준다는 점, 그리고 각 형식의 열 구성 차이를 함께 묻는다.
STAT 열의 코드는 13편에서 다룬 좀비 상태와도 바로 연결된다.
| 코드 | 의미 |
|---|---|
| R | 실행 중(running)이거나 실행 대기(runnable) 상태 |
| S | 인터럽트 가능한 슬립(interruptible sleep) — 신호나 이벤트를 기다리며 대기 |
| D | 인터럽트 불가능한 슬립(uninterruptible sleep) — 주로 디스크 입출력 완료를 기다리는 중이라 시그널로 깨울 수 없음 |
| T | 정지(stopped) — 작업 제어 시그널([Ctrl] + [z] 등)에 의해 일시 정지된 상태 |
| Z | 좀비(zombie) — 종료됐지만 부모가 회수하지 않은 상태 |
코드 뒤에 붙는 보조 문자도 있다. +는 그 프로세스가 현재 포그라운드 작업임을, s는 세션 리더(session leader, 로그인 세션을 대표하는 프로세스)임을, <는 우선순위가 높게 설정됐음을, N은 우선순위가 낮게 설정됐음을 나타낸다. 위 예시에서 2050 vi memo.txt 프로세스의 STAT가 R+인 것은 “지금 실행 중이며 동시에 포그라운드 작업”이라는 뜻이고, 2100번 프로세스가 Z 상태에 명령 이름이 [sleep] <defunct>로 나온 것은 이미 종료된 sleep 명령이 좀비로 남아 있다는 뜻이다.
top — 실시간으로 갱신되는 프로세스 현황판
ps가 그 순간의 스냅샷 한 장이라면, top은 화면이 몇 초마다 스스로 갱신되며 실시간으로 시스템 전체 상태를 보여 주는 대화형 명령이다.
top - 18:41:40 up 4:06, 2 users, load average: 0.00, 0.01, 0.05
Tasks: 182 total, 1 running, 181 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.3 us, 0.0 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 4045260 total, 3228568 free, 976648 used, 1840052 buff/cache
KiB Swap: 3905532 total, 3905532 free, 0 used. 2791320 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 128612 7372 4316 S 0.0 0.2 0:01.48 systemd
2050 ihd 20 0 812340 63120 12300 R 12.4 3.1 0:08.20 vi상단 요약 영역에서 Tasks 줄의 zombie 개수는 지금 시스템에 좀비가 몇 개 쌓여 있는지 바로 보여 주므로, 좀비가 비정상적으로 늘고 있는지 감시하는 용도로 자주 쓰인다. load average(부하 평균)는 최근 1분·5분·15분 동안 CPU 처리를 기다린 프로세스의 평균 개수를 나타내며, 이 값이 CPU 코어 수보다 크게 높으면 시스템이 과부하 상태라는 신호다. 프로세스 목록의 PR(우선순위, priority)과 NI(nice 값) 열은 바로 다음 절에서 다룰 우선순위 조정과 직결된다. top을 실행한 상태에서 k 키를 누르면 종료할 PID를 입력받아 그 자리에서 바로 kill을 실행할 수 있고, q 키를 누르면 top을 종료한다.
jobs, fg, bg — 백그라운드 작업 다루기
13편에서 소개한 포그라운드·백그라운드 개념을 실제로 다루는 명령이 jobs·fg·bg다. 이 셋은 시스템 전체가 아니라, 현재 로그인한 셸 세션 안에서 자신이 띄운 작업만을 대상으로 동작한다는 점이 ps나 kill과 다르다.
$ sleep 300 &
[1] 3210
$ vi report.txt
[1]+ Stopped vi report.txt
$ jobs
[1]+ Stopped vi report.txt
[2]- Running sleep 300 &
$ bg %1
[1]+ vi report.txt &
$ fg %2
sleep 300jobs는 현재 셸에 등록된 작업 목록을 보여 준다. 각 줄 앞의 [번호]가 작업 번호(job number)이며, +가 붙은 작업은 가장 최근에 다뤄진 “현재 작업(current job)”, -가 붙은 작업은 그다음으로 최근인 작업을 가리킨다. bg %작업번호는 정지된 작업을 백그라운드에서 계속 실행하게 하고, fg %작업번호는 백그라운드(또는 정지된) 작업을 다시 포그라운드로 끌어온다. 작업 번호 대신 그냥 fg·bg만 쓰면 + 표시가 붙은 현재 작업을 대상으로 한다.
이 명령들이 다루는 “작업 번호(%1, %2)“는 그 셸 세션에서만 유효한 지역적인 번호이며, 시스템 전체에서 통용되는 PID와는 다른 번호 체계라는 점이 함정으로 잘 나온다. 예를 들어 kill %1처럼 작업 번호를 kill의 대상으로 직접 쓸 수는 있지만, 이는 셸이 작업 번호를 실제 PID로 알아서 변환해 주기 때문에 가능한 것이지 %1 자체가 PID인 것은 아니다.
kill과 killall — 프로세스에 신호 보내기
kill이라는 이름 때문에 “무조건 프로세스를 죽이는 명령”으로 오해하기 쉽지만, 정확한 정체는 지정한 프로세스에게 시그널(signal)을 보내는 명령이다. 시그널은 커널이 프로세스에게 “이런 일이 일어났다” 또는 “이렇게 해 달라”고 전달하는 짧은 신호이며, 그 신호를 받은 프로세스가 실제로 어떻게 반응할지는 시그널의 종류와 프로그램의 구현에 따라 달라진다. 즉 “종료해 달라”는 신호를 보내는 것이지, kill 명령 자체가 프로세스를 강제로 지워 버리는 것이 아니다.
kill PID 형태로 시그널 번호나 이름을 생략하면, 기본으로 15번(SIGTERM) 시그널이 전달된다. 특정 시그널을 지정하고 싶으면 kill -시그널번호 PID 또는 kill -시그널이름 PID 형식을 쓴다(예: kill -9 2050, kill -SIGKILL 2050은 같은 동작).
killall은 PID 대신 명령 이름으로 프로세스를 지정해, 같은 이름을 가진 프로세스 전부에게 한꺼번에 시그널을 보낸다. 예를 들어 killall firefox는 실행 중인 모든 firefox 프로세스에게 SIGTERM을 보낸다. PID를 하나하나 찾아 지정할 필요가 없어 편리하지만, 이름이 같은 프로세스를 전부 건드리므로 의도치 않은 프로세스까지 함께 종료시킬 위험이 있다.
주요 시그널 번호
| 번호 | 이름 | 의미 |
|---|---|---|
| 1 | SIGHUP | 제어 터미널의 연결 끊김을 알리는 신호. 데몬에게는 관례적으로 설정 파일을 다시 읽으라는 신호로도 쓰임 |
| 2 | SIGINT | 인터럽트 요청. 키보드에서 [Ctrl] + [c]를 눌렀을 때 전달되는 신호와 같음 |
| 3 | SIGQUIT | 종료 요청과 함께 코어 덤프(core dump, 프로세스의 메모리 상태를 파일로 남기는 것)를 생성하라는 신호. [Ctrl] + [\]로 발생 |
| 9 | SIGKILL | 무조건 즉시 종료. 프로세스가 이 신호를 가로채거나 무시할 방법이 없음 |
| 15 | SIGTERM | 정상 종료 요청. kill 명령의 기본 시그널이며, 프로세스가 자원 정리 등 마무리 작업을 할 시간을 준 뒤 종료됨 |
시험에서 특히 자주 나오는 두 가지는 다음과 같다.
첫째, kill의 기본 시그널은 9번이 아니라 15번(SIGTERM)이다. “강제 종료”라는 인상 때문에 기본값을 9번(SIGKILL)으로 착각하는 오답이 자주 등장한다. 아무 옵션 없이 kill PID만 실행하면 SIGTERM(15)이 전달되어 대상 프로세스에게 “정상적으로 마무리하고 종료해 달라”고 정중하게 요청하는 것이며, 그 프로세스가 응답하지 않을 때 최후 수단으로 kill -9(SIGKILL)를 쓴다.
둘째, SIGKILL(9번)은 왜 무시할 수 없는가. SIGTERM이나 SIGHUP 같은 대부분의 시그널은 프로그램이 원하면 자기만의 처리 방식으로 가로채거나(핸들러를 등록해 원하는 동작을 대신 실행), 아예 무시하도록 설계할 수 있다. 예를 들어 어떤 프로그램은 SIGTERM을 받으면 즉시 죽지 않고 “지금 열려 있는 파일을 안전하게 저장한 뒤에 종료하겠다”는 자기만의 절차를 실행하도록 만들어져 있다. 하지만 SIGKILL과 SIGSTOP(정지 신호) 두 가지만은 커널 수준에서 특별 취급되어, 프로세스가 아무리 자신만의 핸들러를 등록해도 그 처리를 우회할 수 없고 무조건 커널이 직접 그 프로세스를 즉시 종료(또는 정지)시킨다. 이렇게 설계된 이유는, 만약 SIGKILL마저 프로그램이 무시할 수 있다면 오작동하거나 악의적으로 만들어진 프로세스를 관리자가 강제로 멈출 방법이 시스템에 아예 없어지기 때문이다. 즉 SIGKILL은 “다른 모든 수단이 통하지 않을 때 마지막으로 기댈 수 있는 확실한 종료 수단”으로 남겨 둔 것이다. 다만 SIGKILL은 프로세스에게 마무리 작업을 할 기회를 전혀 주지 않고 그 자리에서 강제로 멈추게 하므로, 작성 중이던 파일이 깨지거나 임시 자원이 정리되지 않고 남는 부작용이 있을 수 있다. 그래서 실무에서는 항상 SIGTERM(15)으로 먼저 정중하게 요청하고, 일정 시간이 지나도 반응이 없을 때만 최후 수단으로 SIGKILL(9)을 쓰는 순서를 따른다.
nice와 renice — 프로세스의 우선순위 조정
리눅스는 여러 프로세스가 CPU 하나(또는 몇 개)를 나눠 써야 할 때, 어떤 프로세스에 CPU 시간을 더 배정할지를 우선순위로 정한다. 이 우선순위를 사용자가 조정할 수 있게 해 주는 값이 nice 값(NI) 이다.
nice 값의 범위와 부호 방향 — 시험 단골 함정
nice 값의 범위는 -20부터 19까지다. 이 범위에서 가장 헷갈리는 부분은 숫자가 작을수록(더 마이너스에 가까울수록) 오히려 우선순위가 높아진다는 점이다. 일상적인 직관으로는 “값이 크면 더 중요하다”고 생각하기 쉽지만, nice 값은 정반대로 움직인다.
| nice 값 | 우선순위 | 의미 |
|---|---|---|
| -20 | 가장 높음 | CPU를 가장 우선적으로 배정받음(가장 “덜 착하게” 양보함) |
| 0 | 기본값 | 대부분의 일반 프로세스가 시작할 때 갖는 기본 우선순위 |
| 19 | 가장 낮음 | CPU 배정에서 가장 밀림(가장 “착하게” 양보함) |
이 부호 방향은 “nice(착하다)“라는 이름의 유래를 생각하면 오히려 외우기 쉬워진다. nice 값이 높다는 것은 “그 프로세스가 다른 프로세스들에게 CPU를 양보하는 데 더 착하다”는 뜻이고, 착한 프로세스일수록 자기 순서를 뒤로 미루니 실제 처리 순위는 낮아진다. 반대로 nice 값이 낮다(마이너스)는 것은 “덜 착하다”, 즉 양보를 안 하고 자기 순서를 먼저 챙긴다는 뜻이라 우선순위가 높아지는 것이다.
또 하나 중요한 규칙은 일반 사용자는 자기 프로세스의 nice 값을 0보다 낮게(우선순위를 높게) 설정할 수 없다는 점이다. 일반 사용자는 기본값에서 우선순위를 낮추는 방향(nice 값을 올리는 방향, 즉 남에게 양보하는 방향)으로만 조정할 수 있다. 우선순위를 더 높이는 방향(nice 값을 음수로 낮추는 것)은 시스템 전체의 반응성을 해칠 수 있는 민감한 조정이라, root만 할 수 있다.
nice — 프로세스를 새로 시작하면서 우선순위 지정
nice 명령은 아직 실행되지 않은 프로그램을 처음 실행할 때 nice 값을 지정하는 명령이다.
$ nice -n 10 backup_script.sh-n 뒤의 숫자가 nice 값이며, 지정하지 않고 nice 명령만 쓰면 기본으로 10을 더한 값으로 실행된다(배포판에 따라 세부 기본값은 다를 수 있다).
renice — 이미 실행 중인 프로세스의 우선순위 변경
renice는 nice와 달리 이미 실행되고 있는 프로세스의 우선순위를 나중에 바꾸는 명령이다. 대상을 지정하는 방식이 nice와 다르다는 점이 매우 중요하다.
$ renice -5 2050renice 새로운nice값 PID 형태로 쓰며, 마지막 인자는 반드시 PID(숫자)여야 한다. 이 부분이 시험에서 실제로 나온 함정이다. renice -10 bash처럼 PID 자리에 프로세스 이름을 그대로 적으면, renice는 그것을 PID로 해석하려다 실패해 사용법 오류로 아예 실행되지 않는다. “-10이라는 값 자체가 우선순위를 높이는 방향인가 낮추는 방향인가”를 정확히 안다 해도, 애초에 명령 형식이 잘못되어 있으면 그 효과 자체가 발생하지 않는다는 점까지 함께 봐야 하는 문제였다. renice로 특정 사용자나 그룹 전체를 대상으로 지정하고 싶을 때는 -u 사용자명이나 -g 그룹명 옵션을 PID 대신 쓸 수 있지만, 옵션 없이 그냥 이름만 적으면 안 된다.
직접 해보기 1 — 시그널 15번과 9번의 차이
00편에서 만든 Rocky Linux 컨테이너에서 진행합니다. 아직 안 만들었다면 00편을 먼저 보세요.
kill의 기본 동작과 kill -9가 실제로 다르게 반응하는지 확인합니다.
sleep 300 &
ps aux | grep sleep
kill [위에서 확인한 PID]
sleep 300 &
kill -9 [새 PID]무엇을 보아야 하나: 첫 kill(기본 15번, SIGTERM)로도 프로세스가 정상적으로 사라지는지, 두 번째 kill -9(SIGKILL)로도 사라지는지 각각 ps aux | grep sleep으로 확인합니다. sleep처럼 단순한 프로세스는 둘 다 즉시 종료되어 겉보기엔 차이가 안 보일 수 있습니다.
왜 이걸 해보나: 겉보기 결과가 같아 보여도 “기본값은 15번, 9번은 프로세스가 절대 무시할 수 없다”는 차이는 시험에서 자주 뒤바뀌어 출제된다. 직접 두 명령을 순서대로 쳐 보면서
kill이 옵션 없이도 이미 시그널 번호(15)를 지정하고 있다는 사실 자체를 몸에 익힌다.
직접 해보기 2 — nice 값과 우선순위 방향
nice로 우선순위를 지정해 띄우고, 자기 프로세스라도 우선순위를 더 높이는(마이너스로 낮추는) 방향으로는 일반 사용자가 손댈 수 없다는 것을 오류로 직접 확인합니다. testuser가 없다면(12편 실습을 안 했다면) useradd -m testuser로 먼저 만드세요.
su - testuser
nice -n 10 sleep 300 &
ps -o pid,ni,cmd | grep sleep
renice -5 [PID]무엇을 보아야 하나: ps -o에서 NI 열이 10으로 찍히는지 봅니다. 그다음 자기 자신이 방금 띄운 그 프로세스인데도 renice -5(nice 값을 -5로 낮춰 우선순위를 높이려는 시도)가 권한 오류(Permission denied)로 거부되는지 확인합니다. exit로 root로 돌아옵니다.
왜 이걸 해보나: “일반 사용자는 nice 값을 낮출 수만 있다”를 글로만 읽으면 방향이 쉽게 헷갈리는데, 실제로 권한 오류를 받아 보면 “낮추는(마이너스) 쪽은 root 전용”이라는 게 손에 남는다.
자주 틀리는 점
kill의 기본 시그널을 9번(SIGKILL)으로 착각하는 문제가 반복 출제된다. 기본값은 반드시 15번(SIGTERM) 이다.- SIGKILL이 왜 무시 불가능한지 이유 없이 “그냥 강한 신호라서”로 암기하면, “SIGTERM도 무시할 수 없다”는 식의 응용 문제에서 틀린다. SIGKILL·SIGSTOP만 커널이 프로세스의 핸들러 등록을 우회해 직접 처리하는 특별한 시그널이다.
- nice 값의 범위(-20부터 19까지)와 부호 방향(값이 작을수록 우선순위가 높음)을 반대로 외우는 실수가 매우 흔하다. “nice(착하다)할수록 순서를 양보해 우선순위가 낮아진다”로 방향을 기억한다.
- 일반 사용자가 nice 값을 마이너스로 낮춰 우선순위를 높일 수 있다고 착각하기 쉽다. 일반 사용자는 우선순위를 낮추는 방향으로만 조정할 수 있다.
renice의 마지막 인자를 프로세스 이름으로 착각해 넣는 실수가 실제로 출제됐다.renice는 PID(또는-u·-g로 지정한 사용자·그룹)를 대상으로 하며, 이름을 그대로 쓰면 사용법 오류로 실행되지 않는다.jobs의 작업 번호(%1)와 PID를 같은 것으로 혼동하기 쉽다. 작업 번호는 그 셸 세션에서만 통하는 지역적인 번호다.
핵심 정리
ps aux(또는ps -ef)는 그 순간의 프로세스 스냅샷을,top은 실시간으로 갱신되는 현황판을 보여 준다.jobs·fg·bg는 현재 셸 세션의 작업 번호를 기준으로 포그라운드·백그라운드를 전환한다.kill은 기본으로 15번(SIGTERM)을 보내며, 9번(SIGKILL)만 프로세스가 무시할 수 없는 강제 종료 신호다.killall은 이름으로 여러 프로세스에 동시에 시그널을 보낸다.- nice 값은 -20(최고 우선순위)부터 19(최저 우선순위)까지이며, 값이 작을수록 우선순위가 높다.
nice는 새로 실행할 때,renice는 실행 중인 PID의 값을 나중에 바꿀 때 쓴다.