본문 바로가기

CS

쉽게 배우는 운영체제 | ch7. 물리 메모리 관리

메모리 관리의 개요

메모리 관리의 복잡성

CPU는 메모리에 있는 내용을 가져오거나 작업 결과를 메모리에 저장하기 위해 메모리 주소 레지스터를 사용한다. 메모리 주소 레지스터에 필요한 메모리 주소를 넣으면 데이터를 메모리에서 가져오거나 메모리에 데이터를 옮길 수 있다.

폰 노이만 구조의 컴퓨터에서 메모리는 유일한 작업 공간이며 모든 프로그램은 메모리에 올라와야 실행 가능하다. 과거의 일괄 처리 시스템에서는 한 번에 한가지 작업만 처리했기 때문에 메모리 관리가 어렵지 않았다. 그러나 오늘날의 시분할 시스템에서는 운영체제를 포함한 모든 응용 프로그램이 메모리에 올라와 실행되기 때문에 메모리 관리가 복잡하다. 복잡한 메모리 관리는 메모리 관리 시스템이 담당한다.

메모리 관리의 이중성

메모리 관리의 이중성이란 프로세스 입장에서는 메모리를 독차지하려고 하고, 메모리 관리자 입장에서는 되도록 관리를 효율적으로 하고 싶어하는 것을 말한다.

 

프로세스가 작업하는 도중에 할당된 공간이 부족하면 메모리 관리자는 새로운 공간을 확보하기 위해 옆의 프로세스를 밀어내거나 더 큰 공간으로 해당 프로세스를 옮겨준다. 작업을 마치고 난 후 빈공간이 생기면 다음 작업을 위해 빈 공간을 어떻게 처리할지도 결정한다.

 

메모리를 계층적 구조로 만들어 작업 속도를 올리고 가격을 낮추는 방법을 계층적 메모리 구조라고 한다. 메모리 관리의 복잡성은 충분히 크지 않은 메모리에서 여러 작업을 동시에 실행하는 문제에서 비롯되었다. 작업 공간이 넉넉지 않기 때문에 모든 프로그램은 메모리에서 자기 공간을 확보하기 위해 치열하게 싸운다.

소스코드의 번역과 실행

기계어와 어셈블리어는 컴퓨터의 동작을 가장 직접적으로 표현한 언어로 저급 언어라고 한다. 저급 언어와 반대되는 개념인 고급 언어는 사용자가 이해하기 쉽게 프로그래밍할 수 있는 언어로, C언어와 자바가 대표적인 예이다.

 

컴파일러를 사용하는 목적은 다음과 같다.

  • 오류 발견: 컴파일러의 첫 번째 목적은 소스코드에서 오류를 발견하여 실행시 문제가 없도록 하는 것이다. 컴파일러는 오류를 찾기 위해 심벌 테이블을 사용한다. 심벌 테이블은 변수 선언부에 명시한 각 변수의 이름과 종류를 모아놓은 테이블로, 선언하지 않은 변수를 사용하지는 않았는지, 변수에 다른 종류의 데이터를 저장하지는 않았는지를 알 수 있다.
  • 코드 최적화: 컴파일러의 두번째 목적은 최적화이다. 컴파일러는 실행하기 전에 코드를 점검하여 오류를 수정하고 최적화함으로써 작고 빠른 실행 파일을 만든다.

컴파일러를 사용하는 프로그래밍 언어는 사용할 변수를 먼저 선언한 후 코드를 작성한다. 컴파일러는 실행 전에 소스코드를 점검하여 오류를 수정하고 필요 없는 부분을 정리하여 최적화된 실행파일을 만든다. 그러나 인터프리터는 한 줄씩 위에서부터 아래로 실행되기 때문에 같은 일을 반복하는 경우나 필요없는 변수를 확인할 수 없다. 따라서 크고 복잡한 프로그램에서는 컴파일러를 사용하고 간단한 프로그램에서는 인터프리터를 사용한다.

