why ps is wrong?

오늘 kldp 에 ps로 메모리를 확인하려고 하는데 잘 안된다는 질문 내용을 봤다.

https://kldp.org/node/152025

답글을 작성하다가 재미있는 내용을 알게 되어 이곳에도 같이 담아둔다.

 

질문 내용

프로그램 안에서 10 바이트씩 malloc 으로 메모리를 할당했습니다.
그리고 ps -eo user,size,cmd 명령으로 메모리 증가량으로 확인하려고 했지만 전혀 메모리 증가가 안되네요.

왜 그런거죠?

답변

두 가지 이유가 있습니다.

1. 확인하고자 하시는 메모리 증가량이 너무 작습니다.

man ps 에서 size 항목을 찾아봤는데 따로 단위는 안나오네요.
하지만 테스트 해보니 단위가 1K 였습니다.
프로그램을 수정해서 한번에 1M 씩 증가하도록 해봤는데, 증가량이 확인되네요.
할당하는 메모리 크기를 1M 단위로 하신다면 쉽게 증가 내역을 확인하실 수 있습니다.

2. ps 명령은 정확한 메모리 량을 확인하기에는 부족한 유틸리티 입니다.
출처 : http://stackoverflow.com/questions/131303/how-to-measure-actual-memory-u…

ps 명령은 사실 어플리케이션에서 사용하는 정확한 메모리 양을 나타내지 않습니다. 단지 예약된 메모리 양을 나타낼 뿐입니다.
달리 말하면 때에 따라(커널 레벨에서 사용되는 페이지가 공유 되거나 할 경우)변동될 소지가 있다는 것입니다. (예를 들면 여러개의 쓰레드나 동적 라이브러리를 사용하는 경우가 있습니다.)

그리고 만약 정확한 메모리 크기를 확인하고자 하신다면 다른 프로그램을 사용하셔야 합니다. valgrind 가 대표적이죠. 주로 메모리 누수 탐지에 사용되지만 메모리 사용량을 확인할 수도 있습니다.

————————————————————————————

실제로 위에 인용한 스택 오버플로우의 내용 말고도 ps 는 메모리 사용량을 확인하는데 부족하다는 내용의 많은 양의 문서를 확인할 수 있었다.

정확한 메모리 사용량을 확인하고자 한다면, 다음의 링크에 소개된 프로그램을 이용하자.

http://www.binarytides.com/linux-command-check-memory-usage/

Libevent event driven memory leak

진행중인 프로젝트에서 Libevent 를 사용중인데.. valgrind 메모리 누수 확인시 자꾸 메모리 누수가 발생하는 것을 확인했다.

어디가 문제일까… 기능상으로는 문제가 없었다.
malloc/calloc 할당한 부분도 문제가 없었다.
valgrind 가 지목한 문제 발생한 부분은 event_new() 부분이었다.

문제 발생 원인은 잘못된 libevent 사용에 있었다.
나는 다음과 같이 프로그램을 개발했었다.

