Embedded System 의 Stack Size 제한

Posted at 2007. 4. 30. 07:00 // in S/W개발 // by 김윤수


이번에는 C++에 대한 글은 아니고, Embedded System Programming 에 대한 글입니다. 개발자가 아니신 분들이나 Embedded System Programming 을 하지 않는 분들은 별로 관심이 가는 내용이 아닐 것 같네요. 그렇지만 알아두시면 다 피가 되고 살이 되는 내용이니 읽어 보신다고 크게 손해 보는 일은 없을 듯 하네요 ^^;

본격적으로 얘기 보따리를 풀기 전에 예제를 한 번 보도록 하겠습니다. Stack Size에 초점을 맞추어 다음 프로그램이 잘못된 점을 한 번 찾아 보시겠어요 ? 조금은 억지스러운 부분이 있긴 하지만, 문제점을 찾아 보시기 바랍니다.

/* rpr.c */
#include <stdio.h>
#include <stdlib.h>

void recursive_print(int count)
{
  int dummy[10000];                 /* 이게 좀 억지스러운 부분이죠 */

  if (count > 1)                    /* boundary check */
    recursive_print(count - 1);

  printf("#%d print\n", count);
}

int
main(int argc, char * argv[])
{
  int count;

  if (argc != 2)
  {
    printf("Usage: rpr count\n\n");
    exit(-1);
  }

  /* 예제의 간결성을 위해 argv[1] 이 진짜 숫자 형식으로 되어 있는지 체크하는 부분은
     생략합니다 */
  count = atoi(argv[1]);
  recursive_print(count);

  return 0;
}

recursive_print() 안에 dummy 라는 쓸모 없는 테이블이 있는 게 썩 좋은 예제는 아니지만, 제가 설명하려는 문제점에 대한 예를 애써 찾으려다 보니 위와 같은 예제를 들게 됐네요. 위 코드를 다음과 같이 컴파일한 후 실행시켜 보시기 바랍니다.

% gcc -c rpr rpr.c

제 개발 환경인 Windows XP 상의 cygwin 에서는 count 값으로 51을 줬더니 다음과 같은 에러가 발생하네요.

$ ./rpr 51
    599 [main] rpr 5560 _cygtls::handle_exceptions: Error while dumping state (probably corrupted stack)


51 보다 작은 값을 넣었을 때는 멀쩡하게 잘 돌았는데, 51 이라는 값을 넣자 마자 위와 같은 에러가 발생하면서 더 이상 돌지 않게 되네요. 51 이라는 숫자에 차이가 있긴 하지만 적당히 큰 숫자를 넣으시면 저와 비슷한 일이 발생하거나 core dump가 발생할 것입니다. 왜 그럴까요 ? 좀 생각할 시간을 드리기 위해 이번에도...

.


.


.


.


.


.


.


.


.


.


.


.


.


.


.


.


어때요 알아내셨나요 ? 제 글의 제목에서도 눈치 채셨겠지만 바로 Stack Size 제한 때문에 그렇습니다. 제 개발환경 같은 경우 ulimit 명령을 사용해서 확인해 봤더니 다음과 같이 나오네요

$ ulimit -a
core file size          (blocks, -c) unlimited
data seg size           (kbytes, -d) unlimited
file size               (blocks, -f) unlimited
open files                      (-n) 256
pipe size            (512 bytes, -p) 8
stack size              (kbytes, -s) 2034 <-- 이 부분에 주목
cpu time               (seconds, -t) unlimited
max user processes              (-u) 63
virtual memory          (kbytes, -v) 2097152

스택의 크기가 2034KB로 제한되어 있네요. 그럼 계산을 한 번 해볼까요 ? 왜 제 개발환경에서 rpr 프로그램에 51 을 명령행 인자로 주었을 때, 에러가 발생하는지 ?

dummy 테이블의 크기가 10000 이고 integer 의 크기가 4 이고, recursive_print()가 51번 호출됐을테니, 계산해 보면

10000 x 4 x 51 = 2040000 = 2040KB (좀 더 정확히 계산하려면 count 인자, 리턴 address를 위한 Stack 공간 등도 계산해야 합니다. 대세에는 영향을 미치지 않아서 생략!)

이네요. 당연히 recursive_print 51번째 호출에서 stack overflow가 발생했겠네요. 시스템에 따라서는 stack overflow가 발생하면 자동으로 stack size를 키워주는 시스템도 있습니다. 그런 시스템의 경우에는 stack overflow는 virtual memory를 다 쓸 때까지는 발생하지 않습니다. 물론 그런 경우라도 매우 큰 count 값에 대해서는 예를 들어 20000 정도면 8GB 정도 되니 웬만한 시스템에서는 core dump 에러가 발생할 것 같네요.

Embedded System 의 경우는 어떨까요 ? Embedded System의 종류에 따라 약간 달라지긴 하겠지만, 통상 Embedded System 은 일반 PC 나 Workstation과는 달리 여러가지 자원이 매우 제한되어 있습니다. 메모리도 많아야 64MB, 128MB이고 적은 경우는 아직도 1MB가 되지 않는 경우도 있습니다. 이런 시스템에서 Stack Size 제한을 얼마로 줄까요 ? 보통은 64KB 가 디폴트 Stack Size 값입니다. 이런 시스템에서는 rpr 프로그램에 count를 2로만 주어도 비정상 동작을 하게 될 것입니다.