컴파일 과정

  1. 소스코드 작성 및 컴파일: 프로그래머가 C언어나 자바로 소스코드를 작성하여 컴파일하면 목적 코드가 만들어진다.
  2. 목적코드와 라이브러리 연결: 목적 코드가 만들어지면 라이브러리에 있는 코드를 목적 코드에 삽입하여 최종 실행 파일을 만든다.
  3. 동적 라이브러리를 포함하여 최종 실행: 오늘날의 프로그래밍에서는 예를들어 printf 코드를 가져올 때 printf문의 자리를 비워놓고 컴파일 한 후 실행할 때 printf문의 실행코드를 라이브러리에서 가져와 실행하는 방법을 사용한다. 이렇게 실행할 때 삽입되는 함수를 가진 라이브러리를 동적 라이브러리(dynamic library)라고 한다.

메모리 관리자의 역할

메모리 관리는 메모리 관리자가 담당한다. 메모리 관리자는 정확히 말해 메모리 관리 유닛(Memory Management Unit)이라는 하드웨어인데 일반적으로 메모리 관리자라고 부른다. 메모리 관리자의 작업은 가져오기(fetch), 배치(placement), 재배치(replacement)이다.

  • fetch: 프로세스와 데이터를 메모리로 가져오는 작업. 메모리 관리자는 사용자가 요청하면 프로세스와 데이터를 모두 메모리로 가져온다. 메모리 관리자는 사용자의 요청이 없더라도 앞으로 필요할 것이라고 예상되는 데이터를 미리 가져오기도 한다.
  • placement: 가져온 프로세스와 데이터를 메모리의 어떤 부분에 올려 놓을지 결정하는 작업이다. 배치 작업 전에 메모리를 어떤 크기로 자를 것인가가 매우 중요하다. 같은 크기로 자르느냐, 실행되는 프로세스의 크기에 맞게 자르느냐에 따라 메모리 관리의 복잡성이 달라지기 때문이다.
  • replacement: 새로운 프로세스를 가져와야 하는데 메모리가 꽉 찼다면 메모리에 있는 프로세스를 하드 디스크로 옮겨 놓아야 새로운 프로세스를 메모리에 가져올 수 있다. 이처럼 꽉 차 있는 메모리에 새로운 프로세스를 가져오기 위해 오래된 프로세스를 내보내는 작업이 재배치 작업이다.

메모리 관리자는 fetch, placement, replacement 작업시 다음과 같은 정책을 수립해 그 정책에 따라 메모리를 관리한다.

  • fetch 정책: 프로세스가 필요로하는 데이터를 언제 메모리로 가져올지 결정하는 정책이다. 프로세스가 요청할 때 메모리로 가져오는 것이 일반적이지만, 필요하다고 예상되는 데이터를 미리 가져오는 방법(prefetch)도 있다.
  • placement 정책: 메모리를 같은 크기로 자르는 것을 페이징(paging)이라고 하며, 프로세스의 크기에 맞게 자르는 것을 세그먼테이션(segmentation)이라고 한다. 배치 정책은 페이징과 세그먼테이션의 장단점을 파악하여 메모리를 효율적으로 관리할 수 있도록 정책을 만드는 것이다. 이는 한정된 메모리를 효율적으로 사용하기 위한 것으로, 시스템의 효율을 좌우하는 매우 중요한 기준이다. 페이징은 책의 모든 페이지가 같은 크기인 데에서 유래한 용어이다.
  • replacemnet 정책: 메모리가 꽉 찼을 때 메모리 내에 있는 어떤 프로세스를 내보낼지 결정하는 정책.

메모리 주소

32bit CPU와 64bit CPU의 차이

CPU의 비트는 한번에 다룰 수 있는 데이터의 최대 크기를 의미한다. CPU의 내부 부품은 이 비트를 기준으로 제작된다. 32bit 대역폭의 버스를 통해 한번에 옮겨지는 데이터의 크기는 당연히 32bit이다. CPU의 비트는 메모리 공간 주소(address space)와도 연관이 있다. 32bit CPU의 경우 메모리 주소를 지정하는 레지스터인 메모리 주소 레지스터(MAR)의 크기가 32bit이므로 표현할 수 있는 메모리 주소의 범위가 0~2^32 - 1, 총 개수가 2^32개이다. 이를 16진수로 나타내면 00000000~FFFFFFFF이며 총 크기는 약 4GB이다. 따라서 32bit CPU컴퓨터는 메모리를 최대 4GB까지 사용할 수 있다.

