Post

[운영체제 아주 쉬운 세 가지 이야기 - Virtualization] 13. Address Spaces

[운영체제 아주 쉬운 세 가지 이야기 - Virtualization] 13. Address Spaces

이 글은 제 개인적인 공부를 위해 작성한 글입니다.
틀린 내용이 있을 수 있고, 피드백은 환영합니다.


개요


초기에는 사용자가 많은 것을 기대하지 않았기에 컴퓨터 시스템을 구현하는 것이 쉬웠다.

사용의 편이, 고성능, 신뢰성이라는 기대가 생겨나면서 고민할 것들이 늘어났다.


초기 시스템


메모리 관점에서 초기 컴퓨터는 많은 개념을 사용자에게 제공하지 않았다. 컴퓨터의 물리 메모리는 아래 그림과 같이 생겼다.

img

운영체제는 물리 주소 0부터 시작하는 메모리에 상주하는 루틴 (라이브러리)의 집합이었다. 물리 메모리에 64KB에서 시작하는 하나의 실행 중인 프로그램이 존재하였고 나머지 메모리를 사용하였다. 특별한 가상화는 거의 존재하지 않았고 사용자는 운영체제로부터 그리 많은 것을 기대하지 않았다.


멀티프로그래밍과 시분할


시간이 흐른 후, 컴퓨터는 고가 장비였기에 사람들이 더 효과적으로 컴퓨터를 공유하기 시작했다. 멀티 프로그래밍의 시대가 온 것이다.

여러 프로세스가 실행 준비 상태에 있고 운영체제는 그들을 전환하면서 실행하였다. 예를 들어 한 프로세스가 입출력을 실행하면, CPU는 다른 프로세스로 전환하였다. 이런 전환은 CPU의 이용률을 증가시켰고 당시에는 이런 효율성의 개선이 중요하였다.

곧 사람들이 컴퓨터를 더 많이 사용하길 원하기 시작했으며 시분할(time-sharing) 시대가 시작되었다. 많은 사람들이 일괄처리방식(batch computing) 컴퓨터의 한계를 인식하였으며, 특히 오랜 시간이 걸리는 프로그램-디버그 사이클에 지친 프로그래머 자신들이 일괄처리방식 컴퓨텅의 한계를 느꼈다. 많은 사용자가 동시에 컴퓨터를 사용하고, 현재 실행 중인 작업으로부터 즉시 응답을 원하기 때문에 대화식 이용(interactivity)의 개념이 중요하게 되었다.

시분할을 구현하는 한 가지 방법은 하나의 프로세스를 짧은 시간 동안 실행시키는 것이다. 해당 기간 동안 프로세스에게 모든 메모리를 접근할 권한이 주어진다. 그런 후에, 이 프로세스를 중단하고, 중단 시점의 모든 상태를 모든 물리 물리 메모리를 포함하여 디스크 종류의 장치에 저장하고 다른 프로세스의 상태를 탑재하여 또 짧은 시간 동안 실행시킨다. 시분할 시스템을 이런 식으로 엉성하게 구현하였다.

이 방법은 너무 느리게 동작한다는 큰 문제가 있었다. 특히 메모리가 커질수록 느리게 되고, 레지스터 상태를 저장하고 복원하는 것은 빠르지만 메모리의 내용 전체를 디스크에 저장하는 것은 엄청나게 느리다. 우리가 할 일은 아래 그림처럼 프로세스 전환 시 프로세스를 메모리에 그대로 유지하면서, 운영체제가 시분할 시스템을 효율적으로 구현할 수 있게 하는 것이다.

img

시분할 시스템이 대중화되면서 운영체제는 여러 프로그램이 메모리에 동시에 존재하기 위해 보호(protection)이라는 중요한 문제가 생겼다. 우리는 한 프로세스가 다른 프로세스의 메모리를 읽거나 혹은 더 안 좋게는 쓸 수 있는 상황을 원하지 않는다.


주소 공간


