brunch

You can make anything
by writing

C.S.Lewis

by doz Nov 16. 2020

2. free 명령어.. 알고 쓰시나요?

이번 시간에는 free 명령어를 통한 결과를 알아보고 이해하는 시간을 갖도록 하자.



free 명령어가 뭐야?

free 명령어는 리눅스 시스템에서 메모리의 전체적인 현황을 빠르게 살펴볼 수 있는 명령어이다. 전체 메모리의 크기와 사용 중인 메모리 크기, 그 밖에 공유 메모리와 buffer, cache 메모리 및 swap의 크기를 알 수 있다.


root@desktop:~# free -h

                     total        used        free      shared  buff/cache   available

Mem:           3.7G        171M        3.0G         59M        502M        3.2G

Swap:          2.0G          0B        2.0G

*h 옵션 : human-readable

위와 같이 명령어를 실행시켰을 때, 각종 메모리 관련된 수치가 나오는 것을 확인할 수 있다.


그렇다면 free 명령어는 왜 중요할까?


메모리는 리눅스 시스템에서 프로세스가 연산할 수 있는 공간을 제공해주는 리소스이다. 만일 이러한 메모리가 부족하게 되면 어떤 일이 발생하게 될까? 답은 간단하다. 사용 가능한 메모리가 없어서 연산이 불가능해지고, 이후 메모리를 필요로 하는 모든 프로세스는 공간 확보가 불가능하여 결국은 시스템 응답 불가 현상 또는 시스템 성능 저하를 초래하게 된다. 그러므로 메모리를 모니터링하는 것은 매우 기본이며 중요하다고 볼 수 있다. 따라서 이러한 메모리를 모니터링하는 툴인 free의 역할도 매우 중요하다(물론 다른 방법도 존재하긴 한다)




자, 그러면 하나씩 뜯어보자.


각각 total, used, free, shared, buffer, cache, available은 어떤 의미일까?


total : 현재 시스템에 설치되어있는 전체 메모리의 크기를 나타낸다

used : 현재 사용 중인 메모리 크기를 나타낸다. (total - free - buffer/cache)

free : 시스템에서 사용 가능한 잔여 메모리의 크기를 나타낸다. 즉, 사용되지 않은 메모리의 양으로 커널 또는 애플리케이션이 사용 가능하다.

shared : 프로세스 사이에서 공유되는 메모리의 크기이다. 주로 프로세스 또는 스레드 간 통신에 사용된다.
man을 통해 설명을 확인하면 tmpfs에서 사용되는 메모리로 나와있다. 이는 top 명령어에서의 shr과 다르다.(top에서의 shr은 공유되는 라이브러리 등이 올라와있는 메모리를 말한다)

buffer / cache : 커널이 성능 향상을 위해 캐시 영역으로 사용하는 메모리의 크기이다. buffer는 Buffer cache의 크기를 나타내며, cache는 Page cache + slabs의 크기이다.

available : centos7에 들어서면서 기존에 존재하던 -/+ buffer/cache 부분이 제거되고 생겨났다. 해당 설명을 man으로 확인하면 아래와 같다.

Estimation  of  how  much  memory  is available for starting new applications, without swapping. Unlike the data provided by the cache or free fields, this field takes into account page cache and also that not all reclaimable memory slabs will be reclaimed due to items being in use (MemAvailable in /proc/meminfo, available on kernels 3.14, emulated on kernels 2.6.27+, otherwise the same as free)           

요약하자면, swapping 없이 새로운 애플리케이션을 실행 가능한 가용 메모리의 크기를 나타낸다.
/proc/meminfo 내부의 MemFree, Active(file), Inactive(file), SReclaimable 크기를 더한 값에서 /proc/zoneinfo의 low 값을 빼면 된다.

Buffer / cache는 정확히 뭘까?


웹상에서 free 명령어에 검색하면 나오는 대부분의 글은 모두 buffer / cache를 다룬다. 그만큼 중요하면서도 어려운 내용을 포함하고 있다. 그렇다면 진짜 buffer / cache는 어떤 의미 일까?