메모리에는 주소 공간이 있다. 메모리의 주소 공간을 물리 주소공간(physical address space)라고 한다. 물리 주소 공간은 하드웨어 입장에서 바라본 주소 공간으로 컴퓨터마다 그 크기가 다르다. 이와 반대로 사용자 입장에서 바라본 주소 공간은 논리 주소 공간(logical address space)라고 한다.

 

상대 주소는 사용자 프로세스 입장에서 운영체제가 어디서 끝나는지, 자신의 데이터가 어디에 존재하는지 알 필요 없이 주소 공간이 항상 0번지부터 시작하는데, 이러한 주소 공간을 논리 주소 공간이라고 부른다. 앞에서 설명했듯이 논리 주소 공간은 물리 주소 공간의 상대적인 개념이다. 즉 논리 주소 공간은 상대 주소를 사용하는 주소 공간이고, 물리 주소 공간은 절대주소를 사용하는 공간이다.

단일 프로그래밍 환경에서의 메모리 할당

메모리 오버레이

프로그램의 크기가 실제 메모리(물리 메모리)보다 클 때 프로그램을 메모리에 가져오는 대신 적당한 크기로 잘라서 가져오는 기법을 메모리 오버레이(memory overlay)라고 한다. 메모리 오버레이는 하나의 메모리에 여러 프로그램을 겹겹이 쌓아놓고 실행하는 것을 말한다.

메모리 오버레이의 경우 프로그램을 몇개의 모듈로 나누고 필요할 때마다 모듈을 메모리에 가져와 사용한다. 메모리 오버레이에서 어떤 모듈을 가저오거나 내보낼지는 CPU 레지스터 중 하나인 프로그램 카운터(PC)가 결정한다. 프로그램 카운터는 앞으로 실행할 명령어의 위치를 가리키는 레지스터로, 해당 모듈이 메모리에 없으면 메모리 관리자에게 요청하여 메모리로 가져오게 한다.

메모리 오버레이는 다음과 같은 중요한 의미를 지닌다.

  • 한정된 메모리에서 메모리 보다 큰 프로그램이 실행가능.
  • 프로그램 전체가 아니라 일부만 메모리에 올라와도 실행가능.

스왑

메모리가 모자라서 쫓겨난 프로세스는 저장장치의 특별한 공간에 모아두는데 이러한 영역을 스왑 영역(swap area)라고 부른다. 그리고 스왑 영역에서 메모리로 데이터를 가져오는 작업은 스왑인, 메모리에서 스왑 영역으로 데이터를 보내는 작업은 스왑 아웃(swap out)이라고 한다.

스왑 영역은 메모리 관리자가 관리한다. 메모리 오버레이에서는 메모리 보다 큰 프로그램을 실행할 때 프로그램을 메모리보다 작은 크기의 모듈로 나누어서 사용한다. 여기에 스왑을 이용하면 스왑영역의 크기가 메모리의 크기로 인식된다. 스왑 영역은 하드디스크에 존재하지만, 메모리 관리자가 관리하기 때문에 디스크 관리자는 그 안에 어떤 내용이 있는지, 어떻게 관리되는지 알 수 없다.

스왑 영역의 크기는 대개 운영체제가 하드디스크의 남은 공간과 메모리 사용량을 고려하여 유동적으로 관리한다.

다중 프로그래밍 환경에서의 메모리 할당

메모리 분할 방식

메모리를 어떤 크기로 나눌 것인가는 메모리 배치 정책에 해당된다. 메모리에 여러개의 프로세스를 배치하는 방법은 크게 가변 분할 방식(variable-size partitioning)과 고정 분할 방식 (fixed size partitioning)으로 나뉜다.

  • 가변 분할 방식: 프로세스의 크기에 따라 메모리를 나누는 것이다. 프로세스의 크기에 맞게 메모리가 분할되므로 메모리의 영역이 각각 다르다. 한 프로세스가 연속된 공간에 배치되기 때문에 연속 메모리 할당(contiguous memory allocation)이라고 한다.
  • 고정 분할 방식: 프로세스의 크기와 상관없이 메모리를 같은 크기로 나누는 것이다. 프로세스의 크기에 상관없이 메모리가 같은 크기로 나뉘며, 큰 프로세스가 메모리에 올라오면 여러 조각으로 나뉘어 배치된다. 한 프로세스가 분산되어 배치되기 때문에 비연속 메모리 할당 (noncontiguous memory allocation)이라고 한다.

