파일 쓰기 시, 메모리 누수 현상 문제.

koreteck의 이미지

안녕하십니까? 오랜만에 글을 쓰네요.

현재 시스템의 로깅을 관리하기 위하여 실시간으로 파일오픈을 하여

디스크에 쓰기를 하고 있습니다.

문제는 파일에 쓰는 시간이 무척 짧아서 발생하는 것 같습니다.

증상은 커널에서 메모리(캐시와 버퍼)를 확보하고 놓아주지 못하고 있습니다.

몇 일 돌리다보면 메모리 부족으로 뻗어버리더군요.

현재 테스트 결과 7ms ~ 20ms 정도로 파일쓰기를 테스트 해보았습니다.

사용한 함수는 fwrite와 write를 사용해 봤습니다. fwrite는 자체적으로

버퍼 관리를 한다고 해서 wirte를 사용하보았지만, fwirte보다 더 빨리 메모리를 차지 하더군요.

fflush와 커널 버퍼를 해제시키는 함수도 사용해 보았지만, 증상이 호전되지 않네요.

물론 시간을 넉넉하게 몇 초에서 몇 십초정도의 간격을 두고 쓰기를 하면 되겠지만,

현재 시스템에서 동작하는 모든 것을 기록해 분석해야 되는 상황이라 난감하네요.

여러가지 테스트를 진행중이지만, 저와 같은 경험을 하신 분들이 있을듯 하여 문의 드립니다.

panda005의 이미지

자체적으로 메모리를 할당해 놓은 후,
일정 사이즈 이상이 되었을 때 디스크에 flushing하면,
write하는 횟수를 줄일 수 있고,
속도도 빨라질 것 같습니다.

예를 들면 10M 정도 메모리를 미리 잡아 놓고,
메모리 꽉 차면 디스크에 쓰는 방식이요 ^^

fwrite도 물론 자체 버퍼링을 합니다만,
디스크 시동시간을 아낄 수 있으니 더 효율적일 듯 합니다.

koreteck의 이미지

^^ 저도 그 방법은 생각 해보았지만, 저는 시스템만 담당하고 있고

실제 알고리즘은 다른 분이 담당하고 있습니다. 가능하면 현재 알고리즘을 유지한체 해볼려구 했는데

안된다면 panda005님 말씀처럼 그 방법을 써야겠네요. ^^ 혹시 다른 분들도 고견이 있으시면 부탁드리겠습니다.

shyblue의 이미지

글쎄요... DISK I/O가 아무리 빨라도, Memory I/O보다 빠를수는 없는데...
제 생각에는, fwrite나 write쪽의 문제가 아니라, 쓰기 위한 데이터를 할당하는 쪽에서의 누수가 아닐까 의심이 됩니다.
efence와 같은 메모리누수 관련 툴이나, C++이라면, 누수 탐지 클래스를 래핑해서 사용하여, 누수 부분을 확인하는것이 먼저가 아닐까 생각합니다.

時日也放聲大哭

時日也放聲大哭

koreteck의 이미지

top 및 메모리 관련 명령, /proc에서 확인한 바로는 실코드에서 메모리가 증가하는 현상은

전혀 없었습니다. 단지 증가하는 것은 커널쪽 메모리 뿐이었구요.

이런 경우도 프로그램상에서 문제가 있을 수 있는건지요?

아래 코드는 실제 사용하고 있는 코드 중, 문제가 되는 부분을 분리하여 테스트하고 있는 코드입니다.

원래는 USB에 로깅을 하려고 하였으나, 문제가 발생하여 하드 쪽으로 옮겨서 테스트를 하였지만 증상이 동일하더군요.

============================================================================================

#include
#include
#include
#include
#include
#include
#include
#include
#include

