4장. free 명령이 숨기고 있는 것들

📌

메모리는 커널이 제공하는 중요한 리소스 중 하나이다.

CPU가 프로세스의 연산 과정에 필요한 리소스라고 한다면, 메모리는 프로세스가 연산할 수 있는 공간을 제공해주는 리소스이다.

프로세스는 메모리라는 공간에 자신이 필요한 함수를 넣어 두거나 변수에 값을 저장하는 방식으로 연산을 위한 공간을 확보한다.

메모리가 부족하다면 프로세스는 더 이상 연산을 위한 공간을 확보할 수 없고, 이는 결국 시스템 응답 불가 또는 큰 성능 저하를 일으킬 수 있다.

과거 3.13 버전까지의 커널은 free 명령어를 입력하면 2번째 줄(Swap 자리)에 ”-/+ buffers/cache:” 영역을 별도로 제공했었다.

하지만, 정확히 얼마만큼 곧바로 재사용 가능한지 명확하지 않았으며 가용 메모리를 잘못 판단할 여지가 있기 때문에 해당 라인이 제거 되었고, 그 이후 3.14 버전 부터는 available 을 도입하였다.

커널은 블록 디바이스라고 부르는 디스크로부터 데이터를 읽거나 사용자의 데이터를 디스크에 저장한다.

디스크는 기본적으로 매우 느린 장치중 하나로, 디스크에 대한 요청을 기다리는 시간이 상당히 많이 소요 되었고 이로 인한 시스템 부하가 일어나기도 했었다.

그래서, 디스크에 대한 요청을 조금 더 빠르게 처리 하기 위해 메모리의 일부를 디스크 요청에 대한 “캐싱 영역”에 할당해서 사용한다.

즉, 한 번 읽은 디스크의 내용을 메모리에 저장 해두고, 동일한 내용을 읽고자 한다면 디스크로 요청하지 않고 메모리로 요청하게 되는 것이다.

이 때 사용되는 캐싱 영역이 바로 buffers, cache 영역이다.

페이지 캐시 사용 테스트

이 초기 상태에서 48k 용량인 uptime_dump 파일을 읽은 뒤 buff/cache 가 얼만큼 증가 했는지 살펴보자.

실제 cat 명령어를 통해 해당 파일을 읽으면 해당 파일 사이즈 만큼 캐싱 영역에 확보 되는 것이 보인다.

📌

buffers 와 cached 두 영역의 차이점은 무엇일까?

Page Cache와 Buffer Cache 가 사용 되는 과정

커널은 블록 디바이스에서 데이터를 읽을 때 데이터가 위치한 특정 블록의 주소를 넘겨주고, 블록 디바이스는 해당 블록 주소의 데이터를 커널에 전달한다.

커널이 읽어야 할 데이터가 파일의 내용이라면 커널은 bio 구조체를 만들고 해당 구조체에 Page Cache 용도로 할당한 메모리 영역을 연결 해준다.

bio 구조체는 디바이스 드라이버와 통신해서 디스크로부터 데이터를 읽어서 Page Cache에 파일의 내용을 채운다.

파일의 내용이 아닌 파일 시스템을 관리하기 위한 메타 데이터를 읽어올 때는 bio 구조체를 사용하지 않고, _get_blk() 와 같은 내부 함수를 통해 블록 디바이스와 직접 통신한다.

각각이 free 에서 표현하고 있는 cached, buffers 영역이다.

📌

free 명령은 왜 이 두 영역을 제외한 영역을 가용한 영역으로 계산해서 다시 보여줄까?

📌

시간의 흐름에 따른 메모리 사용 영역의 변화

서버의 운영 기간이 길지 않을 땐 아마도 1번과 같은 메모리 사용 현황을 볼 수 있다.

하지만, 시간이 조금 지나면 커널은 가용 영역 중 일부를 Cache 영역으로 사용하게 된다. (2번 바 그래프)