우선 buffer / cache를 사용하는 이유를 알아야 한다.


커널은 블록 디바이스라고 불리는 하드디스크 등의 저장 장치를 통해 데이터를 읽거나, 사용자의 데이터를 작성한다. 하지만, 이러한 저장 장치는 다른 장치들에 비해 속도가 너무 느리다. 매번 I/O 요청 때마다 디스크에 접근하여 파일을 읽고 쓰게 된다면 상당한 시간을 기다려야 하며, 시스템에도 부하가 일어난다. 그렇다면 이러한 문제를 어떻게 해결해야 할까? 바로 캐싱이다.


커널은 이렇게 느린 디스크를 잘 활용하고자 메모리의 일부를 디스크 요청에 대한 캐싱 영역으로써 활용한다. 한번 읽은 파일의 내용을 캐시 영역에 저장한 뒤, 다시 해당 파일을 읽으려 시도할 때 상대적으로 매우 빠른 메모리 영역을 활용하여 요청을 좀 더 빠르게 처리할 수 있게 한다. 즉, 커널이 전체적인 성능 향상을 위하여 사용하는 메모리의 일부 영역이며, 이러한 영역을 buffer, cache로 부른다.


그러면 buffer와 cache는 동일한 걸까?

먼저, Unix 파일 시스템(UFS)의 종류를 살펴보자.


UFS structure



유닉스의 파일 시스템은 디렉터리와 내부의 파일을 찾기 쉽도록 유지 및 관리하는 시스템이다. 다음과 같은 4가지의 블록으로 구성되어 있다.








1. Boot Block 

    부팅에 필요한 정보를 담고 있다. 

2. Super Block

    파일 시스템에 대한 총체적인 정보를 담고 있다. 어디서부터 inode block인지, data block인지, 파일 시스템의 크기 등을 저장하고 있다.

3. Inode(index node) list

    파일 하나당 inode가 할당되며, 해당 inode는 파일의 메타 데이터를 갖고 있다. 이러한 inode의 묶음으로 보면 된다. inode는 파일의 정보인 접근 권한, 소유주, 파일 크기, inode 번호 등을 저장하는 부분이다.

4. Data Block

    실제로 데이터의 내용이 저장되는 디스크 영역이다. 각각의 데이터 블록은 한 번에 하나의 파일만 할당될 수 있다.


위와 같은 4가지 블록을 설명한 이유는 buffer와 cache를 분리해서 이해하기 위함이다.

앞서 설명할 때, buffer는 Buffer Cache로 설명했으며 cache는 Page Cache + Slabs라고 표현했다. 그러면 Page Cache, Buffer Cache, Slabs는 무엇인지 알아보자.



Page Cache


Page cache는 리눅스 커널이 I/O 성능 향상을 위하여 사용하는 메모리 영역이다. 커널은 느린 디스크 접근의 단점을 보완하기 위하여 한번 읽은 파일의 내용을 커널 메모리의 일부분인 Page Cache 영역에 저장한다. 이후 재 접근이 일어나게 될 경우 디스크의 내용을 전달하는 것이 아닌 메모리의 Page Cache 영역에 저장된 내용을 전달함으로써 빠른 접근을 가능하게 한다. 이를 Page라는 단위로 관리하며, Page cache라고 부른다.


프로세스가 파일을 읽어 들이면 커널은 프로세스의 메모리에 파일의 데이터를 직접 복사하는 것이 아니라, 커널의 메모리 내에 있는 Page Cache라는 영역에 복사한 뒤 이 데이터를 프로세스 메모리에 복사한다. 커널은 자신의 메모리 안에 Page Cache 에 캐싱한 파일과 그 범위 등의 정보를 보관하는 관리 영역을 가진다. 이러한 Page Cache 영역은 전체 프로세스의 공유 자원이므로 읽어 들인 프로세스는 최초의 데이터에 접근한 프로세스와 달라도 이슈가 없다. 


