기기

RHEL7의 malloc 본문

기술

RHEL7의 malloc

notEmpty 2021. 1. 3. 22:15

 

 

만약 glibc malloc 메커니즘을 찾다가 오신 분들이라면 아래 링크를 꼭! 보신 후에 이 글을 참고로 봐주시면 좋겠습니다.

모든 내용을 다루기에는 너무 많아져서 어려웠던 부분과 느낀점을 위주로 정리하였습니다. 

중간중간 arena와 같은 내용은 아래 링크에서 더 쉽게 확인가능합니다. 

sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/

 

Understanding glibc malloc

I always got fascinated by heap memory. Questions such as How heap memory is obtained from kernel? How efficiently memory is managed? Is it managed by kernel or by library or by application itself?…

sploitfun.wordpress.com

 

 

 

glibc 2.17 malloc을 주제로 정해진 계기

glibc 2.17이 발표 주제로 정해지게 된 계기는 회사에서 프로젝트 OS인 RHEL을 7버전으로 업그레이드하며 메모리 사용량이 증가한 현상때문이다. 시니어 분들께서 이미 원인을 파악하시고 환경설정값(MALLOC_ARENA_MAX)을 바꾸며 테스트하고 계셨다. 버전업을 하며 glibc 2.17의 메모리 할당자인 ptmalloc2가 malloc 호출 시 더 많은 메모리를 사용하고 있었다. 

 

우선 glibc란?

glibc는 C 표준 라이브러리를 구현한 것으로 C++도 지원한다. 제공하는 대표적인 함수는 malloc과 free 등등이 있다. 

특히 2.17버전은 ptmalloc2가 멀티 스레딩 시 더 향상된 성능을 지원하기위해 메모리 사용량이 증가하게 된다. 

 

RHEL6의 glibc 2.12는 multiple-arena 를 제공하여 다중 힙이 존재하는 것처럼 작동한다. RHEL6에서 멀티 스레드는 여러 힙을 사용하는 것처럼 작동하였지만 동시 사용은 불가능했다. 하지만 glibc 2.15는 향상된 메모리 할당자로 멀티 스레딩 프로그램에서 동시에 여러 arena(힙) 접근이 가능해졌다. 원래 glibc 2.12의 multiple-arena에서도 메모리 증가 현상이 있었지만 glibc 2.17에서는 더 증가하게 되었다.

 

하지만 glibc의 메모리 할당자는 범용성을 기반으로 하였다. 그래서 다양한 환경설정 값을 바꿔서 입맛에 맞게 수정할 수 있다. 특히 많은 사람들이 MALLOC_ARENA_MAX 값을 바꿔서 메모리 사용량을 조절하고 있었다. 여기서 모두 다루기에는 내용이 많기 때문에 MALLOC_ARENA_MAX와 arena를 모른다면 구글링해서 더 잘 정리된 내용을 참고바랍니다. 

 

그래서 일단! 첫 임무로 malloc 메커니즘을 정리하고, 실제로 멀티 프로그래밍 코드를 작성해서 테스트하여 적당한 MALLOC_ARENA_MAX를 찾는데 도움을 드리고자 하였다. 

 

 

glibc 2.17에서 메모리 사용량이 많아진 원인

sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/ 

일단 glibc 의 malloc 검색 시 대부분의 자료는 위 링크를 기반으로 하고 있다. 나 역시도 해당 링크를 제일 먼저 참고했다. 그림이랑 예시로 자세한 설명을 하고 있긴하지만 glibc 2.17버전업을 하며 메모리가 증가한 원인을 파악하기에는 역시 부족하다

 

그래서 이 글을 glibc2.17배경에서 보게 될 분들은 링크의 Multiple Arena 예시을 다음 설명과 같이 보시면 좋을 것 같습니다.