우리는 그런 위험한 행위를 하는 사용자를 염두에 두어야 한다. 그런 위험에 대비하여 운영체제는 사용하기 쉬운(easy-to-use) 메모리 개념을 만들어야 한다. 이 개념이 주소 공간(address space), 실행 중인 프로그램이 가정하는 메모리의 모습이다. 운영체제의 메모리 개념을 이해하는 것이 메모리를 어떻게 가상화할지를 이해하는 핵심이다.

주소 공간은 실행 프로그램의 모든 메모리 상태를 가지고 있다.

예를 들어, 프로그램의 코드(code, 명령어)는 반드시 메모리에 존재해야 하고 따라서 주소 공간에 존재한다.

스택은 함수 호출 체인 상의 현재 위치, 지역 변수, 함수 인자와 반환 값 등을 저장하는 데 사용된다.

은 동적으로 할당되는 메모리를 위해 사용된다. C언어의 malloc(), C++나 Java 같은 객체 지향 언어의 new를 통해 메모리를 동적으로 할당받는다.

주소 공간 구성 요소에는 정적으로 초기화된 변수 등의 다른 것들도 있지만, 현재로선 코드, 스택,힙 세 가지만 있다고 가정하자.

아래 그림은 16KB의 아주 작은 주소 공간을 보이고 있다. 프로그램의 코드는 주소 공간의 위쪽에 위치한다. 이 예제에서는 주소 0부터 시작하고, 주소 공간의 첫 1KB를 차지한다. 코드는 정적이기 때문에 메모리에 저장하기 쉽다. 따라서 주소 공간의 상단에 배치하고, 프로그램이 실행되면서 추가 메모리를 필요로 하지 않는다.

다음으로 프로그램 실행과 더불어 확장되거나 축소될 수 있는 두 종류의 주소 공간이 존재한다. 주소 공간 상단에 존재하는 힙과 하단에 존재하는 스택이다. 두 메모리 영역은 확장할 수 있어야 하기 때문에 이런 식으로 배치하고, 주소 공간의 양 끝단에 배치해야 두 영역 모두 확장하는 것이 가능하다. 두 영역은 확장 방향이 반대 방향일 수 밖에 없다.

힙은 코드 바로 뒤 1KB부터 시작하고 아래 방향으로 확장한다. 스택은 16KB에서 시작하고 위쪽 방향으로 확장한다.

그러나 이러한 스택과 힙의 배치는 관례일 뿐이고 원한다면 주소 공간을 다른 방식으로 배치할 수 있다. 나중에 보겠지만 주소 공간에 여러 쓰레드가 공존할 때는 이런 식으로 주소 공간을 나누게 되면 동작하지 않는다.

img

주소 공간을 설명할 때, 운영체제가 실행 중인 프로그램에 제공하는 개념(abstraction)을 설명한다. 실제로 프로그램이 물리 주소 0과 16KB 사이에 존재하는 것은 아니고, 임의의 물리 주소에 탑재된다.

우리의 가장 중요한 문제는 메모리를 어떻게 가상화하는가 이다. 운영체제는 물리 메모리를 공유하는 다수의 프로세스에게 어떻게 프로세스 전용의 커다란 주소 공간이라는 개념을 제공할 수 있는가?

운영체제가 이 일을 할 때, 우리는 운영체제가 메모리를 가상화(virtualizing memory)한다고 말한다. 왜냐하면 실행 중인 프로그램은 자신의 특정 주소의 메모리에 탑재되고 매우 큰 주소 공간을 가지고 있다고 생각하기 때문이다. 현실은 다르다.

예를 들어, 프로세스 A가 가상 주소(virtual address) 0으로부터 load 연산을 수행할 때, 운영체제는 하드웨어의 지원을 통해 물리 주소 0이 아니라 A가 탑재된 물리주소 320KB을 읽도록 보장해야 한다. 이것이 메모리 가상화의 열쇠이고, 현대 모든 컴퓨터 시스템의 기저를 이룬다.


목표


이제 메모리 가상화라는 운영체제의 기능을 논의할 시점이 되었다. 운영체제는 메모리를 가상화할 뿐 아니라 그 가상화를 멋진 방법으로 한다. 운영체제가 가상화를 멋지게 하기 위해서는 몇 가지 목표가 필요하다.

