티스토리 뷰
먼저 CPU가 명령어를 실행하기 위해 어떤 사이클을 수행하는지 생각해봅시다.
CPU의 명령어 실행 사이클은 크게 '명령어 인출'과 '명령어 실행' 단계로 이루어지는데요, 명령어 인출은 말그대로 CPU가 수행할 명령어를 꺼내오는 사이클을 말하고, 명령어 실행은 인출한 명령어를 실행하는 사이클을 말합니다.
이 과정에서 CPU의 많은 레지스터가 사용되지만 이번 주제는 메모리와 관련되어 있으니, MAR, MBR 레지스터만 아주 간단하게 그림으로 나타내보겠습니다.
MAR은 Memory Address Register의 약자이고, MBR은 Memory Buffer Register의 약자입니다.
이름에서 알 수 있듯이, 이 두개의 레지스터는 Memory와 매우 밀접한 관계를 가지며 각각의 역할은 아래와 같습니다.
- MAR : PC에 저장된 명령어 주소가 시스템 주소 버스로 출력되기 전 일시적으로 저장되는 주소 레지스터
- MBR : 기억장치에 저장될 데이터 혹은 기억장치로부터 읽혀진 데이터가 일시적으로 저장되는 버퍼 레지스터
CPU가 명령을 실행하기 위해 필요한 레지스터의 역할을 보면 한가지 알 수 있는 사실이 있습니다. 바로 CPU가 수행하는 모든 명령어, 명령어 실행에 필요한 혹은 결과 데이터는 항상 Memory를 거쳐간다는 것입니다.
그리고 이러한 CPU의 동작 방식은 오늘날 멀티 프로세스 환경에서 해결해야 하는 몇가지 문제가 있습니다. 하나씩 살펴보겠습니다.
프로세스의 메모리 영역을 어떻게 보호할 것인가?
시스템이 올바르게 동작하기 위해서는 메모리에 올라가는 사용자 프로세스, 운영체제 영역을 서로 보호해야하며 프로세스간에도 서로 메모리 영역을 침범하면 안됩니다.
이를위해 CPU와 메모리 영역 사이에 운영체제가 개입해 메모리 간섭이 일어나지 않도록 관리하는 방법을 생각 해볼 수 있습니다.
하지만, 운영체제가 개입 할 경우 성능 저하로 이어질 수 있기 때문에 현대의 컴퓨터는 운영체제가 개입하지 않고, 하드웨어가 보호 기법을 지원하도록 설계되었습니다. 이를위한 하나의 구현 방법이 있는데, 아래의 그림을 보겠습니다.
이 구현방식의 목적은 개별적인 메모리 공간을 분리하기 위해 특정 프로세스만 접근할 수 있는 합법적인 메모리 주소 영역을 설정하고, 프로세스가 합법적인 영역만을 접근하도록 만드는 것입니다.
동작 원리를 이해해보겠습니다.
그림을 보면, '기준(base)'과 '상한(limit)'이 보이고 이둘은 레지스터입니다. '기준 레지스터'는 프로세스가 할당될 가장 작은 물리 메모리 주소의 값을 저장하고, '상한 레지스터'는 주어진 영역의 크기를 저장합니다.
이 두 레지스터에 저장된 값을 이용하면, 프로세스가 운영체제의 메모리 공간이나 다른 프로세스의 메모리에 접근할 경우 OS는 trap을 발생시킬 수 있습니다. 메모리 영역을 보호할 수 있는 것이죠.
프로세스를 메모리의 몇번지에 올릴것인가?
디스크에 저장된 프로그램을 실행하려면 프로그램을 메모리로 가져와서 프로세스 문맥내에 배치 해야하며, 메모리 영역으로 올라오면 프로세스가 됩니다. 프로세스는 각각 독립적인 메모리 영역을 가지고 있는데, 이를 '논리 주소(Logical Address)'라고 부릅니다. 그리고 실제 프로세스가 메모리에 올라간 주소는 '물리 주소(Physical Memory)'라고 부릅니다.
CPU는 논리 주소를 통해 프로세스를 수행할 수 있지만, 실제로는 물리 주소에 접근해야 합니다.
따라서 위의 그림과 같이 논리 주소를 물리 주소로 변환하는 과정이 필요하고 이를 '주소 바인딩 (Address binding)'이라고 부릅니다.
이 주소 바인딩 과정은 언제 실행되는지?에 따라 3가지 방법으로 분류할 수 있습니다.
이 방법들을 알아보기 앞서 우리가 프로그래밍을 하는 상황을 생각해보겠습니다.
#include <bits/stdc++.h>
int n = 1;
void init(){
while(true) {
cout << n << '\n';
}
}
int main(){
ios_base :: sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
init();
}
위와같은 프로그램 코드를 실행하면 우리는 하나의 프로세스를 실행한 것이고, 아래와 같이 눈으로 확인해볼 수도 있습니다.
501 63555 63544 34006 0 31 0 34260244 1100 0 ttys002 0:00.44 /Users/jo/Desktop/C/2
보통 코드를 작성할때 논리 주소나 물리 주소에 직접 접근하지 않습니다. 우리는 'n'이라는 변수를 이용해 메모리를 사용합니다.
다시말해 프로그래머는 숫자 주소가 아닌 'Symbol' 주소로 메모리를 사용합니다. 왜 그럴까요?
DNS 서버를 사용하는 이유와 유사합니다. 메모리에 접근할때, 가변적이고 직관적으로 이해하기 쉽지 않은 메모리 주소보다는 변수로 이를 대치하고 사용하는 것이죠.
그래서 다시 메모리 영역에 접근하는 과정을 다시 나타내보면 위의 그림과 같습니다.
우리는 Symbolic Address를 이용해 코드를 작성하고, 이 코드가 컴파일 되면 Logical Address로 바뀝니다. 그리고 프로세스가 실행이 되기 위해서는 물리 메모리로 올라가야 하므로 Logical Address -> Physical Address 변환 과정이 필요한 것입니다.
자 그래서 주소 바인딩 과정이 언제 일어나는가?를 알아보겠습니다. 주소 바인딩은 크게 3가지 시점으로 나눌 수 있습니다.
- Compile time binding
- Load time binding
- Execution time binding
먼저 Compile time binding을 나타내는 그림입니다.
Compile time binding은 Compile 시점에 결정된 논리 주소가 물리 주소와 일치하는 바인딩입니다.
당연하게도 이 방법은 비효율적입니다. 왜냐하면, 물리 메모리 주소의 공간이 남아있음에도 컴파일 시점의 주소가 같아버리면, 동시에 여러 프로세스를 실행시킬 수 없기 때문입니다. 그래서 오늘날의 컴퓨터 시스템에서는 거의 사용하지 않습니다.
다음으로 Load time binding을 나타내는 그림입니다.
Load time binding은 컴파일 된 후, 실행 시점에 사용할 물리 메모리 영역이 결정됩니다.
이전에 등장한 Compile time binding과는 달리 물리 주소가 동적으로 결정되기 때문에 multiprogramming도 가능하다는 특징도 가집니다. 하지만, Compile time과 같이 프로세스가 실행될때 주소가 이미 결정되기에 주소의 변경이 필요하다면 다시 컴파일을 해야합니다.
자 마지막으로 Execution time binding 입니다.
Execution time binding은 Load time binding과 기본 동작은 유사하지만, 실행 중에 메모리 재배치가 가능하다는 특징을 가지고 있습니다. 즉 컴파일을 다시 하지 않아도 물리 메모리에 위치한 프로세스의 주소를 조정할 수 있다는 것이죠.
그림을 보면, 실행 시점 Logical Address가 Physical Address로 매핑됩니다. 그리고 프로세스가 실행중이더라도 이 관계를 새롭게 정의하고 있습니다. 하지만, 이를 가능하게 만들기 위해서는 특별한 하드웨어가 필요하며 여기서 바로 이 글을 작성한 이유인 'MMU'가 등장합니다.
위에서 봤듯이 프로그램의 실행 중에는 가상 주소를 물리 주소로 바꾸는 작업이 필요합니다. 그리고 이 변환 작업은 위의 그림과 같이 MMU가 실행합니다.
다시 처음에 봤던 그림을 보겠습니다.
'기준 레지스터(base register)'와 '상한 레지스터(limit register)'를 이용하면 프로세스가 서로의 메모리 영역을 침범하지 않음을 보장할 수 있다고 했습니다.
이 기법을 기준으로 아주 단순한 MMU는 어떻게 동작하는지 살펴볼 것입니다. 이제 '기준 레지스터(base register)'를 '재배치 레지스터(relocation register)'라고 부르도록 하겠습니다.
재배치 레지스터에 14000이라는 값이 설정되었다고 가정해보겠습니다. 그러면, 프로세스가 논리 주소 346에 액세스할 때, 재배치 레지스터는 값을 더해 실제 메인 메모리의 14346번지에 액세스하게 됩니다.
여기서 중요한 것은 프로세스가 결코 물리 주소에 접근하지 않는다는 것입니다. 프로세스는 346번지에 대한 포인터를 생성해서 그것에 대해 저장, 연산, 다른 주소들과 비교하는 등 작업을 수행할 수 있습니다. 하지만, 실제로는 기준 레지스터에 의해 물리 주소로 바인딩되어 물리 메모리에 접근하는 방식으로 동작합니다.
MMU가 어떤 역할을 수행하는지에 대한 정의를 내리면 '논리 주소를 물리 주소로 매핑하는 작업을 수행한다.' 입니다.
MMU의 정의는 어렵지 않으나, MMU가 왜 이런 역할을 수행해야하는지 궁금증이 생겼고, 자연스레 MMU를 사용하는 바인딩 기법들을 마주치며 글이 길어진것 같습니다. 잘못된 내용이 있다면 지적 및 피드백 해주시면 감사하겠습니다.
Ref : Operating System Concept 10
'CS > Operating System' 카테고리의 다른 글
[Operating System] 페이징(Paging)이란? (0) | 2022.10.19 |
---|
- Total
- Today
- Yesterday
- java
- paging
- soft delete
- ARP
- Database
- fiber
- go
- cs
- Operating System
- 공지
- Effective Java
- spring
- algorithm
- mmu
- GORM
- effective
- network
- OS
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |