서비스 · 프로세스 관리 07 / 50 · Part 1. 프로세스 기초
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정 analyst)

1. 들어가며

ps가 "한 장의 사진"이라면 top은 실시간 영상 이다. 서버가 느려졌다는 연락을 받았을 때, 또는 CPU 사용률 경보가 울렸을 때 가장 먼저 여는 도구가 top이다. 하지만 화면에 숫자가 너무 많아 load average와 %Cpu, 메모리의 free와 avail을 헷갈리기 쉽다.

이번 글에서는 top 화면을 위에서 아래로 읽는 순서 를 정리하고, 관제 기록을 위한 배치 모드(-b), 정렬·사용자 필터·스레드 보기를 실습한다.

「리눅스 시스템 기초 33편」에서 top의 기본 화면을 소개했다. 이번 글은 수치 해석과 배치 모드 활용에 집중한다.


2. 핵심 개념

2-1. 요약 영역

줄항목해석
1load average: 0.80, 0.23, 0.08최근 1·5·15분 동안 R + D 상태 프로세스 수의 평균. 코어 수와 비교한다
2Tasks: ... zombie상태별 개수. zombie가 늘면 08편
3%Cpu(s)CPU 시간 분류 (아래 표)
4~5MiB Mem / Swap메모리. avail Mem 이 실제 쓸 수 있는 양

2-2. %Cpu 분류

항목의미높을 때 의심
us사용자 공간 코드 실행계산 작업, 크립토마이너
sy커널 코드 실행과도한 시스템 콜, 네트워크 처리
ninice 값이 양수인 사용자 코드우선순위 낮춘 배치 작업 (09편)
id유휴—
waI/O 완료 대기 중 유휴디스크·NFS 병목
hi / si하드웨어/소프트웨어 인터럽트대량 네트워크 트래픽 (DDoS 등)
st하이퍼바이저가 가져간 시간 (steal)클라우드 VM 자원 경합

2-3. 프로세스 영역 열

열의미
PR / NI스케줄링 우선순위 / nice 값
VIRT가상 메모리 크기 (예약만 한 영역 포함)
RES실제 물리 메모리 사용량
SHR공유 메모리 (라이브러리 등)
S상태 (04편)
%CPU직전 갱신 주기 동안 의 CPU 사용률. 코어 1개 = 100%
TIME+누적 CPU 시간

3. 동작 원리

top 화면 읽는 순서

top은 갱신 주기(기본 3초)마다 /proc/stat(CPU), /proc/meminfo(메모리), /proc/loadavg, 그리고 모든 /proc/PID/stat을 읽는다. %CPU는 두 시점의 누적 CPU 시간 차이 로 계산한다.

t1: /proc/267/stat → utime+stime = 100 ticks
t2 (3초 후):          utime+stime = 400 ticks
%CPU = (400 − 100) / (3초 × 100 ticks/초) = 100%   ← 코어 1개를 꽉 채움

그래서 ps의 %CPU(수명 평균)와 top의 %CPU(최근 구간)는 다르게 나온다. 또 배치 모드의 첫 번째 화면은 비교할 이전 값이 짧아 부정확할 수 있으므로, 정확한 값이 필요하면 -n 2로 두 번 찍고 두 번째를 본다.


4. 명령어 실습

# 1) 환경 확인과 부하 만들기
nproc; uptime
timeout 30 sh -c 'while :; do :; done' &                              # CPU 부하
python3 -c 'b=bytearray(300*1024*1024); import time; time.sleep(30)' &  # 메모리 300MB

# 2) 배치 모드로 한 화면 기록 (-b 배치, -n 횟수, -w 폭)
top -b -n 1 -w 110 | head -14

# 3) 메모리 순 정렬 + 사용자 필터
top -b -n 1 -w 110 -o %MEM -u analyst | sed -n '7,12p'
cat /proc/loadavg

# 4) (Ubuntu) 스레드별 보기(-H)와 2회 샘플링
top -b -H -n 1 -w 110 -p $P
top -b -n 2 -d 1 -w 110 | grep -E '^(%Cpu|MiB Mem)'

대화형 top에서는 P(CPU순), M(메모리순), u(사용자), H(스레드), c(전체 명령), 1(코어별), k(kill)를 쓴다. k로 바로 종료하기 전에 반드시 증거를 먼저 수집한다.


5. 실행 결과

실제 실행 결과 — Rocky Linux 9.8 · analyst@rocky9-lab — top 배치 모드로 한 화면 캡처

실제 실행 결과 — Rocky Linux 9.8 · analyst@rocky9-lab — 정렬 기준 바꾸기 (-o %MEM) 와 사용자 필터 (-u)

실제 실행 결과 — Ubuntu 24.04.5 · analyst@ubuntu-lab — 스레드별 보기 (-H) 와 2회 샘플링

