티스토리 뷰
이번 글에서는 메모리 자원을 효율적으로 사용하기 위한 방법중 하나인 heap 메모리에 대해서 설명하도록 하겠다.
이해를 돕기 위하여 다음과 같은 상황을 상상해 보도록 한다.
서울역에서 출발하여 부산역까지 가는 기차가 있다. 이 기차에는 앉을수 있는 위치가 미리 정해져 있는 지정좌석제와 기차에 타는 순서대로 차례대로 앉게 되는 자유좌석제가 있다. 지정좌석제에는 기차가 출발하는 서울역에서부터 이용하는 승객도 있고, 중간 경유지에서 이용하는 승객이 있을 수 있다. 어쨋든 어디서부터 타든지 정확한 자기 좌석이 정해져 있으므로 비록 자리가 비어 있더라도 다른 승객이 이용할 수는 없게 된다. 자유좌석제를 이용하는 승객들은 아무자리에 가서 앉을 수 없고 맨 뒤칸 객차의 뒤쪽에서부터 차례대로 앉을 수 있다. 자유좌석제에 있는 어떤 승객이 기차에서 내리려면 나중에 탔던 승객들 순서대로 내린 후 다시 원래 순서대로 뒤쪽에서부터 자리를 채워나가게 된다. 기차에서 내렸다가 다시 타게되면 이전에 앉아 있던 자리에 다시 앉게 되지 않고 다른 자리에 앉게 될 수도 있다.
이를 그림으로 표현하면 다음과 같게 된다.
서울역에서 기차가 출발할때부터 이미 자리에 앉아서 가는 승객들이 있는 영역은 초기값을 가지고 있는 data section이라고 부른다. 일반적으로 RAM 영역의 시작주소에 할당한다.
중간 경유지에서 타는 승객들이 들어가는 영역은 bss section에 해당된다고 보면 된다. 이 영역은 모두 0으로 초기화 시켜 주어야 한다.
마지막으로 지정된 위치가 정해져 있지 않고 그때 그때 상황에 맞게 임시로 앉을 수 있는 stack memory 영역이 있다. Stack 영역은 높은 주소에서부터 낮은 순서대로 채워나가게 된다.
위에서 얘기한것처럼 지정 좌석제는 비록 좌석이 비어 있더라도 다른 승객들이 앉을 수 없다. 예를 들어 기차가 서울역에서 출발하기 전에, 이미 천안에서 대전역까지 이용하는 승객이 기차 전체 좌석의 1/2을 예약했다고 가정하면 서울에서 천안까지, 또, 대전 이후부터 부산까지는 빈 좌석으로 운행할 수 밖에 없다.
뭔가 상당히 비효율적이라고 생각할 것이다. 이런 비효율을 개선하기 위하여 기차가 서울역에서 출발한 이후에라도 지정 좌석제를 원하는 승객이 있다면 빈자리에 할당해 준 다음 그 승객이 다음역에서 내리게 되면 다른 승객을 그 자리에 할당을 하면 될것이다.
그렇다면 자유좌석제와 별 다른게 없어 보일것 같지만 실제로는 전혀 다른 얘기이다. 비록 자리를 할당받는 것은 임의의 빈자리가 되는건 맞지만, 목적지에서 내릴때까지는 그 자리를 독점할 수 있다는 차이점이 있다.
이러한 역할을 제공하기 위하여 stack memory와 구분되는 별도의 공간을 마련해 두어야 하는데 이를 heap 메모리 영역이라고 부른다.
Heap 메모리 영역은 아래 그림처럼 두가지 방법의 형태로 할당할 수 있다.
왼쪽 그림은 heap 메모리 영역이 bss와 stack영역 사이에 위치하고 있다. Heap은 stack 방향으로 나아가고 stack은 heap 방향으로 나아가게 된다. 만약 heap 메모리 주소가 stack 메모리 영역까지 도달하게 되면 더이상 메모리를 할당할 수 없게 된다.
그러나 왼쪽의 경우 메모리를 빈틈없이 최대한 사용할 수 있으므로 가장 효율적이라고 볼수 있다.
오른쪽 그림은 heap과 stack 메모리 크기를 미리 정해 놓아서 그 크기 안에서만 동작될 수 있도록 만들었다. 그림에서는 stack메모리와 heap 영역이 붙어 있는것으로 표현되었지만 필요에 따라 heap 영역을 따로 분리 시킬 수도 있다.
Heap은 메모리를 효율적으로 사용할 수 있게되는 장점이 있지만 반대 급부의 단점이 있다.
data, bss영역에 들어가는 데이터들은 컴파일 단계에서 그 위치가 결정되기 때문에 프로그램이 동작중에는 그냥 그 위치에 데이터를 쓰고 읽기만 하면 된다. 하지만 heap은 프로그램 동작중에 메모리를 할당하여야 하므로 이를 위한 별도의 코드가 추가되어야 한다. 당연히 불필요한 시간이 많이 필요하게 된다. 시간만 더 추가 되는게 아니라 좌석이 어디에서부터 어디까지 할당되었는지 그 정보를 기록하기 위하여 별도의 좌석하나가 추가로 필요하게 된다.
그리고 마지막으로 메모리를 할당받아 다 쓰고 난 다음에는 반드시 메모리를 반환해 주어야 다른 용도로 재활용이 가능하게 된다. 만약 다 쓴후 반환하지 않게 되면 heap 메모리에서 할당 가능한 메모리 크기가 점점 줄어들게 되어 나중에는 더이상 메모리를 할당할 수 없게된다. 즉, 프로그램이 정상적으로 동작할 수 없게 된다는 뜻이다. 이러한 현상을 메모리 누수라고 표현한다. 컴퓨터나 스마트폰을 오랜시간 사용하게 되면 점점 사용할 수 있는 메모리 크기가 줄어드는 경우가 있는데 대부분 메모리 누수때문에 발생하게 된다.
C/C++는 프로그래머 스스로 메모리 누수가 발생되지 않도록 프로그램을 신중히 작성해야 되지만, 최근에 나오는 프로그램 언어는 garbage collection 기능이 추가 되어 사용하지 않는 메모리를 자동으로 반환해주어 메모리 누수가 발생되지 않도록 한다.
앞의 글에서 만들어 본 printf() 함수처럼 heap 메모리를 할당하고 반환하기 위한 malloc()과 free() 함수도 상당히 많은 소스코드를 찾아볼 수 있다.
이 블로그에서는 그 중에서 "Doug Lea"라는 개발자가 만든 코드를 이용하여 heap 메모리를 활용해 보도록 하겠다.
원본 소스 코드는 ftp://gee.cs.oswego.edu/pub/misc/malloc.c에서 받아 볼 수 있다.
가지고 있는 STM32F103보드에서 사용하기 위하여 원본 코드에 다음과 같은 코드를 추가해 준다.
첨부 파일중 malloc.c 파일의 534 ~ 556까지에 해당되는 내용이다.
메모리 할당과 반환이 제대로 수행되는지 확인하기 위한 코드를 다음과 같이 만들어 보았다.
코드는 먼저 0x100크기의 메모리를 다섯개 할당한 후 반환한다.
다음에는 0x200크기의 메모리를 다섯개 할당한 후 반환한다.
마지막으로 원래대로 0x100 크기의 메모리를 다시 할당한다.
메모리 할당과 반환이 제대로 되었다면 첫번째와 세번째 메모리 할당된 주소가 동일하여야 한다.
위의 그림이 프로그램을 수행한 결과이다. 예상한대로 제대로 동작되는것을 확인하였다.
이번에는 따로 코드를 작성하지는 않고 옵션사항만 추가해 주었으므로 별로 한거는 없다. 프로그램 개발 실력을 향상시키고자 하는 마음이 있다면 메모리 할당과 반환을 어떤 알고리즘을 써서 하는지 소스코드를 분석해보기 바란다. 그리고 실제로 코드를 한줄 한줄 따라서 작성해 보기를 권고한다.
이 블로그에서 참조한 소스코드는 다양한 동작 환경에 모두 사용될 수 있도록 엄청나게 많은 선택사항이 있는데 이런 부분을 다 걷어내고 사용하고 있는 보드에서만 동작될 수 있는 최대한 간단한 코드를 만드는것도 좋은 공부 방법이다. 그렇게 만들어 놓은 코드를 다른 종류의 CPU를 사용하는 보드에 적용되도록 수정하다 보면 소스코드를 어떻게 짜면 재활용성이 높게 될지 조금씩 깨닫게 된다.
이러한 작업을 반복하다보면 어느새 이 블로그에서 소개한 원본 코드와 비슷한 코드를 만들수있게 된다.
이번 글을 작성하면서 예상외로 많은 시간이 필요했는데 컴파일 옵션에 따라 될때도 있고 안될때도 있어서 코드가 잘못되었는지 컴파일 옵션이 잘못되었는지 찾느라 며칠을 고생하였다. 결과적으로 코드와 컴파일 문제가 아니라 동작 속도가 문제 였던 것이다. GPIO를 이용한 LED 제어에서도 언급하였는데 원래는 72MHz까지 동작된다고 메뉴얼에는 나와 있지만 실제로는 56MHz 이상에서는 프로그램이 동작되지 않았었다.
이번에는 56MHz에서도 제대로 동작되지 않았고, 동작 속도를 48MHz로 낮추었더니 비로소 정상동작되었다.
실제로 반도체 회사에서 칩을 제조한 후 시험할 때 특성이 잘 나오면 높은 주파수에서 동작된다고 마킹한 후 비싸게 팔고, 좀 특성이 안좋으면 낮은 동작 주파수를 마킹하여 저렴한 가격에 판매한다는 얘기를 들은적이 있다.
임베디드 시스템 프로그램할때 가장 어려운것이 프로그램에 따라 잘 동작될때도 있고 그렇지 않을때도 있는데, 잘 안되는 프로그램도 다른 보드에서는 아무 문제 없이 잘 동작될때가 있다. 그렇다는 얘기는 프로그램이 문제 있는것은 아니라는 얘긴데, 그렇다면 하드웨어에 무슨 문제가 있을수 있는데, 그 보드에 다른 프로그램을 돌리면 또 아무 문제 없이 잘 동작할때가 있다. 이런 상황이 임베디드 시스템 프로그램 개발할때 가장 힘든 부분이다.
임베디드 프로그램 개발에는 이렇게 예상하지 못했던 현상들을 해결해 나감으로써 자기 자신만의 노하우를 쌓게 되는것이므로 문제가 발생하는것을 두려워 하지 말고 오히려 고맙게 생각하여야 한다.
'ARM Cortex-M' 카테고리의 다른 글
Timer (0) | 2017.04.08 |
---|---|
Cortex-M3 RTOS (0) | 2017.03.25 |
printf()를 만들어 보자 (3) | 2017.03.12 |
interrupt (0) | 2017.02.25 |
System Timer (0) | 2017.02.24 |