이런 비정상 동작이 제 개발 환경처럼 OS가 virtual memory 를 지원해서 프로세스간의 메모리를 서로 잘 보호해 준다면 차라리 별 문제가 없을 수 있습니다. 그렇지만 Embedded System 용 OS 에서는 virtual memory 니 메모리 보호니 하는 그런 사치스런 개념이 없을 때가 많습니다. OS 와 사용자가 직접 작성한 모든 일반 application 이 다 같은 주소 공간에서 실행되기 때문에 recursive_print() 와 같은 코드가 한 번 잘못 실행돼서 메모리 여기 저기를 건드리면-자기 Stack 영역이 아닌 곳에 계속해서 Stack Frame을 쌓아 가기 때문에-시스템이 영원히 미궁속에 빠지게 됩니다. 소위 시스템이 폭주했다라고 하곤 하죠.

그럼 디폴트 Stack Size를 늘려서 그런 문제를 미연에 방지하면 될 것이 아니냐라고 얘기하고 싶은 분도 있을 것입니다. 그렇지만 Embedded System 은 일반 PC나 Workstation 환경에 비해 매우 제약이 심한 환경이라는 것을 상기해 보시기 바랍니다. 그런 시스템에서 사용되지 않을지도 모르는 Stack 을 각각의 프로세스(Embedded System용 OS 에서 태스크라는 용어를 더 많이 씁니다)에 큰 Stack Size를 할당할까요 ? 십중팔구는 그렇지 않을 것입니다. 그 아까운 자원을 그렇게 낭비할 수는 없지요. 아마 Stack Size 의 사용량을 분석해서 더 줄이려고 하지 않으면 다행입니다.

저도 처음으로 Embedded System Programming을 접했던 것이 98년입니다. 요즘은 거의 쓰지 않지만 VRTX라는 Embedded System 용 OS를 사용했었는데, 어느날 로직에는 아무런 문제가 없는데도 시스템이 한참 잘 돌다가도 어느 순간 뻗어 버리고 뻗어 버리고 그러는 것이었습니다. 정말 몇 날 몇 일을 밤새가며 디버깅하고 또 하고 하면서 거의 자포자기 상태에 빠질 때쯤, 같이 일하시던 선배님-그 선배 별명이 강가딘이었습니다-한 분이 지나가는 말로 "Stack Size 확인해 봤어 ?" 라고 한마디 툭 던지고 가시더군요. 같이 일하던 동료와 저는 눈을 껌뻑껌뻑하며 서로 얼굴을 쳐다 보았죠. 그 때부터 VRTX OS 매뉴얼을 뒤져서 Stack Size를 찾아 보았습니다. 그랬더니 아니나 다를까 t_create()-task create 를 뜻합니다-라는 API의 제일 마지막 인자로 Stack Size를 명시하게 되어 있고, 보통은 디폴트로 64KB를 쓰게 되어 있더군요. 그걸 두 배로 키웠더니 씽씽 잘 돌아가는 모양이라니 참 허탈하더군요. 이거 하나 바꾸면 될 걸 그렇게 고생을 하다니... 너무 억울했습니다. 그리고는 속으로 생각했습니다. "이런 건 진작 가르쳐 줘야 하는 거 아냐 ? 난 내 후배한테 꼭 미리 가르쳐 줘야지" 그땐 그렇게 다짐했던 걸 저도 후배들에게 가르쳐 주지 않고 있다가 이제서야 그 다짐을 지키게 되네요. ^_^

Embedded System Programming 시작하시는 분들은 Embedded System이 일반 다른 환경에 비해서 Stack Size에 제한이 크다는 걸 유념하셔야 합니다. 이런 이유로 recursive algorithm 도 될 수 있으면 일반 loop를 사용하는 방식으로 바꿔야 하고-recursive algorithm 을 일반 loop를 쓰는 방식으로 바꾸는 방법은 전산과 학부때 배운 data structure 에 나옵니다-, stack 에 너무 많은 변수를 할당하는 것도 조심하셔야 하고, call depth를 너무 깊게 가져 가도 좋지 않습니다.

이런 Stack Size 제한은 꼭 Embedded System 이 아니더라도 특정 H/W를 위한 device driver를 개발한다던지 module을 개발한다던지 할 때도 적용되는 내용입니다.

이 글을 읽고 저 같은 실수를 하지 않게 되신다면 제가 글 쓴 보람이 있겠네요. 앞으로도 이 나라의 S/W 개발자들의 정신 건강을 위해 쭉~ 계속해서~ 이런 글을 써 가렵니다. 많이 성원해 주시고, 소프트웨어 관련된 저의 다른 글들도 참고로 읽어 보세요.

소프트웨어는 soft 해야 제 맛이다
Flexible한 S/W 작성하기
소스코드 복사의 위험성
C++ 이야기 첫번째: auto_ptr 템플릿 클래스 소개
C++ 이야기 두번째: auto_ptr의 두 얼굴
C++ 이야기 세번째: new와 delete
C++ 이야기 네번째: boost::shared_ptr 소개
C++ 이야기 다섯번째: 내 객체 복사하지마!
C++ 이야기 여섯번째: 기본기 다지기(bool타입에 관하여)
C++ 이야기 일곱번째: auto_ptr을 표준 컨테이너에 담지 말라