프로세스가 데이터를 파일에 쓰면 커널은 Page Cache에 데이터를 작성하고, 이때 해당하는 페이지 테이블의 '수정된 것' 이라는 의미로 dirty 플래그를 붙인다. 이 플래그가 붙은 페이지를 dirty page 라고 부른다. 이러한 dirty page의 내용은 나중에 커널의 백그라운드로 처리되며 스토리지 내의 파일에 반영하고 해당 플래그를 지운다.


Page Cache 사이즈는 시스템의 메모리가 비어있는 상태에서 프로세스가 캐시에 없는 파일을 읽을 때마다 점점 증가한다. 이후 시스템 메모리가 부족해지면 dirty page 가 아닌 것들 부터 파기시켜 메모리 확보를 하고, 그래도 부족하면 dirty page 들은 write back 한 뒤 파기시켜 확보한다.


관련 커널 파라미터 

vm.dirty_writeback_centisecs : dirty page의 write back이 발생하는 주기를 변경한다. 단위는 1/100초로 default는 5초에 1번 write back한다.


Buffer Cache


Buffer cache도 Page cache와 동일하게 리눅스 커널이 I/O 성능 향상을 위하여 사용하는 메모리 영역이다. Page cache는 파일의 내용을 저장했다면, Buffer cache는 파일의 내용이 아닌 UFS 기준으로 Super block과 inode block에 해당하는 메타데이터를 저한다. 디렉터리를 읽고자 하는 경우에는 디렉터리에 포함된 파일들의 inode block 들을 버퍼 캐시에 저장한다. 이후 접근 시, buffer cache에 존재하는 값을 가져다 보여준다. 실제로 ls 명령어를 입력하면 free 명령어의 buffers 결과가 달라짐을 확인할 수 있다.


Slab 영역

커널 역시 프로세스이므로 메모리가 필요하다. 즉, slab 영역이란 디렉터리 구조를 캐시 하는 dentry cache와 파일의 정보를 저장하고 있는 inode cache 등 커널이 사용하는 메모리 영역을 말한다. 쉽게 말하자면 커널이 내부적으로 사용하는 캐시라고 표현할 수 있다.


inode와 dentry는 파일 자료구조를 의미한다. VFS(Virtual File System)와 관련된 부분을 공부하다 보면 자주 만나게 되는 dentry는 경로명 탐색을 위한 캐시 역할도 수행한다고 알려져 있다.

간단히 얘기해서 어떠한 파일을 생성할 때 파일의 정보를 담고 있는 inode와 dentry는 보다 빠른 데이터 접근을 위해서 커널의 Slab 자료구조에 추가된다고 이해하면 된다.


cat /proc/meminfo 명령어를 수행하면 다음과 같은 항목을 볼 수 있다.


Slab:                     687768 kB

SReclaimable:     632124 kB

SUnreclaim:         55644 kB


Slab은 앞서 말한 것처럼 커널이 사용하는 메모리의 크기를 나타낸다.

SReclaimable은 Slab Recaimable로써 메모리 부족 시, 프로세스로 할당함으로써 재사용 가능한 slab 영역의 크기를 나타낸다. 대부분의 Slab 캐싱 영역이 여기에 포함된다.

SUnreclaim은 Slab Unreclaim 이로써 메모리가 부족하여도 재 할당되지 않는 메모리 크기를 나타낸다. 커널이 현재 사용 중인 영역의 크기이며, 해제해서 사용할 수가 없다.


Slab에 대해서 더 자세한 내용을 살펴보고 싶으면 slabtop이라는 명령어를 통해서 확인 가능하다.


위 내용을 통해 buffer랑 cache는 엄연히 다른 영역이며, 커널의 I/O 성능 향상을 위해 사용한다는 점에선 동일한 장점을 지녔다. 


그렇다면 buffer/cache는 어디에 속하는 걸까?


man free에서 보면 used는 다음과 같이 계산한다고 한다.

Used memory (calculated as total - free - buffers - cache)

즉, used에는 buffers, cache(page, slab)이 포함되지 않는다는 것이다.  별개로 표현하는 것처럼 free 에도 포함이 되지 않는다(이전 버전에서는 포함됐다고 표현해도 문제가 없었다). 이를 어우르는 것이 바로 available이라고 보면 될 것 같다.