텍스트 원본(실제 출력):

[analyst@rocky9-lab ~]$ nproc; uptime
2
 11:48:42 up 0 min,  1 user,  load average: 0.78, 0.22, 0.07
[analyst@rocky9-lab ~]$ timeout 30 sh -c 'while :; do :; done' &
[analyst@rocky9-lab ~]$ python3 -c 'b=bytearray(300*1024*1024); import time; time.sleep(30)' &
[analyst@rocky9-lab ~]$ sleep 3; top -b -n 1 -w 110 | head -14
top - 11:48:45 up 1 min,  1 user,  load average: 0.80, 0.23, 0.08
Tasks:  22 total,   2 running,  20 sleeping,   0 stopped,   0 zombie
%Cpu(s): 51.7 us,  0.0 sy,  0.0 ni, 48.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   8032.1 total,   6600.8 free,    977.1 used,    693.5 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   7054.9 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    267 analyst   20   0    4604   3236   2972 R 100.0   0.0   0:03.38 sh
      1 root      20   0   24388  13964  10344 S   0.0   0.2   0:00.18 systemd
     21 root      20   0   28512  11056   9804 S   0.0   0.1   0:00.04 systemd-journal
     31 root      20   0   11788   7940   6696 S   0.0   0.1   0:00.01 sshd
     32 root      20   0   20904   9704   8392 S   0.0   0.1   0:00.04 systemd-logind
     37 root      20   0    4728   2708   2524 S   0.0   0.0   0:00.00 atd
     38 root      20   0    6072   3564   2704 S   0.0   0.0   0:00.00 crond
[analyst@rocky9-lab ~]$ timeout 30 sh -c 'while :; do :; done' &
[analyst@rocky9-lab ~]$ python3 -c 'b=bytearray(300*1024*1024); import time; time.sleep(30)' &
[analyst@rocky9-lab ~]$ sleep 3; top -b -n 1 -w 110 -o %MEM -u analyst | sed -n '7,12p'
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    310 analyst   20   0  316996 314456   4768 S   0.0   3.8   0:00.14 python3
     68 analyst   20   0   22460  12020  10236 S   0.0   0.1   0:00.02 systemd
    284 analyst   20   0   18036   7616   5300 S   0.0   0.1   0:00.00 sshd-session
     70 analyst   20   0   24436   4560   1916 S   0.0   0.1   0:00.00 (sd-pam)
    312 analyst   20   0    7704   3856   3312 R   0.0   0.0   0:00.00 top
[analyst@rocky9-lab ~]$ cat /proc/loadavg
0.89 0.26 0.09 3/181 314
[analyst@rocky9-lab ~]$ kill $(jobs -p) 2>/dev/null; true
analyst@ubuntu-lab:~$ python3 -c 'import threading
> def spin():
>     while True: pass
> [threading.Thread(target=spin,daemon=True).start() for _ in range(2)]
> import time; time.sleep(20)' &
analyst@ubuntu-lab:~$ sleep 2; P=$!; top -b -H -n 1 -w 110 -p $P | sed -n '7,12p'
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    307 analyst   20   0  162768  10224   6608 S  50.0   0.1   0:01.16 python3
    308 analyst   20   0  162768  10224   6608 R  50.0   0.1   0:01.14 python3
    305 analyst   20   0  162768  10224   6608 S   0.0   0.1   0:00.01 python3
analyst@ubuntu-lab:~$ top -b -n 2 -d 1 -w 110 | grep -E '^(%Cpu|MiB Mem)'
%Cpu(s): 95.2 us,  4.8 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   8032.1 total,   6872.1 free,    705.1 used,    694.2 buff/cache
%Cpu(s): 99.1 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.9 st
MiB Mem :   8032.1 total,   6872.1 free,    705.1 used,    694.2 buff/cache
analyst@ubuntu-lab:~$ kill $P

6. 결과 해석

관찰의미
nproc = 2코어 2개. load 2.0이면 포화 상태다
%Cpu(s): 51.7 us, 48.3 id코어 2개 중 1개가 사용자 코드로 꽉 찼다 → 전체의 약 50%
sh 프로세스 R 100.0한 프로세스가 코어 하나를 100% 사용. top의 %CPU는 코어 단위라 멀티코어에서 100%를 넘을 수 있다
load 0.80 (1분)부하를 막 걸어서 1분 평균이 1.0을 향해 올라가는 중이다. 15분 값(0.08)과의 차이로 최근에 시작된 부하 임을 알 수 있다
-o %MEM 정렬 1위 python3 RES 314456 (3.8%)300MB 배열이 실제 물리 메모리를 차지했다. VIRT(316996)와 RES가 거의 같은 이유는 bytearray가 0으로 채워져 실제로 접근된 메모리이기 때문
avail Mem 7054.9 vs free 6600.8buff/cache 중 회수 가능한 부분을 더한 값이 avail이다. 메모리 여유는 avail로 판단한다
/proc/loadavg = 0.89 0.26 0.09 3/181 314앞의 3개는 load, 3/181은 실행 가능/전체 스레드 수, 314는 마지막 할당 PID
Ubuntu -H: python3 스레드 307·308 각 50%스레드 두 개가 CPU를 나눠 쓴다. Python GIL 때문에 합쳐도 약 100%다
2회 샘플링: 95.2 us → 99.1 us첫 화면과 두 번째 화면의 값이 다르다. 정확한 값은 두 번째 화면
0.9 st가상화 호스트가 CPU를 잠시 가져간 시간. 실습 환경이 VM 위에 있다는 흔적이다

7. 보안 관점

주제내용
크립토마이너us가 장시간 높고, 모르는 프로세스가 코어 수만큼 %CPU를 쓰면 채굴을 의심한다. 일부는 top 실행을 감지하면 잠시 멈추는 기능이 있어 배치 모드 기록과 모니터링 추이가 필요하다 (47편)
이름 위장top의 COMMAND는 comm(15자)이다. kworker, kthreadd 같은 커널 스레드 이름으로 위장한 사용자 프로세스는 c 키나 /proc/PID/exe로 확인한다
si/hi 급증소프트 인터럽트가 급증하고 네트워크 처리가 몰리면 DDoS나 대량 스캔을 의심한다
가용성wa·load 급증은 공격이 아니어도 서비스 장애다. 관제는 보안 이벤트와 가용성 이벤트를 함께 본다

8. 보안관제 관점

[Detection]  모니터링 경보: web01 CPU 95% 30분 지속, 평소 20%
     ↓
[기록]       top -b -n 2 -d 5 -w 200 > top_$(hostname)_$(date +%F_%H%M).txt
     ↓
[분석]       %Cpu us 높음 / CPU 상위 프로세스 = "kdevtmpfsi" (UID www-data)
             ps -o pid,ppid,lstart,args -ww -p <PID> ; ls -l /proc/<PID>/exe
     ↓
[판단]       커널 스레드 이름인데 PPID 1, 사용자 www-data, exe=/tmp/... → 채굴 악성코드
     ↓
[Response]   증거 수집 → 프로세스 정지 → 지속성(cron·systemd) 확인 → 침투 경로(웹) 조사
관점확인
추이순간값보다 모니터링 그래프의 시작 시점이 중요하다. 부하 시작 시각 = 침해 시각 후보
기록대화형 화면은 남지 않는다. -b로 파일을 남겨 보고서에 첨부한다
기준load는 코어 수로 나누어 판단한다. 2코어의 load 2.0과 32코어의 load 2.0은 다르다

9. 실무에서 자주 발생하는 실수

실수결과예방
free 메모리가 적다고 장애 판단캐시를 사용 중일 뿐인 정상 상태를 오판avail Mem 으로 판단
load 값을 코어 수와 비교하지 않음과대·과소 평가nproc과 함께 본다
배치 모드 첫 화면 수치를 그대로 보고부정확한 %CPU-n 2로 두 번째 값 사용
%CPU 합이 100%를 넘어 이상하다고 판단멀티코어에서는 정상코어 1개 = 100% 기준 이해
top에서 바로 k로 종료증거 소실기록·수집 후 종료

10. 실습 체크리스트

[ ] load average 를 코어 수(nproc)와 비교해 해석했다
[ ] %Cpu 의 us, sy, wa, st 의미를 구분했다
[ ] free 와 avail Mem 의 차이를 설명할 수 있다
[ ] top -b -n 1 로 결과를 파일로 남길 수 있다
[ ] -o %MEM, -u USER 로 정렬·필터를 적용했다
[ ] -H 로 스레드별 CPU 사용량을 확인했다

11. 핵심 정리

  • top은 load → Tasks → %Cpu → Mem → 프로세스 목록 순서로 읽는다.
  • load average는 R + D 프로세스 수의 평균이며, 코어 수와 비교해야 의미가 있다.
  • %CPU는 코어 1개 = 100% 기준이며, 직전 갱신 주기의 사용률이다.
  • 메모리 여유는 free가 아니라 avail 로 판단한다.
  • 관제 기록은 top -b -n 2로 파일에 남긴다. 대화형 화면은 증거가 되지 않는다.

12. 다음 편 예고

다음 글 「08. 좀비 프로세스와 고아 프로세스」 에서는 04편에서 잠깐 본 <defunct>를 본격적으로 다룬다. 좀비는 왜 kill -9로 사라지지 않는지, 부모가 먼저 죽은 고아 프로세스는 누가 입양하는지 직접 확인한다.


참고 자료


시리즈 이동

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글