먼저 고립은 신뢰할 수 있는 시스템을 구축하는 데 중요한 원칙이다. 두 개체가 서로 적절하게 고립된 경우, 한 개체가 실패하더라도 상대 개체에 아무 영향을 주지 않는다는 것을 암시한다. 운영체제는 프로세스를 서로 고립시키고 한 프로세스가 다른 프로세스에게 피해를 주는 것을 방지한다. 더 나아가 메모리 고립을 사용하여 운영체제는 프로그램이 운영체제 동작에 영향을 줄 수 없다는 것을 보장한다.

가상 메모리 시스템(VM)의 주요 목표 중 하나는 투명성(transparency)이다. 운영체제는 실행 중인 프로그램이 가상 메모리의 존재를 인지하지 못하도록 가상 메모리 시스템을 구현해야 한다. 프로그램은 메모리가 가상화되었다는 사실을 인지해서는 안된다. 오히려 프로그램은 자신이 전용 물리 메모리를 소유한 것처럼 행동해야 한다. 많은 작업들이 메모리를 공유할 수 있도록, 무대 뒤에서 운영체제와 하드웨어가 모든 작업을 수행한다.

VM의 또 다른 목표는 효율성(efficiency)이다. 운영체제는 가상화가 시간과 공간 측면에서 효율적이도록 해야 한다. 시간적으로는 프로그램이 너무 느리게 실행되서는 안되고 공간적으로는 가상화를 지원하기 위한 구조를 위해 너무 많은 메모리를 사용해서는 안된다. 시간 효율적인 가상화를 구현할 때, 운영체제는 TLB 등의 하드웨어 기능을 포함하여 하드웨어의 지원을 받아야 한다.

마지막으로 VM의 세 번째 목표는 보호(protection)이다. 운영체제는 프로세스를 다른 프로세스로부터 보호해야 하고 운영체제 자신도 프로세스로부터 보호해야 하낟. 프로세스가 탑재, 저장, 혹은 명령어 반입 등을 실행할 때 어떤 방법으로든 다른 프로세스나 운영체제의 메모리 내용에 접근하거나 영향을 줄 수 있어서는 안된다. 즉, 자신의 주소 공간 밖의 어느 것도 접근할 수 있어서는 안된다. 보호 성질을 이용하여 우리는 프로세스들을 서로 고립(isolation)시킬 수 있다. 각 프로세스는 잘못된 혹은 악성 프로세스로부터 안전한 자신 만의 보호막 안에서 실행되어야 한다.

추가로 우리가 보는 모든 주소는 가상 주소이다. C언어에서 포인터를 출력한다면 우리가 보는 값은 가상 주소이다. 사용자 프로그램이 볼 수 있는 주소는 모두 가상 주소이고, 명령어와 데이터가 탑재되어 있는 물리 메모리 주소를 알 수 있는 것은 오직 운영체제뿐이다.


정리


  • 주소 공간 Address space

실행 중인 프로그램이 가정하는 메모리의 모습, 운영체제가 제공하는 메모리 추상화

코드, 스택, 힙으로 구성되어 있음

코드 : 프로그램 명령어, 정적이며 주소 공간 상단에 위치

스택 : 함수 호출, 지역 변수, 함수 인자와 반환 값 등을 저장, 아래 방향으로 확장

힙 : 동적 메모리 할당, 위 방향으로 확장


  • 메모리 가상화

프로그램이 보는 가상 주소를 실제 물리 주소로 변환


  • VM 시스템의 목표

투명성 Transparency : 프로그램이 가상 메모리의 존재를 인지하지 못하도록 함

효율성 Efficiency : 시간적, 공간적으로 효율적인 가상화 구현

보호 Protection : 프로세스 간 메모리 접근 차단, 운영체제 보호

고립 Isolation : 각 프로세스를 독립적인 보호막 안에서 실행


참고

This post is licensed under CC BY 4.0 by the author.