또한, 일부 글에서는 slab은 used에 포함된다고 하지만, 실제로 man free를 수행하면 cached의 정의는 다음과 같이 나온다. 

"Memory  used  by  the  page  cache  and  slabs  (Cached and SReclaimable in  /proc/meminfo)" 

따라서 buffer/cache는 무조건 전부 반환될 수 있는 크기로 보면 안 된다. SUnreclaim은 반환이 불가능하기 때문이다.

메모리가 부족하다면, buffer/cache 는 어떻게 될까?


free 공간의 여유가 있다면 커널은 I/O 성능 향상을 위하여 buffer/cache의 영역을 점점 더 넓혀간다. 하지만  메모리 사용량이 많은 프로세스가 used 공간을 점점 더 넓혀나가게 될 경우, 커널은 Cache 영역으로 사용하던 영역을 반환한다. 즉, 메모리의 여유가 있을 경우에는 자체적으로 커널에서 I/O 성능 향상을 위한 캐싱 영역을 늘려가며, 반대로 여유가 없을 경우에는 할당 한 캐시 영역을 반환함으로써 메모리 공간을 확보한다.


만일 buffer/cache 영역을 전부 반환했는데도 메모리가 부족하면?


buffer/cache의 영역을 전부 반환했음에도 불구하고 메모리가 부족하다면, 커널은 swap 영역을 확인 및 사용하게 된다. 하지만 swap 영역을 사용하는 것은 성능 저하를 야기하게 된다. 더불어 swap 영역마저 전부 사용했을 경우, OOM(Out of Memory) Killer에 의해 프로세스들이 강제 종료되게 될 것이다. 자세한 swap에 대한 내용은 따로 작성하도록 하겠다. 


관련 커널 파라미터 

vm.panic_on_oom : 기본값은 0이며, 메모리 부족시에 OOM Killer를 실행한다. 1로 변경하면 메모리 부족 시 서버가 강제로 종료된다.



메모리의 비정상적인 사용이 발견되었다면 어떻게 해야 할까?


가장 핵심은 모니터링을 통한 원인 분석이다. 메모리의 비정상적인 상태가 확인되었을 경우, free 명령으로는 자세한 내용을 보는데 한계가 있다. 이러한 한계점은 앞서 설명한 파일인 /proc/meminfo를 통해서 극복할 수 있다. 그렇다면 meminfo에 대해서 알아보자


/proc/meminfo


리눅스에서는 /proc/meminfo를 통해 자세한 메모리 현황을 볼 수 있도록 했다.

root@localhost:~$ cat /proc/meminfo 

MemTotal:       16267596 kB 

MemFree:          599360 kB    

MemAvailable:   14724428 kB

Buffers:          442136 kB

Cached:         13563836 kB

SwapCached:            0 kB

Active:          9923056 kB

Inactive:        4897540 kB

Active(anon):     498904 kB

Inactive(anon):   486412 kB

Active(file):    9424152 kB

Inactive(file):  4411128 kB

SwapTotal:       8227836 kB

SwapFree:        8227836 kB

Dirty:                16 kB

Slab:             687576 kB

SReclaimable:     632124 kB

SUnreclaim:        55452 kB


- Buffers : 앞서 설명한 것처럼 파일 시스템의 메타 데이터가 저장된 Buffer cache의 크기이다.

- Cached : Page cache의 크기를 나타낸다. 

SwapCached : 메모리 부족으로 인하여 swap이 되었다가 다시 돌아왔을 때, swap 영역에서는 해당 공간을 지우지 않는다. 추후에 다시 부족하게 될 경우 I/O 부하를 줄이게 하기 위해서 이다.

Action : 비교적 최근에 사용된 영역으로써, Action(file)과 Action(anon)을 합친 크기이다. 

Inaction : 비교적 참조/사용이 오래된 영역으로써 Swap 영역으로 이동될 수 있는 메모리 영역을 말한다. Inaction(file)과 Inaction(anon)을 합친 크기이다.