시간이 흐를수록 애플리케이션에서 사용하게 되는 영역이 점점 넓어지게 되는데, 그 이유는 Cache 영역이 충분히 있어야 I/O 성능 향상의 효과를 받을 수 있기 때문이다.

사용 영역이 점점 더 커져서 일정 수준 이상이 되면 커널은 Cache 영역으로 사용하던 영역을 애플리케이션이 사용할 수 있도록 메모리 관리 시스템에 반환한다. (4번)

더 이상 반환할 메모리도 없고 가용 할 메모리가 없는 순간이 발생하게 되면, 시스템은 이 때 부터 swap 이라는 영역을 사용하게 되고 시스템의 성능이 줄어든다.

💡 buffers cached 영역은 시스템의 I/O 성능 향상을 위해서 커널이 사용하는 영역이다.

메모리가 부족한 상황이 되면 커널은 해당 영역을 자동으로 반환하기 때문에 free 명령에서도 해당 영역을 제외한 영역을 실제 사용 가능한 영역으로 계산하게 된다.

📌

free 명령보다 더 자세하게 메모리의 사용 현황을 볼 수 없을까?

free 명령은 시스템이 사용하고 있는 전체 메모리와 앞으로 가용한 메모리의 용량을 볼 수 있지만 각 메모리가 시스템의 어느 부분에 사용 되고 있는지 자세하게 볼 수는 없다. 리눅스에서는 /proc/meminfo 를 통해서 자세한 메모리 현황을 볼 수 있는 방법을 제공한다.

📌

cat /proc/meminfo

  1. SwapCached: swap 으로 빠진 메모리 영역 중 다시 메모리로 돌아온 영역

시스템에 메모리가 부족하면 커널은 프로세스의 주소 공간 중 swap 영역으로 이동시킬 수 있는 메모리를 선택해서 swap 영역으로 이동 시킨다.

이 과정에서 I/O 가 일어나기 때문에 성능 저하가 발생한다.

그 후, 메모리가 다시 확보되어 swap 영역으로 빠졌던 영역이 다시 메모리로 돌아가게 되지만 커널은 swap 영역에서 해당 메모리 내용을 삭제 하지 않는다.

또 다시 메모리 부족 현상이 일어날 경우를 대비하는 것으로, 해당 메모리 영역이 다시 swap 영역으로 내려와야 하는 일이 생기면 이전에 참고한 메모리 영역을 그대로 다시 활용한다.

이를 통해 swap 이 발생하더라도 조금이나마 I/O를 줄일 수 있다.

  1. **Active(anon)**: 특정 파일의 내용을 저장하고 있는 `Page Cache` 영역을 제외한 메모리 영역, 주로 프로세스들이 사용하는 메모리 영역을 지칭할 때 많이 사용된다. **비교적 최근에 메모리 영역이 참조되어** `swap` **영역으로 이동되지 않을 메모리 영역을 의미함**.
  2. **Inactive(anon)**: 2번과 같은 영역을 의미하지만, **비교적 참조된지 오래되어 swap 영역으로 이동될 수 있는 메모리 영역을 의미함**
  3. **Active(file)**: file 로 되어있는 이 영역은 **커널이 I/O 성능 향상을 위해 사용하는 영역**을 의미한다. 위에서 살펴본 **buffers 와 cached 영역이 여기에 속하며**, 비교적 **최근까지 메모리 영역이 참조되어 swap 영역으로 이동되지 않을 메모리 영역**임.
  4. **Inactive(file)**: 4번과 마찬가지로 I/O 성능 향상을 위해 커널이 캐시 목적으로 사용하고 있는 영역, **비교적 참조된지 오래되어 swap 영역으로 이동될 수 있는 메모리 영역**임.
  5. **Dirty**: 4, 5번과 비슷한 용도로 I/O 성능 향상을 위해 커널이 캐시 목적으로 사용하는 영역 중, **쓰기 작업이 이루어져서 실제 블록 디바이스의 블록에 씌어져야 할 영역,**
📌