예시에서는 스레드 4개가 '거의' 동시에 malloc 요청을 하게 된 것이다. '거의' 동시에 메모리를 요청하게되는데 아주 약간의 차이로 어떤 스레드는 메모리를 할당받는 중에 다른 스레드들이 추가로 malloc을 요청하게 되는 과정이다. 즉 아직 메모리를 unlock하지 않았는데 요청을 받은 상황. glibc2.12는 동시 요청이 불가능하지만 glibc2.17은 가능하다. aren 단위로 lock 하기 때문이다. 

 

헷갈렸던 부분이 arena수가 MALLOC_ARENA_MAX 보다 작다고 무조건 arena를 생성하는 것이 아니었다. (glibc 버전마다 다를 것이라 위 내용에서는 더 깊히 다루지 않았던 걸 수 있다) 사용할 수 있는 arena가 없고 MALLOC_ARENA_MAX 보다 현재 생성한 arena 수가 작을때 arena를 새로 생성한다. 그게 아니라면 메모리를 요청하는 스레드는 기존의 arena를 재사용하거나, aren를 lock못하니 block되어 다른 스레드가 arena를 unlock할때까지 대기하게 된다. 

그래서 수 많은 스레드가 malloc을 요청하게 되면 다양한 arena로부터 malloc 요청을 하게된다. 그럼 하나의 스레드가 사용하는 메모리 영역은 여러 arena에 퍼져있게 된다. 

 

즉 여기서 더 많은 파편화 문제(메모리 낭비)가 발생할 수 있다. glibc에서 arena들은 서로 독립적으로 각자 메모리를 관리하게 되는데 그때 free 메모리 영역을 재사용하거나 입접한 free 영역들을 하나로 합친다. 그런데 arena 여기저기에서 메모리를 할당받은 스레드가 종료되었다면 메모리가 인접하기 어렵고, 혹시라도 인접해도 arena는 독릭접이라 하나의 free 영역으로 합쳐지기 어렵게 되는 것이다. 

 

또한 스레드는 arena를 재사용하려고할때 직전에 malloc 요청 시 사용한 arena를 사용하려고 한다. 이게 실패하면 다른 arena를 lock 하기 위해 시도하게 된다. 그런데 프로젝트에서는 스레드가 128개나 되어 스레드가 많아질수록 직전에 사용한 arena를 못잡을 확률이 커지게 되고 메모리 파편화가 더 발생하게 된다고 추측한다. ( 추측으로 참고로 봐주시기 바랍니다. ) 

 

하지만 그렇다고 MALLOC_ARENA_MAX값을 128 이상으로 잡는 것도 비효율적이다. 실제 MALLOC_ARENA_MAX 값을 1, 10, 20, 30, 40, 50, 60, ,, 다양하게 테스트한 결과 MALLOC_ARENA_MAX 값이 작을 수록 메모리를 적게 사용했다. 특이한 것은 약 10이상부터는 메모리 사용량은 비슷비슷했다.

 

테스트 환경

glibc2.17에서 8코어 64비트(디폴트 max arena 64)환경에서 멀티스레드 128개, 그리고 각 스레드에서 기본 1MB 여러 번 나눠 할당받고, 50기가를 500kb로 나누어 malloc과 free를 반복했다. 한 번 테스트시 약 10분 정도 시간소요 
MALLOC_ARENA_MAX 값 별로 10번 수행하여 평균내었었다 

 

즉 메모리를 더 줄이고자한다면 1단위로 작게 줄여야한다는 것을 알 수 있었다. 실제로 C를 사용하는 하둡에서도 MALLOC_ARENA_MAX를 4로 제한을 둔 경우도 있었다. 

성능 측면에서는 MALLOC_ARENA_MAX 값이 높을수록 동일한 작업을 더 빠르게 처리하였다. 그래프로 출력했을때 깔끔한 모양이 나와서 만족스러웠다 ㅎㅎ 

 

이 결론까지 오는데 저 자료뿐만 아니라 glibc2.17코드와 다양한 자료를 참고해야했다ㅜ