현대의 운영체제에서 메모리 관리는 기본적으로 고정 분할 방식을 사용하면서 일부분은 가변 분할 방식을 혼합하고 있다.

가변 분할 방식의 메모리 관리

가변 분할 방식은 빈 영역이 있어도 서로 떨어져 있으면 프로세스를 배정하지 못한다. 이로 인해 작은 조각들이 발생하는 현상을 단편화(fragmentation)또는 조각화라고 한다. 가변 분할 방식에서 발생하는 작은 빈 공간을 외부 단편화 (external fragmentation)이라고 한다. 프로세스의 바깥쪽에 조각이 발생하기 때문에 이렇게 불리는 것이다. 가변 분할 방식에서는 외부 단편화로 인한 문제를 해결하기 위해 메모리 배치 방식이나 조각 모음(defragmentation)을 사용한다.

외부 단편화는 하드 디스크와 같은 저장장치에서도 발생한다. 빈 하드 디스크에 데이터를 채우면 데이터가 차곡차곡 쌓이다가 데이터의 삭제와 저장을 반복하면 데이터가 여러 공간에 나뉘어 저장된다. 이러한 현상은 시간이 흐를수록 더욱 심해지고 결국 하드디스크의 데이터 입출력 속도를 떨어뜨려 컴퓨터의 성능을 저하시키는 원인이 된다. 따라서 하드디스크와 같은 저장장치도 성능을 유지하려면 주기적으로 조각 모음을 해야한다.

 

가변 분할 방식의 외부 단편화 문제를 해결하기 위한 대표적인 메모리 배치 방식으로는 최초배치, 최적 배치, 최악 배치등이 있다. 이러한 방식으로 메모리를 배치해도 당연히 단편화 현상은 발생한다. 원래 가변 분할 방식의 목적은 프로세스를 한 덩어리로 취급하여 메모리 관리의 효율성을 높이는 것인데, 메모리 배치 방식으로는 근본적으로 문제를 해결하지 못한다.

고정 분할 방식의 메모리 관리

가상 메모리 시스템에서 고정 분할 방식을 페이징이라고 한다. 가변 분할 방식과 달리 고정 분할 방식은 프로세스의 크기에 상관없이 메모리를 같은 크기로 나누기 때문에 관리하기가 편하다. 그러나 프로세스가 메모리의 여러 조각에 나뉘어 저장되는 것이 문제이다. 고정 분할 방식은 가별 분할 방식보다 공간을 효율적으로 관리한다. 고정 분할 방식은 가변 분할 방식처럼 조각 모음을 할 필요가 없어 관리가 수월하므로 현대의 메모리 관리 시스템은 고정 분할 방식을 기본으로 사용하고 있다.

고정 분할에서는 일정한 크기로 나뉜 파티션 안쪽에 작은 조각이 발생한다. 이처럼 각 메모리 조각에 프로세스를 배치하고 공간이 남는 현상을 내부 단편화(internal fragmentation)이라고 한다. 같은 크기로 나뉜 공간의 내부에서 발생하기 때문에 이렇게 불리는 것이다. 가변 분할 방식의 외부 단편화는 조각 모음으로 조정했으나 고정 분할 방식에서는 내부 단편화를 해결하기 위해 조각 모음을 할 수 없고, 남은 공간을 다른 프로세스에 배정할 수도 없다. 대신 동일하게 분할되는 공간의 크기를 조절하여 내부 단편화를 최소화한다.

글씨가..

버디 시스템

가변 분할 방식의 단점인 외부 단편화를 완화하는 방법으로 버디 시스템이 있다. 버디 시스템은 가변 분할 방식이지만 고정 분할 방식과 유사한 점이 있다.

버디 시스템의 특징은 가변 분할 방식과 고정 분할 방식의 중간 구조라는 것이다.

 

  1. 프로세스의 크기에 맞게 메모리를 1/2로 자르고 프로세스를 메모리에 배치한다.
  2. 나뉜 메모리의 각 구역에는 프로세스가 1개만 들어간다.
  3. 프로세스가 종료되면 주변의 빈 조각과 합쳐서 하나의 큰 덩어리를 만든다.