[Operating Systems] Memory Management 1

chowisely·2020년 8월 20일
0

Operating Systems

목록 보기
4/11

OS는 사용자에게 메모리에 대한 추상화를 제공할 뿐만 아니라 메모리 관리(memory management)를 통해 한정된 메모리 자원을 효율적으로 사용하고 메모리를 보호한다. 또한 메모리 관리는 각 프로세스들에 할당된 메모리 정보와 비어있는 메모리 공간에 대한 정보 관리를 포함한다.

메모리를 효율적으로 사용한다는 것은 곧 메모리 낭비가 없어야 한다는 뜻이다. OS의 메모리 관리자는 MMU(memory management unit)과 협업하여 프로세스의 적재 요청이 있으면 메모리를 할당하고, 필요 없으면 해당 메모리를 해제하여 다른 프로세스에게 내어준다.

메모리 관리 정책은 fetch, placement, replacement 세 가지로 구분한다. fetch는 디스크에서 메모리로 프로세스를 올릴 시기를 결정하고, placement는 어느 메모리의 위치에 프로세스를 적재할 것인지 결정한다. replacement는 프로세스에 더이상 할당할 메모리 여유분이 없을 때 어떤 프로세스의 메모리를 내어줄지 결정한다. 다음 글에서도 이어지겠지만, placement는 first-fit, best-fit, worst-fit, replacement는 FIFO, Optimal, LRU, Second chance algorithm과 관련이 있다.

이 글에서는 메모리 관리 중에서도 주소 바인딩(address binding)스와핑에 대해서 설명할 것이다.


Address Binding

주소 바인딩을 언제 하느냐로 구분하면 세 가지로 나눌 수 있다.
1. 컴파일 시: 프로세스가 적재될 위치가 컴파일 시에 알려지고 시작 위치가 변경되면 컴파일을 다시 해야 한다. 컴파일러는 절대 코드(absolute code)를 생성하게 된다.
2. 적재 시: 컴파일러가 상대 주소를 사용하여 재배치 가능 코드를 생성한 경우 로더(loader)가 물리적 주소를 부여할 수 있다. 만약 시작 주소가 변하면 변화된 값을 반영하기 위해 프로세스를 재적재해야 한다.
3. 런타임 시: 런타임까지 바인딩이 연기되면 재배치 목적 레지스터만 바꿔주면 메모리 상에서 위치를 옮길 수 있다. 이 경우에는 CPU가 논리적 주소로 메모리를 참조할 때마다 물리적 주소로의 바인딩이 유효한지 점검하는 하드웨어적인 지원이 필요하다.

'컴파일러', '로더'가 물리적 주소를 부여하면, 시작 주소가 바뀌면 각각 '컴파일', '로딩'을 다시 해야한다.


Dynamic Loading

프로그램 실행에 반드시 필요한 루틴만 적재함으로써 메모리를 효율적으로 사용할 수 있는 방법이다. 모든 루틴이 아닌 필요한 루틴만 메모리에 적재하고 나머지는 재배치 가능한 형태로 디스크에 저장한다. 메인 프로그램에서 어떠한 루틴을 호출하면 해당 루틴이 메모리에 적재되어 있는지 먼저 확인하고, 그렇지 않다면 디스크로부터 해당 루틴을 적재한다.

예를 들어서, 에러 핸들링이 프로그램의 20%를 차지한다고 하자. 보통의 경우에는 에러 핸들링이 실행 과정에서 호출되는 경우는 드물다. 따라서 에러 핸들링 루틴을 디스크에 저장해두게 되면 그만큼의 메모리를 절약하여 다른 프로세스를 적재시킬 수 있는 것이다.


Dynamic Linking

// p1.c
#include <stdio.h>
int main() {
	printf("P1 hello world!");
}

// p2.c
#include <stdio.h>
int main() {
	printf("P2 hello world!");
}

위 두 코드를 컴파일할 때 printf가 존재하는 라이브러리를 포함시켜 실행 파일을 만든다고 하자.
링커에 의해 p1.o와 printf.o, p2.o와 printf.o가 묶여 두 개의 실행 파일이 만들어질 것이다. 이들이 동시에 메모리에 적재되어 있다면, printf에 해당하는 라이브러리가 메모리에 중복되어 메모리 낭비로 이어진다. 이처럼 컴파일 시에 라이브러리를 포함시키는 것을 static linking이라고 한다. 프로그램 내에 라이브러리를 포함시키면 overhead는 작아지지만 메모리의 비효율적인 사용의 위험이 있다.

반면에 printf를 호출할 때마다 런타임에 printf.o를 링킹하는 것을 dynamic linking이라고 한다. 실행 파일을 생성할 때 printf.o를 포함하지 않아 메모리에 중복으로 적재될 가능성이 사라진다.


Swapping

이전 글에서 중기 스케쥴러에 대해 말하면서 스와핑을 언급한 적이 있었다.

스와핑은 메모리에 적재되어 있으나 현재 사용되고 있지 않은 프로세스 이미지를 디스크에 있는 스왑 영역(backing store)으로 스왑 아웃(swap-out)하여 내보내고, 후에 프로세스 이미지를 스왑 인(swap-in)하여 다시 메모리에 적재하는 것을 말한다. 스와핑에 소요되는 시간은 대부분 전송 시간이고, 전송 시간은 프로세스 이미지의 크기에 비례한다. 따라서 너무 자주 스와핑을 하는 것도 성능상 좋지 않을 수 있다.

하나 생각해야할 점은 스왑 아웃되었다가 스왑 인되는 프로세스는 이전에 위치했던 메모리 주소로 다시 돌아갈 확률이 매우 낮다는 거다. 그래서 스와핑을 지원하기 위해서는 프로세스의 물리적 주소가 재배치가 가능한 것이 좋다. 컴파일 시와 적재 시 주소 바인딩을 하게 되면 반드시 원래 주소로 돌아와야만 한다. 하지만 런타임 시 주소 바인딩을 사용하면 반드시 같은 주소로 돌아올 필요가 없고 재배치 레지스터(relocation register)만 바꿔주면 된다.


MMU(Memory Management Unit)

런타임 시 주소 바인딩을 하게 되면, 접근하고자 하는 메모리의 논리적 주소를 물리적 주소로 바꿔주어야 한다. 이를 담당하는 것이 MMU이다. 기준 레지스터(base register 혹은 relocation register)경계 레지스터(limit register)를 가지고 사용자 모드에서 메모리로의 접근이 유효한지 검사한다.

조금 더 자세히 말하자면, 사용자 프로세스는 0~X라는 논리적 주소를 가지며 100이라는 주소에 접근하고 싶고, MMU는 해당 프로세스에 대해서 기준 레지스터는 500, 경계 레지스터는 1000을 가지고 있다고 하자. CPU가 메모리 상의 주소 100에 접근하기 이전에 MMU를 통과하게 된다. MMU는 논리적 주소 100을 받아 기준 레지스터에 더하고, 그 값이 기준 레지스터와 경계 레지스터의 합보다 작은지 검사함으로써 유효한 접근인지 판단한다. 이 경우 100 + 500 < 500 + 1000이므로 논리적 주소 100은 물리적 주소 600으로 변환되고 메모리에 유효한 접근을 하게 된다. 즉, CPU는 주소 100에 접근한다고 생각하지만 사실은 그렇지 않은 것이다.

profile
honesty integrity excellence✨ [이사 중🚚](chowisely.github.io)

0개의 댓글