Study/Linux

[Linux]top을 통해 살펴보는 프로세스 정보들

seomj 2024. 4. 6. 00:53

시스템 상태를 파악하기 위해 top 명령을 사용해보자. 전반적으로 파악하는 방법과 특히 프로세스와 관련된 값들이 어떤 의미가 있는지 보자.


시스템 상태 살피기

root@worker1:~# top

top - 06:45:11 up  1:57,  1 user,  load average: 1.46, 1.55, 1.16
Tasks: 300 total,   2 running, 298 sleeping,   0 stopped,   0 zombie
%Cpu(s): 37.5 us, 14.2 sy,  9.2 ni, 38.2 id,  0.3 wa,  0.0 hi,  0.5 si,  0.0 st
MiB Mem :   1928.3 total,     99.4 free,   1164.8 used,    664.0 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.    586.8 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND           
  96030 root      39  19  147328 103316  76820 R  20.5   5.2   0:00.62 apt-check         
  95899 root      20   0  386108 132820  56816 S  10.2   6.7   0:00.62 unattended-upgr   
    890 root      20   0 1860032  44388  15776 S   5.0   2.2   2:45.12 kubelet       
    ...

top 명령을 옵션없이 입력하면 3초 간격으로 갱신되는 화면을 볼 수 있다.

 

순간의 정보를 확인하기 이해 -b 옵션을 사용

root@worker1:~# top -b -n 1
top - 06:51:31 up  2:03,  1 user,  load average: 0.37, 0.63, 0.86
Tasks: 292 total,   1 running, 291 sleeping,   0 stopped,   0 zombie
%Cpu(s): 16.7 us,  2.8 sy,  0.0 ni, 80.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   1928.3 total,    272.8 free,   1095.8 used,    559.7 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.    656.7 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  99980 root      20   0   11984   3848   3200 R  11.8   0.2   0:00.04 top
    890 root      20   0 1860032  47296  19472 S   5.9   2.4   3:12.45 kubelet
...

06:51:31 up 2:03 : 현재 서버의 시간과 구동 시간

1 user, load average: 0.37, 0.63, 0.86 : 몇 명의 사용자가 로그인해 있는지, 시스템의 Load Average는 어느정도인지

Tasks: 292 total, 1 running, 291 sleeping, 0 stopped, 0 zombie : 현재 시스템에서 구동 중인 프로세스의 개수

 

그 아래엔 각각 CPU, Mem, Swap 메모리의 사용량이다.

PR : 프로세스의 실행 우선 순위

NI : PR을 얼마만큼 조절할 것인지를 결정

VIRT, RES, SHR : 프로세스가 사용하는 메모리의 양이 얼마인지 확인할 수 있는 정보

S : 프로세스의 상태

 

각각에 대해 더 자세히 알아보자.

 

프로세스의 메모리

  • VIRT: task가 사용하고 있는 virtual memory의 전체 용량
  • RES: task가 사용하고 있는 물리 메모리의 양
  • SHR: 다른 프로세스와 공유하고 있는 shared memory의 양
    • ex. 라이브러리 리눅스 프로세스들은 glibc라는 라이브러리를 참조, 매번 glibc를 메모리에 올려 사용하는 것은 공간 낭비이다.

메모리 점유율이 높은 프로세스를 찾기 위해서는 RES 영역이 높은 프로세스를 찾아야 한다.

 

그렇다면 왜 VIRT와 RES로 구분하는가?

VIRT로 표현되는 가상 메모리는 프로세스가 커널로부터 예약받은 메모리라고 생각할 수 있다. 프로세스는 시스템 콜로 커널에 메모리를 요청한다. 요청받은 커널은 가용한 공간이 있다면 프로세스에게 가상의 메모리 주소를 전달한다. 하지만 실제 물리 메모리에 할당하진 않는다. 이를 Memroy Commit이라고 한다.

그 후 프로세스가 해당 메모리 영역에 작업을 하면 Page fault가 발생하고, 그때 커널은 실제 물리 메모리에 프로세스의 가상 메모리를 매핑한다. 이는 Page Table이라는 커널의 전역 변수로 관리된다.

이렇게 물리 메모리에 바인딩된 영역이 RES가 된다.

 

왜 커널은 프로세스의 메모리 요청에 따라 즉시 할당하지 않고 Memory Commit과 같은 기술을 써서 요청을 지연시키는 걸까?

새로운 프로세스를 만들기 위한 콜을 처리해야 하기 때문이다. fork() 시스템 콜을 사용하면 커널은 현재 실행 중인 프로세스와 똑같은 프로세스를 하나 더 만들게 된다. 이후 exec() 시스템 콜을 통해서 전혀 다른 프로세스로 변한다. 따라서 이때 확보해 둔 메모리 영역이 쓸모없어지는 경우가 생긴다. 그래서 COW(Copy-On-Write)라는 기법을 통해서 복사된 메모리 영역에 실제로 쓰기 작업이 발생한 후에야 실질적인 메모리 할당을 시작한다.

 

그렇다면 VIRT는 malloc() 등의 시스템 콜을 사용하면 늘어나게 되는데, 한도 끝도 없이 늘어나게 될까?