#define PRINT2(fmt, x...) { \
time_t the_time; \
struct tm *tm_ptr; \
char timestr[100]; \
log_fd2 = fopen(log_filename2, "a+"); \
if(log_fd2 != 0) \
{ \
time(&the_time); \
tm_ptr = localtime(&the_time); \
sprintf(timestr, "%04d-%02d-%02d %02d:%02d:%02d", \
tm_ptr->tm_year+1900, \
tm_ptr->tm_mon+1, \
tm_ptr->tm_mday, \
tm_ptr->tm_hour, \
tm_ptr->tm_min, \
tm_ptr->tm_sec); \
fprintf(log_fd2, "[%s] ", timestr); \
fprintf(log_fd2, fmt, ## x); \
fflush(log_fd2); \
fclose(log_fd2); \
} \
else \
{ \
printf(fmt, ## x); \
} \
}

#define F_CHECK_FLAG F_OK
#define F_CHECK(file) access(file, F_CHECK_FLAG)
#define F_EXIST 0

FILE *log_fd;
FILE *log_fd2;
int synCount=0;
char log_filename1[255];
char log_filename2[255];
char log_start_time[255];
char log_filename3[255];

int main()
{
FILE *fd;
int logid = 0;
int result = 0;

int count_large=0;
int count_small=0;

result = F_CHECK("/data/tmp_setup");
PRINT2("result [%d]\n", result);

if(result == 0)
{
system("mkdir /data/sung");
//system("mount -t vfat /dev/sda1 /mnt/usb");

PRINT2("%s : %d\n", __FILE__, __LINE__);

if((fd = fopen("/data/sung/logid.no", "r")) == NULL)
{
if((fd = fopen("/data/sung/logid.no", "w")) == NULL)
{
PRINT2("/data/sung/logid.no file을 쓰지 못했음\n");
}
else
{
fprintf(fd, "0\n");
fclose(fd);
}
}
else
{
PRINT2("%s : %d\n", __FILE__, __LINE__);
fscanf(fd, "%d", &logid);
PRINT2("usb 저장소에서 log id : %d 를 읽었습니다\n", logid);
fclose(fd);
if((fd = fopen("/data/sung/logid.no", "w")) != NULL)
{
PRINT2("%s : %d\n", __FILE__, __LINE__);
fprintf(fd, "%d", ++logid);
fclose(fd);
}
else
PRINT2("/data/sung/logid.no file open error\n");
}
sprintf(log_filename2, "/data/sung/usblog%05d.log", logid);
PRINT2("this is usblog test message\n");
PRINT2("this is usblog second test message\n");
}
else PRINT2("usb 저장 장치가 발견되지 않았음\n");
// <--- usb 저장소에 log를 저장하기 위한 준비 끝


while(1)
{
usleep(20000);
count_small++;
if(count_small >= 50000)
{
count_large++;
count_small=0;
}
PRINT2("Test Count is %d || %d \n", count_large, count_small);
}
}

panda005의 이미지

이게 실제 소스인가요?

멀티-쓰레드같지는 않은데...
전역 변수에 메모리 버퍼 하나 넉넉하게 잡아놓고,
PRINT2만 버퍼에 쓰도록 바꾸고,
PRINT2에서 일정 사이즈 넘어가면 파일 오픈해서 쓰게끔하면
기본 로직은 하나도 안 건드리고 쉽게 바꿀 수 있지 않을까요?

뭐 멀티쓰레드라서 쓰레드-세이프 해야 된다면 좀 더 복잡해지겠습니다만,
그렇지 않다면 PRINT2만 바꿔서도 얼마든지 처리 가능할 듯 합니다.

koreteck의 이미지

^^ 네 판다님 저도 그래서 현재 테스트 중입니다.

흠... 그런데 지금 1시간 정도 테스트 중이지만, 현재까지는 동일한 문제가 보이는 듯 하네요.

자세한 것은 내일 테스트 후, 결과를 알려드릴게요. ^^ 그럼 안녕히 주무세요.

아 그리고 실 코드는 멀티스레드로 돌아갑니다. 머 그것도 고려를 해야겠지만, 현재 발생하는 문제부터

해결하는 것이 순서인 것 같네요.

익명 사용자의 이미지

FILE *log_fd;
FILE *log_fd2;
모든 thread에서 공용(global)으로 사용하시나요?
usleep은 thread-unsafty한걸로 알고있는데 확인해보세요.
localtime -> localtime_r로 바꾸시고.

lock걸어 주시는것도...

koreteck의 이미지

기존에 로직을 만드시고 테스트 하셨던 분이 메모리 문제일거라고 하셔서 스레드 쪽은 배제하고

진행하고 있었습니다. 그러나 테스트를 해보니, cached 버퍼만을 확보하더군요.

아시겠지만 cached 버퍼를 커널에서는 확보만 하고 요청이 있을 때 반환되지요.

실제 메모리를 풀로 cached 버퍼로 바뀔때가지 테스트를 하여도 문제가 없더군요. ㅡㅡ;

이건 제가 처음 정보를 접했을 때, 그것이 사실이다라고 단정지어서 생긴 문제 같네요.

말씀하신데로 실제 문제는 스레드 세이프 하지 않게 테스트를 한게 문제였던 것 같습니다.

현재 스레드 세이프하게 수정해서 24시간 테스트를 진행하고 있습니다.

추가로 발견되는 사항이 있으면 다른분들도 보실 수 있게 올릴게요. 감사합니다.