Active 영역과 Inactive 영역을 구부하는 방법은 어떤 기준으로 구분될까?

커널이 직접 사용하는 메모리 영역을 제외하고 대부분의 메모리는 프로세스가 사용하거나, 캐시 영역으로 사용된다.

anon, file 영역의 메모리는 LRU 기반의 리스트로 관리되고, 이 리스트는 다시 Active, Inactive 두 개의 리스트로 나뉜다.

가장 최근에 참조한 메모리가 Active List 에, 참조 시기가 오래될수록 Inactive 영역으로 이동하고 그 이후 free 영역으로 이동한다.

기본적으로 프로세스가 메모리 할당을 요청하면 해당 메모리의 페이지가 Active 리스트에 연결된다.

그 후 메모리 할당이 실패하거나 메모리가 부족하게 되면 kswaped 혹은 커널 내부에서 try_to_free_pages() 함수를 통해서 LRU 리스트에 있는 메모리들을 확인한다.

이 과정에서 Active 리스트에 있던 페이지가 Inactive 리스트로 옮겨가거나 Inactive 리스트에 있던 페이지가 해제되어 다른 프로세스에게 할당되는 작업이 이루어진다.

10분간 쉬는 이유는 malloc()을 통해 할당 받은 메모리 영역에 아무런 작업을 하지 않으면 과연 Inactive 영역으로 돌아갈 것인가? 를 확인한다.

위 코드를 실행 하면, 10번의 할당이 끝난 후 sleep 을 통해 아무것도 하지 않아도 메모리 영역은 계속 Active에 남아있다.

단순히 시간이 지난다고 해서 Active 메모리가 Inactive로 이동하지 않는다것을 알 수 있다.

“그럼 Active에 있는 페이지는 언제 Inactive로 이동할까?”

메모리 부족 현상이 발생해서 해제해야 할 메모리를 찾아야 하는 순간이 와야 커널은 비로소 LRU 리스트를 살펴보게된다.

시스템에서 유지해야 하는 최소한의 free 메모리 양을 조금 높게 설정 한 뒤 top 을 통해 살펴보면 kswapd가 실행되는 것을 알 수 있다.

kswapd 데몬이 열심히 일을 하면서 Active 영역에 있는 페이지 중 오래된 페이지를 우선적으로 Inactive 로 옮긴 후 메모리를 해제하는 작업을 진행하고 있다.

💡**/proc/meminfo 에서 보이는 Inactive(anon), Inactive(file) 영역의 크기는 각각 영역에서 자주 되지 않은, 누군가 참조한지 오래된 메모리의 양을 의미하며, 메모리가 부족할 경우 반환될 수 있다.**

커널 역시 프로세스의 일종이기 때문에 메모리를 필요로 하며, 조금 특별한 방법으로 메모리를 할당 받아서 사용한다.

/proc/meminfo 내용 중 Slab 으로 표시되는 영역이 바로 커널이 사용하는 영역이다.

  1. Slab: 메모리 영역 중 커널이 직접 사용하는 영역, 이 영역에는 dentry cache, inode cahce 등 커널이 사용하는 메모리가 포함 됨
  2. SReclaimable: Slab 영역 중 재사용될 수 있는 영역, 캐시 용도로 사용하는 메모리들이 주로 여기에 포함 됨
  1. SUnreclaim: Slab 영역 중 재사용될 수 없는 영역, 커널이 현재 사용중인 영역으로 해제해서 다른 용도로 사용할 수 없음
📌

Slab 은 어떤 영역이길래 다른 영역과 구분되는걸까?

  • slabtop -o 를 통해 Slab의 정보를 살펴볼 수 있음

모든 프로세스는 작업을 하기 위해 메모리가 필요하고 이는 커널도 예외가 아니다.

I/O 작업을 조금이라도 더 빠르게 하기 위해 inode cache, dentry cache 등을 사용하거나, 네트워크 소켓을 위한 메모리 영역을 확보하거나 하는 작업들은 커널이 하게 되는데 이 과정이 메모리가 필요한 과정이다.