/*
* main.c
*
* Copyright 2014 Sungtae Kim <pchero21@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <event2/event.h>

typedef struct _wctx
{
struct event_base*  w_base;     // main base
} wctx;

static void cb_main_listen(__attribute__ ((__unused__)) int fd, __attribute__ ((__unused__)) short event, void *arg);
static void cb_process(__attribute__ ((__unused__)) int fd, __attribute__ ((__unused__)) short event, void *arg);
static void cb_process_detail(__attribute__ ((__unused__)) int fd, __attribute__ ((__unused__)) short event, void *arg);

int main(int argc, char **argv)
{

wctx* work = calloc(1, sizeof(wctx));

work->w_base = event_base_new();
struct event* main_listen = event_new(work->w_base, STDIN_FILENO, EV_READ, cb_main_listen, work);
event_add(main_listen, NULL);

event_base_dispatch(work->w_base);

printf(“System terminate…n”);
event_free(main_listen);
event_base_free(work->w_base);

return 0;
}

static void cb_main_listen(__attribute__ ((__unused__)) int fd, __attribute__ ((__unused__)) short event, void *arg)
{
wctx* work = (wctx*)arg;
struct event* w_event = evtimer_new(work->w_base, cb_process, work);

struct timeval  tVal;
tVal.tv_sec = 0;
tVal.tv_usec = 0;
evtimer_add(w_event, &tVal);
}

static void cb_process(__attribute__ ((__unused__)) int fd, __attribute__ ((__unused__)) short event, void *arg)
{
printf(“Process!!n”);
wctx* work = (wctx*)arg;
struct event* w_event = evtimer_new(work->w_base, cb_process_detail, work);

struct timeval  tVal;
tVal.tv_sec = 0;
tVal.tv_usec = 0;
evtimer_add(w_event, &tVal);

}

static void cb_process_detail(__attribute__ ((__unused__)) int fd, __attribute__ ((__unused__)) short event, void *arg)
{
printf(“Detail Process!!n”);
}

 

아래는 valgrind 로 메모리 누수를 확인한 내용이다.

pchero@MyGalaxy:~/workspace/Study/Program/LibEvent/memory_leak$ echo “Hello” | valgrind –leak-check=full ./main
==2222== Memcheck, a memory error detector
==2222== Copyright (C) 2002-2012, and GNU GPL’d, by Julian Seward et al.
==2222== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==2222== Command: ./main
==2222==
Process!!
Detail Process!!
System terminate…
==2222==
==2222== HEAP SUMMARY:
==2222==     in use at exit: 280 bytes in 3 blocks
==2222==   total heap usage: 12 allocs, 9 frees, 1,848 bytes allocated
==2222==
==2222== 136 bytes in 1 blocks are definitely lost in loss record 2 of 3
==2222==    at 0x4C2A2DB: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2222==    by 0x4E43A46: event_new (in /usr/lib/x86_64-linux-gnu/libevent-2.0.so.5.1.9)
==2222==    by 0x400A16: cb_process (main.c:73)
==2222==    by 0x4E41F93: event_base_loop (in /usr/lib/x86_64-linux-gnu/libevent-2.0.so.5.1.9)
==2222==    by 0x400933: main (main.c:49)
==2222==
==2222== 144 (136 direct, 8 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 3
==2222==    at 0x4C2A2DB: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2222==    by 0x4E43A46: event_new (in /usr/lib/x86_64-linux-gnu/libevent-2.0.so.5.1.9)
==2222==    by 0x4009A1: cb_main_listen (main.c:61)
==2222==    by 0x4E41F93: event_base_loop (in /usr/lib/x86_64-linux-gnu/libevent-2.0.so.5.1.9)
==2222==    by 0x400933: main (main.c:49)
==2222==
==2222== LEAK SUMMARY:
==2222==    definitely lost: 272 bytes in 2 blocks
==2222==    indirectly lost: 8 bytes in 1 blocks
==2222==      possibly lost: 0 bytes in 0 blocks
==2222==    still reachable: 0 bytes in 0 blocks
==2222==         suppressed: 0 bytes in 0 blocks
==2222==
==2222== For counts of detected and suppressed errors, rerun with: -v
==2222== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 2 from 2)

메모리 누수가 확인되는 지점이 두 곳이 보인다. 모두 event_new 를 했던 곳이다. 내용인즉, event_new() 를 통해 생성한 event를 따로 정상 삭제하지 않았다는 내용이다.
나는 timer 에 등록할 경우, 시간이 되어 함수가 작동할 경우, libevent 에서 자동으로 메모리를 해제해 주는 줄 알았다.
하지만 아니었다…

결국, 소스를 다음과 같이 수정하였다.

/*
* main.c
*
* Copyright 2014 Sungtae Kim <pchero21@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <event2/event.h>

typedef struct _wctx
{
struct event_base*  w_base;     // main base
struct event*       w_event;    // work event
} wctx;

static void cb_main_listen(__attribute__ ((__unused__)) int fd, __attribute__ ((__unused__)) short event, void *arg);
static void cb_process(__attribute__ ((__unused__)) int fd, __attribute__ ((__unused__)) short event, void *arg);
static void cb_process_detail(__attribute__ ((__unused__)) int fd, __attribute__ ((__unused__)) short event, void *arg);

int main(int argc, char **argv)
{

wctx* work = calloc(1, sizeof(wctx));

work->w_base = event_base_new();
struct event* main_listen = event_new(work->w_base, STDIN_FILENO, EV_READ, cb_main_listen, work);
event_add(main_listen, NULL);

event_base_dispatch(work->w_base);

printf(“System terminate…n”);
event_free(main_listen);
event_base_free(work->w_base);
free(work);

return 0;
}

static void cb_main_listen(__attribute__ ((__unused__)) int fd, __attribute__ ((__unused__)) short event, void *arg)
{
wctx* work = (wctx*)arg;
//    struct event* w_event = evtimer_new(work->w_base, cb_process, work);
work->w_event = evtimer_new(work->w_base, cb_process, work);

struct timeval  tVal;
tVal.tv_sec = 0;
tVal.tv_usec = 0;
//    evtimer_add(w_event, &tVal);
evtimer_add(work->w_event, &tVal);
}

static void cb_process(__attribute__ ((__unused__)) int fd, __attribute__ ((__unused__)) short event, void *arg)
{
printf(“Process!!n”);
wctx* work = (wctx*)arg;
//    struct event* w_event = evtimer_new(work->w_base, cb_process_detail, work);
evtimer_assign(work->w_event, work->w_base, cb_process_detail, work);

struct timeval  tVal;
tVal.tv_sec = 0;
tVal.tv_usec = 0;
//    evtimer_add(w_event, &tVal);
evtimer_add(work->w_event, &tVal);
}

static void cb_process_detail(__attribute__ ((__unused__)) int fd, __attribute__ ((__unused__)) short event, void *arg)
{
printf(“Detail Process!!n”);
wctx* work = (wctx*)arg;
event_free(work->w_event);
}

소스 수정 후, 다시 valgrind 를 돌려보았다.

pchero@MyGalaxy:~/workspace/Study/Program/LibEvent/memory_leak$ echo “Hello” | valgrind –leak-check=full ./main
==2440== Memcheck, a memory error detector
==2440== Copyright (C) 2002-2012, and GNU GPL’d, by Julian Seward et al.
==2440== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==2440== Command: ./main
==2440==
Process!!
Detail Process!!
System terminate…
==2440==
==2440== HEAP SUMMARY:
==2440==     in use at exit: 0 bytes in 0 blocks
==2440==   total heap usage: 11 allocs, 11 frees, 1,720 bytes allocated
==2440==
==2440== All heap blocks were freed — no leaks are possible
==2440==
==2440== For counts of detected and suppressed errors, rerun with: -v
==2440== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

결과는 성공!

문제 해결의 포인트는 다음과 같았다.

1. event_new()/evtimer_new() 의 사용을 줄이는 것. 가능하면 event_assign()/evtimer_assign() 을 사용하도록 했다.
2. new() 가 있으면 free() 도 해 준다는 것.