또한 이 설명이 틀릴 수 있기 때문에 궁금한 점이 있으시다면 댓글로 달아주시면 답변드리겠습니다. 

 

 

MALLOC_ARENA_MAX 설정 값 결론.. 

버전업으로 메모리를 아끼고자 한다면 MALLOC_ARENA_MAX을 매우 작게 바꾸는 것을 추천한다. 어중간한 값은 설정하나마나인것같다. 

그리고 MALLOC_ARENA_MAX 말고도 ptmalloc2는 다양한 설정가능한 옵션을 제공한다. 예를들어 arena를 lock한 스레드가 큰 메모리를 요청하게 되면 brk시스템 콜이 아닌 mmap 시스템 콜을 통해서 메모리를 os로부터 할당받는다. 시스템 콜이기 때문에 시간이 걸리지만 free를 하게되면 메모리 할당자에게 주지 않고 바로 OS로 반환하여 메모리 누수를 방지할 수 있다. 즉 mmap 시스템 콜을 호출하는 기준을 작게 수정하여 더 자주 mmap을 호출하여 메모리 누수를 방지할 수 있을 것 같다. 

하지만 7버전업을 할때 해당 기준은 변경이 없기 때문에 따로 수정은 없었다. 

 

나는 spring 백엔드를 하고 싶기도해서 c 메모리 할당자를 준비하는 것이 어떤 장점이 있을지 몰랐다. 하지만 glibc는 단순히 c 로 작성된 프로그램 뿐만 아니라, 자바 등 여러 프로그램에도 영향을 끼칠 수 있었다. 자바의 native memory 영역이 있으며, 하둡 등 다양한 프로그램에서도 환경 변수를 조절하는 방법으로 메모리를 아끼기 위한  방법 중 하나로 사용하고 있었다. 

 

 

그런데 왜 glibc 이렇게 메모리를 많이 사용하게 되었을까

하드웨어가 발전하며 비슷한 가격에 램의 크기도 커졌다. 이걸 고려해서 glibc 개발자들은 램 사용량이 증가하여도 성능을 선택하게 되었다고 생각한다. 하지만 glibc는 다른 할당자들과 다르게 범용성있는 glibc라는 것이 코드 주석에 명시적으로 적혀있다.

즉 무조건 성능을 올리기보다는 환경변수를 이용하여 성능이 다소 줄더라도 메모리를 아낄 수 있도록 옵션을 제공하고 있다. 

 

마찬가지로 jvm도 메모리 영역별로 설정가능한 방법이 있다. 성능과 메모리 사이에서 프로젝트에 맞는 적절한 환경설정이 필요할 것이다. 

이 부분도 나중에 시간내서 정리가 필요할 것이다. 

 

또한 더 깊이 파고들기 위해서는 다른 사이트나 glibc malloc.c/arena.c 코드에서 추가적인 공부가 필요한다. 여러 블로그를 봤지만 버전마다 은근히 코드가 달라서 직접 버전에 맞는 코드를 확인하는 것이 좋을 것 같다. 

 

 

혹시 더 높은 버전에서 제공하는 tcache와 추가적인 시스템 콜 설명이 필요하다면 아래 링크를 참고해도 좋을 것 같다. 

sourceware.org/glibc/wiki/MallocInternals

 

MallocInternals - glibc wiki

One Heap to malloc them all, One Heap to free them, One Heap to coalesce, and in the memory bind them... Overview of Malloc The GNU C library's (glibc's) malloc library contains a handful of functions that manage allocated memory in the application's addre

sourceware.org

 

 

glibc malloc 추가 개념 이해해 많은 도움 받었던 툴 및 명령어 

1) gdb!! 

gdb로 메모리를 분석해봤다! 하지만 centOS에 glibc 2.17에서 malloc 테스트가 필요했는데 gdb설치가 잘 안됐다. 결국은 시간들여서 gdb peda까지 그리고 pwnable까지는 설치했다. 

