개발 공부

손승열(Son Seungyeol)

[운영체제][OSTEP] 메모리 관리 API

Unix/C 프로그램에서 메모리를 할당하고 관리하는 방법에 대해 공부해봅니다.

손승열(Son Seungyeol)
[운영체제][OSTEP] 메모리 관리 API

1️⃣ 메모리 공간의 종류

C 프로그램이 실행되면, 두 가지 유형의 메모리 공간이 할당된다.

스택(stack) 메모리

할당과 반환은 프로그래머를 위해 컴파일러에 의해 암묵적으로 이루어진다.
➡️ 이러한 이유로 자동(automatic) 메모리라고도 불림.

C 프로그램에서 스택에 메모리를 선언하기 위해서는 다음과 같은 식으로 코드를 작성하면 된다.

c
void func() {
  int x; // 스택에 int 형을 선언
  ...
}

위는 func( )라는 함수 안에서 정수를 저장할 변수 x를 위한 공간이 필요한 경우이다.

이 경우 컴파일러가 나머지 작업을 수행하여, func( )가 호출될 때 스택에 공간을 확보한다.

함수에서 리턴하면 컴파일러는 프로그래머 대신 메모리를 반환한다.
➡️ 함수 리턴 이후에도 유지되어야 하는 정보는 스택에 저장하지 않는 것이 바람직

힙(heap) 메모리

모든 할당과 반환이 프로그래머에 의해 명시적으로 처리된다.

이로 인해 프로그래머는 메모리 사용에 대한 책임을 져야하고 또한 이는 많은 버그의 원인이 되기도 한다. 🐞

C 프로그램에서 힙에 메모리를 할당하기 위해서는 다음과 같은 식으로 코드를 작성하면 된다.

c
void func() {
  int *x = (int *) malloc(sizeof(int));
  ...
}

위는 정수에 대한 포인터 변수 x를 위한 공간을 힙에 할당하는 경우이다.

⚠️ 위 코드에서의 주의 사항을 살펴보자.

1️⃣ 한 행에 스택과 힙 할당이 모두 발생한다.
➡️ 포인터 변수 int *x 선언 시 정수 포인터를 위한 공간을 스택에 할당한다.
➡️ 프로그램이 malloc()을 호출하여 정수를 위한 공간을 힙으로부터 요구한다.

malloc()성공한 경우 그 정수가 저장된 메모리 주소를 반환하고, 실패한 경우에는 NULL을 반환한다.
➡️ 이 반환된 주소는 스택에 저장되어 프로그램에 의해 사용된다.

이러한 명시적 성질과 다양한 쓰임새로 인해 힙 메모리의 사용은 사용자 & 시스템 모두에게 어려운 숙제


2️⃣ malloc( ) 함수

힙에 요청할 공간의 크기를 넘겨 주었을 때,
[✅ 성공 시] 새로 할당된 공간에 대한 포인터를 사용자에게 반환
[🚫 실패 시] NULL을 반환

malloc()을 사용하기 위해서는 stdlib.h를 소스 코드에 포함시켜야 한다.
➡️ 모든 C 프로그램에 디폴트로 링크되는 C 라이브러리는 malloc() 함수를 가지고 있으므로 포함시키지 않아도 됨.
➡️ 헤더 파일 추가 시 malloc()을 올바르게 호출했는지 컴파일러가 검사할 수 있음.

malloc()의 인자는 size_t 타입(필요 공간의 크기를 바이트 단위로 표시)의 변수

예를 들어, double 부동 소수점 값을 위한 공간을 확보하기 위해서는 다음과 같이 코드를 작성할 수 있음.

c
double *d = (double *) malloc(sizeof(double));

sizeof()컴파일 시간 연산자(함수X)로 할당하려는 공간의 크기가 컴파일 시간에 결정된다.


3️⃣ free( ) 함수

더 이상 사용되지 않는 힙 메모리를 해제하기 위해 호출한다.

인자로는 malloc()에 의해 반환된 포인터 하나를 받는다.

예를 들어, int 변수를 위한 공간을 할당한 후 메모리를 해제하는 경우 다음과 같이 코드를 작성할 수 있다.

c
int *x = malloc(10 * sizeof(int));
...
free(x);

4️⃣ 흔한 오류

많은 새로운 언어들이 자동 메모리 관리(automatic memory management)를 지원함.
➡️ 쓰레기 수집기(garbage collector): 참조되지 않는 메모리가 알아서 해제

메모리 할당 잊어버리기

많은 루팅은 자신이 호출되기 전 필요한 메모리가 미리 할당되었다고 가정 ex) strcpy()
➡️ 세그멘테이션 폴트(segmentation fault)가 발생할 수 있으므로 사용 전 메모리를 반드시 할당받자.

메모리를 부족하게 할당받기

때때로 버퍼 오버플로우(buffer overflow)라고 불림.
잘 실행되는 듯 보일 수 있으나, 특정 시점에 보안 취약점의 원인이 될 수 있다.
➡️ 프로그램이 한 번 올바르게 실행된다고 하더라도, 프로그램이 올바르다는 것은 아님.

할당받은 메모리 초기화하지 않기

