메모리를 관리하며 가장 중요한 점은 무엇일까? 다음 질문에 대해 고민해 보자.
❓ 한정된 메모리 공간을 어떻게 하면 효율적으로 관리할 수 있을까?
CPU가 직접적으로 접근할 수 있는 저장 장치는 메모리이지만, 메모리의 공간은 한정적이다. 여러 개의 프로그램을 실행해야 하는 운영체제에서는, 협소한 메모리 공간을 최대한 효율적으로 관리해서 CPU가 필요한 데이터를 빨리 끌어올 수 있도록 하는 것이 목표이다.
CPU는 메모리에 접근할 때 ‘주소값’을 이용한다. 메모리의 주소 공간에 대해 살펴보자.
주소 공간, Address Space
메모리의 주소 공간은 아래와 같이 표현한다.
위 그림에서 오른쪽 박스는 메모리 공간을 나타내고, 각 칸마다 왼쪽에 해당 칸의 메모리 주소값이 적혀있다. CPU는 이 주소값을 통해 메모리에 접근한다.
메모리 단위는 비트(bit)가 아니라 바이트(byte)이다. 따라서 위 공간에서 한 칸은 1byte의 데이터를 저장할 수 있다. 위 예시에서 0번지 주소에 해당하는 명령어도 0100 1111로 1byte임을 알 수 있다.
또한 주소 공간이 2^32 라는 것은, 최소 0부터 2^32-1까지 주소가 존재한다는 것이며, 따라서 이는 32비트로 표현될 수 있다. 또한 메모리 칸의 개수가 2^32개라는 뜻이기도 하다.
물리적 주소, Physical Address
물리적 주소란 메모리 자체의 인덱스, 주소를 말한다.
메모리의 기능은 ‘데이터를 저장하는 컨테이너’로, 크기가 굉장히 큰 배열로 볼 수 있다. 배열이기 때문에 인덱스 값을 가지고, 이 인덱스 값을 물리적 주소라 한다. 즉, 위 그림에서 메모리의 각 주소가 물리적 주소에 해당한다.
논리적 주소, Logical Address
논리적 주소란 CPU 입장에서의 메모리 주소, 또는 프로그램 실행 중에 CPU가 생성하는 주소를 말하며, 가상주소라고도 한다.
논리적 주소를 사용하는 이유 : 멀티 프로세스
논리적 주소를 사용하는 이유 중 하나는 멀티 프로세스 때문이다. 논리적 주소가 없다면 모든 프로세스는 데이터에 접근할 때 물리적 주소로 직접 접근해야 한다. 이러면 아무런 안전장치가 없기 때문에 프로세스끼리 주소 공간을 침범할 수도 있는 문제가 발생한다.
공간을 구분하는 기술 : Base Register, Limit Register
같은 메모리를 사용하면서 서로 침범하지 않게 하려면 어떻게 해야할까? ‘여기서부터 여기까지가 나의 공간이다’라고 표시하면 된다. 즉, 자기가 사용하는 주소 공간을 표현하기 위해 시작 주소와 사용하는 주소 공간의 크기를 지정하면 된다. ‘시작 주소 + 크기’가 끝 주소가 되기 때문에 이 둘만 지정한다면 프로세스의 시작 주소와 끝 주소를 명시할 수 있다.
이를 활용해서 프로세스가 접근하고자 하는 주소가 시작 주소와 끝 주소 사이에 있으면 접근할 수 있고, 그 외에 있으면 에러를 발생시키는 방식이다.
이렇게 모든 프로세스는 ‘시작 주소’와 ‘크기’를 나타내는 레지스터가 정의돼 있는데, 각각 Base Register와 Limit Register라 부른다.
MMU : 논리적 주소를 물리적 주소로 변환
이제 모든 프로세스는 Base Register로부터 물리적 주소에서의 시작 주소를 알 수 있기 때문에 항상 0번지 주소부터 시작해도 아무 문제가 없다. 따라서 새로운 프로세스가 생성되면 항상 0번지 주소부터 시작한다. 이를 논리적 주소라 한다.
간단한 예를 들어보자. 논리적 주소가 3이고 base register 값이 1230이면 물리적 주소는 몇일까? 3 + 1230 = 1233이다. 이렇게 단순히 더하면 된다.
이처럼 프로세스가 물리적 주소에 접근하려면 논리적 주소를 물리적 주소로 Mapping시키는 과정이 필요한데, 이를 MMU(Memory Management Unit)가 수행한다. 주소 매핑 과정은 매우 빈번하게 일어나므로 MMU는 하드웨어적으로 구현되어 있다. MMU의 역할은 아래 그림과 같이 논리적 주소에 base register 값을 더해주는 것이다.
위 그림에서 relocation register가 base register와 같은 것이라 보면 된다.
주소 바인딩
주소 바인딩이란 논리적 주소를 물리적 주소로 매핑시키는 여러 시점을 말한다.
예시로 아래와 같이 간단한 프로그램을 작성했다고 가정해보자.
data = 10;
이는 data 메모리 번지에 10을 write하는 코드이다. 그런데 실제로 프로그램을 실행시키면 컴파일 과정을 통해 다음과 같은 어셈블리어로 변환된다.
store $98000 10 //$98000은 data의 논리적 주소
위에서 $98000은 논리적 주소를 뜻한다. 아까 주소 바인딩은 논리적 주소가 물리적 주소로 Mapping되는 시점을 뜻한다고 하였다. 위 예제에서 data의 물리적 주소는 compile time, load time, execution time에 결정된다. Relocation Register의 값은 100000이라고 하자. 즉, 물리적 주소값은 198000이라는 뜻이다. 각각 어떻게 달라지는지 살펴보자.
Compile Time Binding
프로그램이 컴파일 될 때 주소가 매핑되는 경우이다.
위 그림을 보면 메모리에 올라가기 전에 이미 물리적 주소가 결정된 상태이다. 이는 말도 안 된다.
간단한 예를 들어보자. 게임 회사에서 어떤 게임의 주소 바인딩을 compile time에 했다고 가정해보자. 바인딩 된 주소는 위 그림처럼 198000~1000000이다. 이 게임을 설치한 사람들은 어떻게 될까? 컴파일을 통해 실행파일을 생성하기 때문에, 물리적 주소의 198000~1000000 사이가 무조건 비어 있어야만 게임을 실행할 수 있게 된다. 왜냐하면 물리적 주소가 이미 실행 전 정해졌기 때문이다.
이처럼 compile time에 주소 바인딩을 한다는 것은, 멀티 프로그래밍을 포기한다는 것과 같은 말이다. 이는 프로그램 내부에서 사용하는 주소와 물리적 주소가 같다는 것으로 이해해도 된다. 이러면 논리적 주소의 존재 이유가 없다.
그렇다면 compile time에 메모리 번지가 결정되어도 상관 없는 상황은 어떤 경우일까? 메모리에 오직 하나의 프로그램만 올리면 가능하다. 예를 들면 아두이노는 내가 만든 프로그램만 돌아가기 때문에 compile time에 메모리 번지가 결정되어도 상관 없다.
Load Time Binding
프로그램을 메모리에 로딩할 때 주소가 매핑되는 경우이다.
우선 논리적 주소와 물리적 주소를 분리시켰다는 점에서 compile time binding과는 다르게 멀티프로그래밍이 가능하다. Relocation Register가 제 기능을 하기 때문에 프로세스의 주소가 메모리의 어디에나 위치할 수 있기 때문이다.
그런데 어떤 문제가 있을까? 프로그램 안에 있는 모든 주소를 한 번에 전부 relocation 시켜야 하기 때문에 로딩할 때 시간이 오래 걸리는 문제가 발생한다.
Execution Time Binding
프로그램을 실행할 때 주소가 매핑되는 경우이다.
쉽게 말해서 code level 실행 시 relocation을 해주는 것이다. code level이란 ‘코드 한 줄’로 생각하면 된다. 위 그림의 경우 ‘data = 10’이 실행될 때 비로소 논리적 주소를 물리적 주소로 바인딩해주는 것을 확인할 수 있다.
비교 : Load Time Binding VS Execution Time Binding
두 방법이 헷갈릴 수 있다. 로드 타임은 메모리에 로딩할 때 주소 변환을 미리 다 해놓는 방법이고, 실행 타임은 프로그램을 실행해서 그 코드가 실행될 때마다 주소를 변환하는 방법이다.
로드 타임의 경우 한 번만 바꿔놓으면 그 다음부터는 해당 주소로 접근하면 되는데, 실행 타임은 변환 작업을 반복 수행해야 한다.
로드 타임은 한 번에 변환을 실행하기 때문에 Relocation은 소프트웨어적으로 실행되며, 한 번 로딩할 때 시간이 오래 걸린다. 하지만 실행 타임은 Relocation이 매우 빈번하게 일어나기 때문에 하드웨어적인 도움이 필요하다. 이때 쓰는 것이 바로 MMU이다.
참고 자료
[ 운영체제 ] 주소 공간(Address Space), 물리적 주소(Physical Address), 논리적 주소(Logical Address), 주소 바
저번 글에서는 컴파일, 런타임, 로딩을 이해하는 시간을 가졌다. 이 세 용어를 확실하게 이해했다면 이번 글은 술술 읽으면서 넘길 수 있을 것이다. '메모리 계층 구조'부터 이 글까지는 '메모리
charles098.tistory.com
'Computer Science > Operating System' 카테고리의 다른 글
외부 단편화와 페이징, External Fragmentation and Paging (0) | 2024.04.17 |
---|---|
CPU 주소 체계와 가상메모리 (0) | 2024.04.17 |
컴파일(Compile), 링킹(Linking), 로딩(Loading), 런타임(Runtime) (0) | 2024.04.17 |
DeadLock, 교착상태 (0) | 2024.04.17 |
Race Condition, Mutex와 Semaphore (0) | 2024.04.16 |