Action(anon) : anonymous의 줄임말로, Page cache를 제외한 비교적 최근에 사용된 프로세스들이 사용하는 메모리 영역이다.

Action(file) : 커널이 I/O 성능 향상을 위해 사용하는 Page cache, Buffer cache 영역의 크기이다. 이름처럼 비교적 최근에 참조되어 swap 영역으로 이동되지 않을 메모리 영역이다.

Inaction(anon) : Action(anon)과 동일하게 프로세스들이 사용하는 영역이지만, 참조가 오래되어 swap 영역으로 이동할 수 있는 메모리 영역이다.

Inaction(file) : Page cache, Buffer cache 목적으로 사용되는 영역이지만, 참조된 지 오래되어 swap 영역으로 이동될 수 있는 메모리 영역이다.

- Dirty : Page cache를 통해 저장된 파일 내용을 사용하는 과정에서 쓰기 작업이 이루어질 경우, 디스크에 있는 내용과 Page cache에 있는 내용은 서로 다르게 된다. 이때 커널은 달라졌음을 표시하는 Dirty 비트를 켜고 해당 영역을 Dirty page라고 부른다. 이후 '일정 주기, 일정 크기' 단위로 디스크에 동기화를 한다. 이러한 영역의 크기를 말한다.


위 설정을 보면 Action과 Inaction이라는 생소한 옵션이 보인다. 내부 anonymous는 단순히 프로세스들이라는 이름으로, file 은 buffer/cache라고 생각하면 간단하다. 하지만, Action과 Inaction은 비교적 최근, 나중에 참조된 것을 말하는 것으로 보인다. 그렇다면 다음과 같은 생각을 해볼 수 있다.


'비교적'이란 게 기준이 뭐야?
LRU list for anon / file

그림에서 보듯이 anon과 file은 LRU(Least Recently Used) List에 의해 관리되며, active_list와 inactive_list로 나뉜다. 최근에 참조된 메모리는 active_list의 head에 추가되며, tail에 위치한 영역은 inactive_list의 head로 들어가게 된다. inactive 영역의 tail 부분은 이후 free 영역으로 이동함으로써 메모리 공간이 해제되거나 swap 영역으로 이동된다. 즉, 비교적이라는 것은 Active LRU List와 Inactive LRU List 중 어디에 프로세스가 속해 있느냐에 따라서 표현될 수 있다.

 

그렇다면 언제 active_list에 있는 메모리 영역이 inactive_list로 옮겨갈까?


바로 메모리 부족 현상이 발생되어 해제해야 할 메모리를 찾아야 하는 순간에 커널이 확인하게 된다. 메모리 부족 현상이 발생되면, kswapd라는 커널 데몬이 동작한다. 우선 Active LRU List에 있는 항목 중 tail에 위치한 페이지를 Inactive LRU List의 head로 이동시킨다. 이후 Inactive LRU List의 tail에 위치한 페이지를 메모리 해제/swap 공간으로 이동하는 작업을 진행함으로써 메모리 확보를 한다. 즉, 메모리가 부족했을 때 kswapd에 의해서 옮겨지게 된다.


free 메모리 공간이 모두 사용되어야 cache free나 swap 하는 건가?


그렇지 않다. 이럴 때 사용되는 커널 파라미터가 존재한다. 바로 vm.min_free_kbytes라는 파라미터이다.

이는 최소한으로 시스템에서 유지해야 할 free 영역을 나타낸다. 메모리 사용량이 증가할 경우 kswapd는 해당 파라미터를 참고하여 그만큼의 공간은 유지한 채 swap 또는 cache free 작업을 진행하게 된다.








마치며


free에 포함된 내용을 설명하다 보니 정말 계속해서 연관된 내용이 나오게 된다. 해당 내용을 작성하면서 어쩌면 이건 끝나지 않는 거 아닐까 라는 생각을 하게 됐다. 해당 내용을 통해서 메모리 문제 발생 시 바라보는 시선이 조금은 달라질 수 있을 것으로 본다.


브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari