# 주소 변환의 원리[^ji-nyu]

[^ji-nyu]: [박진우](https://github.com/ji-nyu)

메모리 가상화는 가상화를 제공하는 동시에 효율성과 제어 모두를 추구한다.

- 효율성: 레지스터, TLB등의 하드웨어 지원을 활용해 효율을 높인다.
- 제어: 응용 프로그램이 자기 자신의 메모리 이외에는 다른 메모리에 접근하지 못한다.
- 유연성: 프로그래머가 원하는대로 주소공간을 사용하고 프로그래밍 하기 쉬운 시스템을 만들기를 원한다.

주소변환(Address translation)을 통해 하드웨어는 명령어 반입, 탑재, 저장 등의 가상주소를 정보가 실제 존재하는 물리주소로 변환한다. (하드웨어 기반 주소 변환)

프로그램의 모든 메모리 참조를 실제 메모리 위치로 재지정하기 위하여 하드웨어가 주소를 변환한다.
운영체제는 메모리의 빈 공간과 사용중인 공간을 항상 알고 있어야 하고, 메모리 사용을 제어하고 관리한다.

## 가정

- 사용자 주소공간은 물리 메모리에 연속적으로 배치되어야 한다고 가정한다.
- 주소공간의 크기가 너무 크지 않다고 가정한다. (물리 메모리 크기보다 작다.)
- 각 주소 공간의 크기가 같다고 가정한다.

## 사례

1. 가상 주소 공간 설정
   - 프로세스마다 독립적인 가상 주소 공간이 할당됩니다. 이 예제에서는 16KB의 가상 주소 공간이 할당되었습니다.
   - 가상 주소 공간은 프로세스가 메모리에 접근할 때 사용하는 논리적인 주소 공간입니다.
2. 물리 메모리 공간 설정
   - 실제 물리 메모리는 32KB로 가정합니다.
   - 물리 메모리는 시스템의 실제 하드웨어 메모리를 의미합니다.

```c
// 가상 주소 공간 크기: 16KB
// 물리 메모리 크기: 32KB

#define VIRTUAL_MEM_SIZE (16 * 1024)
#define PHYSICAL_MEM_SIZE (32 * 1024)

char virtual_mem[VIRTUAL_MEM_SIZE];
char physical_mem[PHYSICAL_MEM_SIZE];

// 가상 주소를 물리 주소로 변환하는 함수
char* translate_address(char* virtual_addr) {
    // 가상 주소 공간 내에 있는지 검사
    if (virtual_addr >= virtual_mem && virtual_addr < virtual_mem + VIRTUAL_MEM_SIZE) {
        // 가상 주소와 물리 주소 사이의 오프셋 계산
        int offset = virtual_addr - virtual_mem;

        // 오프셋을 이용하여 물리 주소 계산
        char* physical_addr = physical_mem + offset;

        return physical_addr;
    } else {
        // 가상 주소 공간 밖의 주소에 접근하려는 경우 예외 처리
        printf("Invalid virtual address access!\n");
        return NULL;
    }
}

int main() {
    // 가상 주소 공간에 데이터 쓰기
    virtual_mem[0] = 'A';
    virtual_mem[1] = 'B';

    // 가상 주소를 물리 주소로 변환하여 접근
    char* physical_addr = translate_address(virtual_mem);
    printf("Physical data: %c%c\n", physical_addr[0], physical_addr[1]);

    return 0;
}
```

translate_address 함수는 주어진 가상 주소가 가상 주소 공간 내에 있는지 검사한 후, 그렇다면 가상 주소와 물리 주소 사이의 오프셋을 계산하여 실제 물리 주소를 계산합니다.

main 함수에서는 가상 주소 공간에 'A'와 'B'를 씁니다. 그리고 translate_address 함수를 호출하여 가상 주소를 물리 주소로 변환한 후, 해당 물리 주소에 저장된 데이터를 출력합니다.

## 동적 (하드웨어-기반) 재배치

각 CPU 마다, 2 개의 하드웨어 레지스터가 필요하다. 하나는 베이스 레지스터라고 불리고 다른 하나는 바운드 레지스터 혹은 한계 레지스터라고 불린다. 이 베이스와 바운드쌍은 우리가 원하는 위치에 주소 공간을 배치할 수 있게한다. 배치와 동시에 프로세스가 오직 자신의 주소 공간에만 접근한다는 것을 보장한다.

이 설정에서 각 프로그램은 주소 0에 탑재되는 것처럼 작성되고 컴파일된다. 프로그램 시작시, 운영체제가 프로그램이 탑재될 물리 메모리 위치를 결정하고 베이스 레지스터를 그 주소로 지정한다.
프로세스에 의해 생성되는 모든 주소가 다음과 같은 방법으로 프로세서에 의해 변환된다.

Physical Address = Virtual Address + bios

프로세스가 생성하는 메모리 참조는 가상주소다. 하드웨어는 베이스 레지스터의 내용을 이 주소에 더해서 물리주소를 생성한다.

가상주소에서 물리주소로의 변환이 주소변환이라고 하는 기술이다. 하드웨어는 프로세스가 참조하는 가상주소를 받아들여 데이터가 실제로 존재하는 물리주소로 변환한다. 이 주소의 재배치는 실행시에 일어나고, 프로세스가 실행을 시작한 이후에도 주소공간을 이동할 수 있기 때문에, 동적 재배치(dynamic relocation)이라고도 불린다.

바운드 레지스터는 보호를 지원하기 위해 사용된다. 프로세서는 메모리 참조가 합법적인가를 확인하기 위해 가상주소가 바운드 안에 있는지 확인한다. 바운드의 요점은 프로세스가 생성한 모든 주소가 합법적이고 프로세스의 범위에 있다는 것을 확인하는 것이다.

베이스와 바운드 레지스터는 CPU칩에 존재하는 하드웨어 구조다. 주소 변환에 도움을 주는 프로세서의 일부를 메모리 관리 장치(MMU : Memory Management Unit) 라고 부른다.

바운드 레지스터는 두 가지 방식 중 하나로 정의될 수 있다. 한 가지 방법은 주소 공간의 크기를 저장하는 방식으로 하드웨어는 가상주소를 베이스 레지스터에 더하기 전에 먼저 바운드 레지스터와 비교한다. 두 번째 방식은 주소공간의 마지막 물리주소를 저장하는 방식으로 하드웨어는 먼저 베이스 레지스터를 더하고 그 결과가 바운드 안에 있는지를 검사한다.

가상주소가 너무 크거나 음수일 경우 폴트를 일으키고 예외가 발생한다.

## 예제: 베이스와 바운드를 이용한 주소 변환 [^kodahee0]

[^kodahee0]: [고다희](https://github.com/kodahee0)

베이스와 바운드 레지스터를 활용한 주소 변환 과정을 더 자세히 이해하기 위해 간단한 예시를 살펴보겠습니다.

가상 주소 공간의 크기가 4KB인 프로세스가 있다고 가정해 보죠. (현실에서는 매우 작은 크기이지만, 이해를 돕기 위한 예시입니다.) 이 프로세스는 물리 메모리의 16KB 지점에 적재되어 있습니다. 이 경우 주소 변환은 다음과 같이 이루어집니다.

| 가상 주소 | 물리 주소          |
| --------- | ------------------ |
| 0         | 16KB               |
| 1KB       | 17KB               |
| 3000      | 19384              |
| 4400      | 폴트 (바운드 초과) |

이 예시에서 알 수 있듯이, 물리 주소를 얻으려면 가상 주소에 베이스 레지스터의 값(여기서는 16KB)을 더해주면 됩니다. 가상 주소는 프로세스의 주소 공간 내에서의 오프셋(offset)으로 생각할 수 있습니다.

만약 가상 주소가 주소 공간의 크기(여기서는 4KB)를 초과하거나 음수라면, 즉 바운드 레지스터로 정의된 범위를 벗어나면 폴트(fault)가 발생하고 예외(exception)가 처리됩니다.

여기서 사용된 주요 용어들을 정리해 보겠습니다.

- 베이스 레지스터(base register): 프로세스의 물리 메모리 시작 주소를 가리키는 레지스터입니다. 주소 변환 시 가상 주소에 더해집니다.

- 바운드 레지스터(bound register): 프로세스의 주소 공간 크기를 나타내는 레지스터입니다. 가상 주소가 이 범위 내에 있는지 확인하는 데 사용됩니다.

- 가상 주소(virtual address): 프로세스가 사용하는 주소 공간 내의 주소입니다. 0부터 시작하여 프로세스 주소 공간의 크기까지의 값을 가집니다.

- 물리 주소(physical address): 실제 물리 메모리 상의 주소입니다. 가상 주소를 베이스 레지스터의 값과 더함으로써 얻어집니다.

- 폴트(fault): 가상 주소가 유효하지 않을 때(바운드를 초과하거나 음수일 때) 발생하는 예외 상황입니다. 운영체제는 폴트를 처리하여 프로세스를 안전하게 종료시키거나 다른 적절한 조치를 취합니다.

이처럼 베이스와 바운드 레지스터를 사용한 주소 변환은 프로세스 별로 독립적인 주소 공간을 제공하면서, 동시에 잘못된 메모리 접근으로부터 시스템을 보호하는 간단하면서도 효과적인 메커니즘입니다.

## 하드웨어 지원: 요약

- 커널, 유저 모드가 구분되어야 한다.
- 베이스, 바운드 레지스터를 제공해야 한다.
- 베이스, 바운드 레지스터로 가상 주소를 변환하는 능력, 잘못된 가상 주소 공간을 체크하는 능력을 제공해야 한다.
- 오류가 발생했을 때 처리할 수 있는 능력이 있어야 한다.
- 오류가 발생했을 때 오류를 발생할 수 있어야 한다.

## 운영체제 이슈

베이스와 바운드 방식의 가상 메모리 구현을 위해서 운영체제가 반드시 개입되어야 하는 중요한 세 개의 시점이 존재한다.

- 첫째, 프로세스가 생성될 때 운영체제는 주소공간이 저장 될 메모리 공간을 찾아 조치를 취해야 한다. 운영체제는 물리 메모리를 슬롯의 배열로 보고 각 슬롯의 사용여부를 관리하다. 새로운 프로세스가 생성되면 운영체제는 새로운 주소 공간 할당에 필요한 영역을 찾기 위해 자료구조를 검색해야 한다. 검색을 통해 선택된 공간을 사용 중이라고 표시한다.
- 둘째, 프로세스가 종료할 때, 정상적으로 종료되거나 잘못된 행동을 하여 강제적으로 죽게될 때 프로세스가 사용하던 메모리를 회수하여 다른 프로세스나 운영체제가 사용할 수 있게 한다. 프로세스가 종료하면, 운영체제는 종료한 프로세스의 메모리를 다시 빈 공간 리스트에 넣고 연관된 자료구조를 모두 정리한다.
- 셋째, 운영체제는 문맥교환이 일어날 때에도 몇 가지 추가 조치를 취해야 한다. CPU마다 한 쌍의 베이스-바운드 레지스터만 존재한고, 각 프로그램은 다른 물리주소에 탑재되어야 하기 때문에 실행중인 프로그램마다 다른 값을 가진다. 운영체제는 프로세스 전환시 베이스와 바운드 쌍을 저장하고 복원해야 한다.
- 운영체제가 실행 중인 프로세스를 중단시키기로 결정하면 운영체제는 메모리에 존재하는 프로세스 별 자료구조 안에 베이스와 바운드 레지스터의 값을 저장해야 한다. 이 자료구조는 프로세스 구조체 또는 프로세스 제어블록(PCB)이라고 불린다. 운영체제는 실행 중인 프로세스를 다시 시작할 때 또는 처음 실행시킬 때, 이 프로세스에 맞는 값으로 CPU의 베이스와 바운드 값을 설정해야 한다. 프로세스가 중단되면, 운영체제가 메모리의 현 위치에서 다른 위치로 주소 공간을 비교적 쉽게 옮길 수 있다. 프로세스의 주소 공간을 이동시키려면 운영체제는 먼저 프로세스의 실행을 중지시킨다. 그런 후 운영체제는 현재 위치에서 새 위치로 주소공간을 복사한다. 마지막으로, 운영체제는 프로세스 구조체에 저장된 베이스 레지스터를 갱신하여 새 위치를 가리키도록 한다. 프로세스가 실행을 재개하면 새로운 베이스 레지스터가 복원되고 다시 실행을 시작하고, 명령어와 데이터가 전혀 다른 새 위치에 존재한다는 사실을 인식하지 못한다.
- 넷째, 운영체제는 예외 핸들러 또는 호출될 함수를 제공해야 한다. 운영체제는 부팅할 때, 특권 명령어를 사용하여 이 핸들러를 설치한다. 프로세스가 바운드 밖의 메모리에 접근하려는 경우 CPU는 예외를 발생시킨다. 운영체제는 불법 행위를 한 프로세스를 종료한다.

## 요약

- 주소 변환은 프로세스에게 투명하다
- OS는 프로세스로부터 메모리 접근을 통제 가능
- 주소 공간의 바운드로 접근을 보장한다.
- 하드웨어의 지원으로 효율성이 보장됨