더 이상 줄 수 있는 메모리 영역이 없다면 swap을 사용하거나 OOM으로 프로세스를 죽이는 등의 방법으로 메모리를 확보한다.

 

그렇다면 VIRT와 같이 실제 사용하지 않는 영역의 경우는 어떻게 될까?

vm.overcommit_memory 파라미터에 의해 결정된다.

Memory Commit 상태는 sar 모니터링 툴로 확인할 수 있다. 여기서 %commit이라는 시스템의 메모리 커밋 비율을 확인할 수 있다. 해당 비율이 낮다면 순간적으로 쓰기 작업이 들어가도 시스템 전체적으로는 전혀 문제가 없다. 하지만 해당 비율이 높다면 시스템에 부하를 일으키거나 커널 응답 불가 현상을 일으킬 수도 있다. 그렇기 때문에 vm.overcommit_memory라는 파라미터로 제어한다.

 

값으로는 0, 1, 2 세팅 가능하다.

  • 0: 기본값. page cache와 swap영역 그리고 slab reclaimabl을 합한 값.
  • 1: 무조건 commit.
  • 2: 제한적으로 commit. vm.overcommit_ratio에 설정된 비율과 swap 영역의 크기를 토대로 계산.

 

프로세스의 상태 보기

앞서 살펴본 VIRT, RES, SHR 옆에 S가 있었다.

man top
...
29. S  --  Process Status
           The status of the task which can be one of:
               D = uninterruptible sleep
               I = idle
               R = running
               S = sleeping
               T = stopped by job control signal
               t = stopped by debugger during trace
               Z = zombie
...
  • D: 디스크 혹은 네트워크 I/O를 대기. Wait Queue에 들어가게 된다. (usually IO)
  • R: 실행 중인 프로세스
  • S: sleeping 상태 프로세스. D와 차이는 요청한 리소스를 즉시 사용할 수 있는지 여부이다. (waiting for an event to complete)
  • Z: 부모 프로세스가 죽은 자식 프로세스

 

D: 깨울 수 없음. 즉, 중지 시킬 수 없는 잠자고 있는 프로세스 상태

S: 깨울 수 있음. 즉, 잠자고 있지만 중지 시킬 수 있는 프로세스 상태

 

Z

모든 프로세스는 fork()를 통해서 만들어지기 때문에 부모와 자식 관계가 되고, 보통 부모 프로세스는 자식이 완료될 때까지 기다리게 된다. 하지만, 부모 프로세스가 죽었는데도 자식 프로세스가 남아 있거나 자식 프로세스가 죽기 전에 비정상적인 동작으로 부모 프로세스가 죽는 경우 좀비 프로세스가 만들어진다. 이는 시스템의 리소스를 차지하지 않기 때문에 큰 문제가 되지 않지만, PID를 점유하고 있기에 문제가 된다. 새로운 프로세스에 할당할 PID가 모자라게 되고, 이는 더 이상 PID를 할당하지 못하는 PID 고갈로 이어질 수 있다.

 

프로세스 우선순위

CPU마다 Run Queue가 존재하며, 이는 우선순위 별로 프로세스가 연결되어 있다. 스케줄러는 유휴 상태에 있던 프로세스가 깨어나거나 특정 프로세스가 스케줄링을 양보하는 등의 경우에 현재 Run Queue에 있는 프로세스들 중 하나를 꺼내 디스패처에 넘겨준다. 디스패처는 현재 실행 중인 프로세스의 정보를 다른 곳에 저장한 후 넘겨받은 프로세스의 정보를 가지고 다시 연산하도록 요청한다.

스케줄러의 기본 동작

 

PR과 NI는 커널이 프로세스를 스케줄링할 때 사용하는 우선순위를 나타내는 값이다.

  • PR은 커널에서 인식하는 해당 프로세스의 실제 우선순위 값을 의미한다.
  • NI는 nice 값이라고 부르며, 명령어를 통해서 우선순위를 낮출 때 사용한다.

기본적으로 모든 프로세스들은 20의 우선순위 값을 갖는데, NI가 -4라면 PR은 16의 값을 가지게 되며, 이는 20보다 더 자주 실행된다.

PR값 중 RT로 표현되는 프로세스들이 있다. RT(Real Time) 스케줄러는 반드시 특정 시간 안에 종료되어야 하는 중요한 프로세스들, 커널에서 사용하는 데몬들이 대상이다.

 

nice로 우선순위를 낮췄다고 해도 CPU Core 수와 동일한 수의 프로세스가 돌아가고 있다면 CPU 경합을 벌일 필요가 없기 때문에 nice로 우선순위를 낮춰도 비슷한 시간에 끝나게 된다. CPU Core 수가 2개이고 두 개의 터미널을 열어 2개의 프로세스를 실행시키면 나눠서 실행되기 때문에 효과가 없다. 그렇다면 3개의 터미널을 열어 3개의 프로세스를 실행시켜보자. 이때는 nice로 우선순위를 낮춘 프로세스가 확실하게 먼저 끝난다. 우선순위를 낮춘 프로세스가 CPU 1개를 독차지하고 나머지가 하나의 CPU를 나눠서 사용하기 때문이다.

 

 

 

<참고>

DevOps와 SE를 위한 리눅스 커널 이야기