다음은 테스트하면서 자주 사용했던 gdb 명령어들이다. 

  • call malloc_stats()
    아레나별 메모리 사용량 (malloc_stats는 arena의 헤더 역할을 한다.)
  • p main_arena
    메인 아레나 상태 
  • p narenas  
    현재 생성된 아레나 수 
  • heapinfo
  • heapinfoall 
  • b 라인넘버
    디버깅 용 
  • 등등 

 

아래 블로그를 참고하면 실제 사용 예시와 메모리에 대해서 쉽게 배울 수 있다. 

wogh8732.tistory.com/178?category=699165

 

Heap 기초1

Heap 기초1 Date @Mar 02, 2020 Person jaeho jung 목차 1. 데이터를 저장하는 여러 방법들 1.1 전역변수로 할당된 데이터 1.2 스택에 할당된 데이터 1.3 동적으로 힙에 할당된 데이터 2. Heap 기초 2.1 Dynamic m..

wogh8732.tistory.com

rninche01.tistory.com/entry/heap3-glibc-malloc2-feat-chunk?category=838537

 

heap(3) - glibc malloc(2) (feat. chunk)

참고 : GNU C Library의 Memory Allocator인 ptmalloc2(glibc 2.23)를 대상으로 설명 지금부터는 실제로 메모리 할당 및 해제되는 공간인 청크에 관한 이야기를 시작하도록 하겠다. 중간중간에 bins에 대한 내용

rninche01.tistory.com

 

 

 

 

glibc malloc의 소스코드 분석에 도움받었던 블로그 

테스트가 필요한 버전은 glibc 2.17이었기 때문에 소스코드가 다른 부분이 있었지만 간단히 코드를 분석하는데 많은 도움을 받았다. 

 

chp747.tistory.com/251

 

malloc.c 상세 분석일지 1 (glibc-2.25)

malloc.c 상세 분석일지 1 (glibc-2.25) heap 공부는 malloc 동작 분석부터 하는게 맞는거 같다. malloc 관련해서 예전에 정리해놓은 걸 좀 더 다듬어 봤다. heap 공부를 하는 누군가에게(나를 포함한) 도움이

chp747.tistory.com

kimvabel.tistory.com/88

 

[glibc 2.23] malloc.c 분석

요 며칠동안 plaiddb 문제 풀다 멘붕와서, 잠깐 머리 식힐 겸 미뤄뒀던 malloc 분석을 할 겸 glibc 2.23의 malloc.c 소스를 분석해서 포스팅 해볼까 합니다. source : https://github.com/andigena/glibc-2..

kimvabel.tistory.com

 

 

 

마지막으로 MALLOC_ARENA_MAX를 테스트하기 전에 참고했던 블로그

아래 링크를 보고 테스트까지 했었다. 

 

참고로 MALLOC_ARENA_MAX 설정은 다음과 같이 설정할 수 있다. 

  • export MALLOC_ARENA_MAX=아레나 수 

실행시간은 리눅스 time 명령어로 테스트하였다. 때문에 실제 시간과 user mode에서의 실행 시간, kernel mode에서의 실행시간까지 편하게 확인할 수 있었다. 

 

 

 

마지막으로 다른 MALLOC_ARENA_MAX  정리글로 참고바랍니다. 

github.com/prestodb/presto/issues/8993

 

Consider lowering MALLOC_ARENA_MAX to prevent native memory OOM · Issue #8993 · prestodb/presto

Yes, we leak native memory When compressing/decompressing gziped tables with rcfile writers, we use java native zlib inflaters and deflaters which allocate system native memory. There is an ongoing...

github.com

 

'기술' 카테고리의 다른 글

Spring Boot의 Java 버전 변경하기 (maven, Intellij)  (0) 2024.05.08
Maven을 이용한 웹 어플리케이션 생성 및 설정  (0) 2022.07.23
Maven이란?  (0) 2021.01.08
Unicode와 encoding  (0) 2020.09.09