메모리를 할당해 주는 버디 시스템은 4KB 의 페이지 단위로 메모리를 할당하는데, 사실 커널 입장에서는 이렇게 큰 영역을 할당 받을 필요가 없다.

영역이 크다면 실제 사용하는 영역과 할당 받은 영역의 차이가 커지면서 메모리 단편화 현상도 발생할 수 있기 때문이다.

이와 같은 이유로 커널이 사용하려는 메모리 영역은 좀 더 작고 효율적으로 사용할 수 있어야 한다. 이를 충족 시키기 위해 Slab 할당자를 통해서 원하는 메모리 영역을 확보한다.

📌

slab 할당자의 메모리 사용 개념도

Slab 영역 중 가장 많이 사용되는 캐시가 dentry 와 inode_cache 이다.

만약 파일에 자주 접근하고 디렉터리의 생성/삭제가 빈번한 시스템이 있다면 Slab 메모리가 높아질 수 있으며, 그 중에서도 위 언급 되었던 dentry, inode_cache가 높아질 수 있다.

또한, Slab 할당자는 free 명령에서 used 로 계산된다.

⇒ 커널이 사용하는 영역이기 때문에 buffers/cached 영역에 포함될 것 같지만, used 영역으로 계산된다.

“만약, free 명령에서 메모리 사용률이 비교적 높지만 프로세스들의 메모리 사용량은 현저히 낮을때”

어디서 메모리를 사용하고 있는지 정확히 파악되지 않는 상황에서 /proc/meminfo 를 확인해보면 의외로 Slab 메모리 영역이 나머지를 채우고 있을 수 있다.

Slab 영역이 지나치게 많이 차지하고 있다면 slabtop 명령을 통해 어떤 캐시 메모리에서 많이 쓰이고 있는지 확인하고 그에 걸맞는 조치를 취해야한다.

일련의 예로, 흔히 디렉터리 계층 관계를 저장 해두는 dentry 캐시에 메모리 사용률이 증가했다고 가정해보자.

이 때 캐시 영역 강제 플러싱 할 수 있는데, 이는 drop caches 를 이용하여 처리한다.

dentry cache 를 날리기 위해 echo 2를 이용하지만, 3을 이용하면 page cache 까지 모두 플러싱할 수 있다.

하지만 불필요한 작업으로 CPU 자원을 낭비 할 필요가 없기 때문에 echo 2 로 조치를 취한다.

  1. free 명령으로 볼 수 있는 buff 는 파일 시스템의 메타 데이터 등을 저장하고 있는 블록 디바이스의 블록을 위한 캐시이다.
  2. free 명령으로 볼 수 있는 cache 는 I/O 작업의 효율성을 위해 한 번 읽은 파일의 내용을 저장하는 데 사용하는 캐시 영역이다.
  3. buff/cache 는 미사용 중인 메모리 영역을 시스템의 효율성을 위해 커널이 사용하는 용도이며, 프로세스가 필요할 때 언제든 해당 영역을 해제하여 프로세스에게 전달해준다.
  4. /proc/meminfo 에서 보이는 anon 영역은 프로세스에서 사용하는 영역이고, file 영역은 I/O 성능 향상을 위해 커널이 캐싱 용도로 사용하는 영역이다.
  5. annon 과 file 영역은 각각 Active, Inactive 라 불리는 LRU List 를 통해 관리되고 있으며 각각은 시스템에 해당 메모리 영역에 얼마나 최근에 접근했는지를 기준으로 관리된다.
  6. Slab 영역은 커널이 사용하는 캐싱 영역을 의미하며 dentry cache, inode_cahce, buffer_head 등 다양한 캐싱 용도로 사용된다.

참고 자료

medium.com
페이지 캐시와 버퍼 캐시, 그리고 리눅스 파일 시스템
brunch.co.kr
리눅스의 페이지 캐시와 버퍼 캐시