초기화되지 않은 읽기(uninitialized read): 힙으로부터 알 수 없는 값을 읽음.
➡️ 임의의 값 혹은 해로운 값이 읽힐 수 있으므로 메모리를 할당 받은 후 초기화를 하자.

메모리 해제하지 않기

메모리 누수(memory leak): 메모리 해제를 잊었을 때 발생함.

장시간 실행되는 응용 프로그램이나 운영체제 자체와 같은 시스템 프로그램에서 큰 문제
➡️ 결국 메모리 부족으로 시스템을 재시작해야 함.

메모리 청크의 사용이 끝나면 반드시 해제
➡️ 쓰레기 수집 기능이 있어도 메모리 청크에 대한 참조가 존재하면, 어느 쓰레기 수집기도 그 청크를 해제하지 않음.

💡 프로세스가 종료하면 왜 메모리 누수가 일어나지 않는가?

시스템이 실제 두 단계로 메모리를 관리하기 때문이다.

[첫 번째 단계] 운영체제에 의해 수행
프로세스가 실행할 때 메모리를 프로세스에게 건네 주고 프로세스가 종료하거나 다른 이유로 죽을 때 메모리를 되돌려 받음.

[두 번째 단계] 각 프로세스의 힙 내에서 수행(malloc()free() 호출 시 등)

따라서 free()를 호출하지 못했더라도 프로세스 종료 시 운영체제가 프로세스의 모든 메모리(프로세스의 코드, 스택, 힙 등 모든 페이지)를 회수한다.
➡️ 실행이 시간이 짧은 프로그램의 경우, 큰 문제가 발생하지 않음.
➡️ Web 서버나 DBMS와 같이 장시간 실행되는 경우 심각한 문제가 될 수 있음.
➡️ 특히 운영체제 자신(커널 코드)에서 메모리 누

메모리 사용이 끝나기 전에 메모리 해제하기

해제된 메모리를 가리키는 dangling pointer 문제가 발생할 수 있다.
➡️ 프로그램을 크래시시키거나 유효 메모리 영역을 덮어쓸 수 있음.

반복적으로 메모리 해제하기

프로그램은 가끔씩 메모리를 한 번 이상 해제하며 이중 해제(double free)라 불림.
➡️ 결과는 예측하기 어려움. 가장 흔하게는 크래시가 일어남.

free( ) 잘못 호출하기

free()malloc()받은 포인터만 전달될 것으로 예상
➡️ 그 이외의 값을 전달하면 유효하지 않은 해제(invalid frees) 문제가 발생한다.


5️⃣ 운영체제의 지원

malloc()free()는 시스템 콜이 아닌 라이브러리 함수이다!

malloc 라이브러리가 프로세스 가상 주소 공간 안의 공간을 관리하지만 라이브러리 자체는 시스템에게 더 많은 메모리를 요구하고 반환하는 시스템 콜을 기반으로 구축됨.

그런 시스템 콜 중 하나가 brk 시스템 콜: 프로그램의 break 위치를 변경하는 데 사용됨.
🔖 break는 힙의 마지막 위치를 나타냄.

brk는 새로운 break 주소를 나타내는 한 개의 인자를 받음.
➡️ 새로운 break가 현재 break보다 큰지 작은지에 따라 힙의 크기를 증가시키거나 감소시킴.

sbrk는 인자로 increment를 더 받는 것을 제외하고 비슷한 용도로 사용됨.

⚠️ brk 또는 sbrk를 직접 호출해서는 안 됨. ➡️ malloc()free() 사용

mmap() 함수를 사용하여 운영체제로부터 메모리를 얻을 수도 있음.

올바른 인자를 전달하면 mmap()은 프로그램에 anonymous의 메모리 영역을 만듦.
🔖 anonymous 영역은 특정 파일과 연결되어 있지 않고 스왑 공간(swap space)에 연결된 영역을 말함. ➡️ 힙 처럼 취급되고 관리됨.


6️⃣ 기타 함수들

calloc()은 할당 영역을 0으로 채워서 메모리를 반환
➡️ 초기화되지 않은 읽기 문제 방지

realloc()은 더 큰 새로운 영역을 확보하고 옛 영역의 내용을 복사한 후에 새 영역에 대한 포인터를 반환
➡️ 이미 할당된 공간(배열 등)에 대해 추가의 공간이 필요할 때 유용


📚 참고 문헌

Operating Systems: Three Easy Pieces ― 14: Interlude: Memory API

운영체제 아주 쉬운 세 가지 이야기 ― 17: 막간 : 메모리 관리 API

관련있는 게시물

[운영체제][OSTEP] 주소 공간의 개념
개발 공부

[운영체제][OSTEP] 주소 공간의 개념

주소 공간의 개념에 대해 공부해봅니다.

손승열(Son Seungyeol)
손승열(Son Seungyeol)
[운영체제][OSTEP] 주소 변환의 원리
개발 공부

[운영체제][OSTEP] 주소 변환의 원리

효율적이고 유연하게 메모리를 가상화하는 방법에 대해 공부해봅니다.

손승열(Son Seungyeol)
손승열(Son Seungyeol)

Made with React, Gatsby and DatoCMS by @smastrom

Contribute or star on GitHub

© 2022-2023 손승열 for 🅒🅞🅝🅣🅔🅝🅣🅢

모두 좋은 하루 보내세요! 😊