메모리 관리의 개념과 정책
메모리 관리의 개념
- 모든 프로그램은 우선 메모리에 적재되어야 실행이 가능하므로 메모리는 프로그램을 실행하는데 중요한 작업 공간이다.
- 메모리 관리는 프로세스들을 위해 메모리를 할당하고 제거하며 보호하는 활동이다. 더 단순하게는 프로세스의 요청에 따라 메모리의 일부를 할당하고, 필요 없으면 자유롭게 재사용할 수 있도록 하는 것이다.
- 디스크에 있는 프로그램을 실행하려면 먼저 메모리에 적재한 후 메모리 관리자가 예약된 메모리를 할당해 주어야 하는데, 이것도 메모리 관리에 해당한다.
- 다중 프로그래밍 시스템에서는 여러 프로세스가 메모리에 상주할 수 있도록 OS 가 동적으로 메모리를 세분화하는 이 과정도 메모리 관리에 해당한다.
메모리 관리는 메모리 관리자가 담당한다. 메모리 관리자는 OS 의 관리 모듈과 메모리 관리장치(MMU, Memory Management Unit) 가 협업하여 관리한다.
메모리 관리 정책
메모리 관리자가 메모리와 관련된 여러 정책을 수립하고 정책에 따라 메모리를 관리한다. 아래는 주요 정책 세 가지 이다.
적재 정책(fetch policy)
디스크에서 메모리로 프로세스를 적재할 시기를 결정하는 것
- 요구 적재 : OS 나 시스템 프로그램, 사용자 프로그램 등 참조 요청에 따라 다음에 실행할 프로세스를 메모리에 적재하는 오래된 방법
- 예상 적재 : 시스템의 요청을 미리 예측하여 메모리에 적재하는 방법이다. 요청된 페이지 외의 다른 페이지도 함께 불러들여 탐색 시간과 회전 지연시간을 가지는 보조기억장치(디스크)의 특성을 참조한다.
배치 정책(placement policy)
디스크에서 반입한 프로세스를 메모리 어느 위치에 저장할 것인지 결정
- 최초 적합 : 사용 가능 공간 리스트에서 충분히 큰 첫 번째 공백 분할 공간에 적재
- 최적 적합 : 사용 가능 공간 리스트에서 가장 작은 크기의 사용 공간을 작업에 적재
- 최악 적합 : 가장 큰 사용 가능 공간에 적재
대치 정책(replacement policy, 재배치, 교체 정책)
메모리가 충분하지 않을 때 현재 메모리에 적재된 프로세스 중 제거할 프로세스를 결정하는 교체 방법
- 시기 및 사용 빈도에 따라 선입 선출 대치 알고리즘, 최근 최소 사용 대치 알고리즘 등 다양한 방법이 가능
메모리의 구조와 매핑(사상)
명령어를 실행하는 과정에서 메모리는 주소의 연속으로, 이 주소는 크게 두 가지 관점에서 해석할 수 있다.
메모리의 구조
논리적 주소
- 프로그래머가 프로그래밍에 사용하는 공간으로 보는 논리적 관점의 논리적 주소
- CPU가 생성하는 주소를 일반적으로 논리 주소라고 한다.
- 논리적 주소는 가상 주소라고도 하는데 목적 코드(object code) 가 저장된 공간과 프로그램에서 사용하는 자료구조 등이 이에 해당
물리적 주소
- 실제 데이터나 프로그램을 저장하는 공간으로 보는 물리적 관점의 물리적 주소
- 메모리가 취급하게 되는 주소[즉, 메모리 주소 레지스터(MAR) 에 주어지는 주소]
- 물리적 주소는 논리적 주소에 대응하여 적재하는 실제 주소로, 메모리 칩이나 디스크 공간에서 만든다.
메모리 장치의 주소 변환
- 논리적 주소와 물리적 주소의 변환은 메모리관리장치(MMU) 가 처리한다.
- 메모리관리장치가 논리적 주소를 물리적 주소로 변환할 때는 고정 분할, 동적 분할, 페이징, 세그먼테이션, 페이지화된 세그먼테이션 등 변환 방법을 사용할 수 있다.
매핑(Mapping)
- 해당 프로세스의 논리적 주소에 대응하는 물리적 주소를 알아야 프로세서가 프로세스를 실행할 수 있다.
- 바인딩(binding) : 이 두 주소를 연결, 즉 매핑(mapping) 시켜 주는 작업
C 언어나 자바와 같은 고급 프로그래밍 언어에서는 변수를 사용하여 논리적 주소를 표시하고, 이를 실행하면 물리적 주소로 변환한다.
논리적 주소를 물리적 주소로 변환하는 시점에 따라 바인딩을 컴파일(compile) 시간, 적재 시간, 실행 시간으로 구분할 수 있다.
컴파일 시간
- 프로세스가 메모리에 적재될 위치를 컴파일 과정에서 알 수 있다면 컴파일러는 물리적 주소를 생성할 수 있다.
- 예를 들어, 사용자 프로세스가 위치 R로 시작해서 적재한다고 알려지면 생성된 컴파일러 코드는 그 위치에서 시작하여 확장해 나갈 것이다. 이후에 시작 위치가 변하지 않으면 코드를 다시 컴파일할 필요가 없다.
적재 시간
- 프로세스를 메모리의 어디에 적재해야 할지 컴파일 과정에 알려 주지 않으면 컴파일러는 대체 가능한 상대 주소를 생성한다.
- 상대 주소는 프로그램의 시작 주소가 0으로 생성되므로 최종 바인딩을 적재 시간까지 연기한다.
- 시작 주소가 변하면 단지 변화된 값을 반영하려고 사용자 코드를 재적재한다. 이런 과정을 정적 대치(교체)라고 한다.
실행 시간
- 한 프로그램(프로세스)이 동일한 장소에서 작동한다면 적재 시간 과정에서 바인딩할 수 있다.
- 그러나 프로세스를 실행하는 도중에 메모리의 한 세그먼트에서 다른 세그먼트로 이동한다면 바인딩은 수행 시간까지 지연된다.
- 이런 주소 체계는 기본 및 경계(한계) 레지스터 등 특수 하드웨어의 지원이 필요하다. 현재 범용 OS 대부분은 실행 시간에 바인딩하는 방법을 사용한다.
※ 참고
링커와 로더
컴파일러(compiler)가 원시 코드를 컴파일하여 목적 파일을 생성하면 링커(linker, 연결기) 가 이 목적 파일에 라이브러리 파일이나 다른 목적 파일을 결합한다. 그런 다음 로더(loader, 적재기) 가 지정 위치에서 시작하여 메모리에 프로그램을 배치하는데, 하드웨어 구조에 따라 다음 방법으로 수행한다.
절대 적재 : 프로그램을 메모리의 지정된 위치에 적재한다.
교체 적채 : 프로그램을 메모리의 여러 위치에 적재한다.
메모리 관리 관련 용어
동적 적재(dynamic loading)
초기 컴퓨터 시스템에서는 메모리의 크기가 프로세스의 크기를 제한했다. 그래서 바인딩을 최대한 늦춰 실행 직전에 주소를 확정하면 메모리를 효율적으로 운영할 수 있는 동적 적재(dynamic loading) 방법을 제안하게 되었다.
- 동적 적재는 모든 루틴을 메모리에 적재하지 않고 교체 가능한 형태로 디스크에 저장하며, 메인 프로그램만 먼저 메모리에 적재하여 수행
- 메인 프로그램에 다른 루틴이 필요 할 때 메모리에 적재되어 있는지 조사
- 적재되어 있지 않다면 해당 루틴을 메모리에 적재하려고 호출하면서 프로그램의 주소 테이블을 갱신
- 동적 적재는 사용하지 않을 루틴을 메모리에 적재하지 않으므로 메모리를 효율적으로 사용할 수 있음
- 오류가 발생하기도 하지만 프로그램 전체 양이 많을 때 더 유용
중첩(오버레이)
실행하려는 프로그램이 메모리보다 클 때는 당장 필요하지 않은 프로그램의 일부는 중첩(overlay, 오버레이) 으로 설정할 수 있다.
- OS 영역과 메모리의 일부 영역에는 프로그램 실행에 꼭 필요한 명령어와 데이터만 저장하고, 나머지 중첩 영역에는 필요할 때 호출하여 적재한 방법이다.
구조
실행할 프로그램을 초기, 처리, 출력 모듈로 나누고, 중첩 영역에 초기 모듈을 먼저 적재하여 실행한 후 처리 모듈, 출력 모듈 순으로 실행하는 구조이다.
중첨 예
패스 1에 심벌 테이블을 작성하고 패스 2에 기계어 코드를 생성하는 2단계 어셈블러(2pass assembler)
- 이 어셈블러를 모두 적재하는데 메모리 210KB 필요하지만 200KB 만 사용할 수 있다면, 이 프로세스를 실행하는 것은 불가능하다.
- 패스 1과 패스 2는 기능이 다르므로 동시에 메모리에 있을 필요가 없으니 이 부분은 중첩으로 설정한다.
- 그런 다음 중첩 드라이브를 추가해서 패스 1을 포함한 중첩 A 를 실행하고, 패스 2를 메모리에 불러들여 중첩 B를 실행한다.
중첩은 OS의 지원 없이도 실행 가능하다. 사용자가 파일을 메모리로 읽어 들이고, 그 메모리로 분기하여 새로운 읽기 명령을 수행하는 간단한 파일 구조를 사용하여 중첩을 구현할 수 있다. 그러면 OS 는 평소보다 더 많은 입출력이 있다고만 인식하게 된다.
사용자는 프로그램 구조와 코드, 자료구조를 완전히 이해해야 중첩을 사용할 수 있다. 하지만 프로그램이 점점 커져 확실히 이해하기가 어려우면 현재는 메모리를 확장하고, 중첩은 마이크로 컴퓨터나 실제 메모리 크기가 제한된 하드웨어에서만 사용한다.
스와핑(프로세스 교체)
다중 프로그래밍 환경에서 프로세스는 사용자 프로그램이 끝날 때까지 메모리에 저장되는 원칙을 지켜왔다. 그러나 이 방법은 프로세스를 빈번하게 교환하는 순환 할당 알고리즘이나 우선순위에 바탕을 둔 알고리즘에는 적합하지 않다.
- 프로세서 할당이 끝나고 수행이 완료된 프로세스는 보조기억장치로 보내고(스왑 아웃), 새롭게 시작하는 프로세스는 메모리에 적재해야 한다(스왑 인)
- 물론 프로세스는 메모리에 있어야 수행되므로 일시적으로 디스크로 이동했다가 메모리로 되돌아와 다시 수행할 수 있다.
프로세스의 스와핑 과정
예를 들어, 순환 할당(Round-Robin) 프로세서 스케줄링 알고리즘에서는 프로세서 할당 시간이 지나면 실행 중인 프로세스를 디스크로 옮기고 다른 프로세스를 메모리로 가져온다.
스와핑(swapping)은 중기 스케줄링에 해당하므로, 중기 스케줄러가 디스크에 저장된 프로세스를 메모리로 옮기고, 메모리에 적재된 프로세스는 준비 큐에서 대기하도록 하는 과정이다.
실행 중에 스와핑 대상이 되면 대기 상태로 바뀌고, 다시 실행 조건을 만족하면 준비 큐로 이동하거나 디스크로 이동하는 스왑 아웃을 한다. 스왑 대기 상태의 프로세스는 실행 조건을 만족하면 스왑 준비 상태로 바뀐다.
이때 우선순위가 더 높은 프로세스가 도착하면 메모리관리장치(MMU) 가 이 우선순위가 높은 프로세스와 스와핑할 수 있다. 그러다 우선순위가 높은 프로세스가 실행을 중지하면 우선순위가 낮은 프로세스를 다시 스왑 인하여 계속 실행한다.
이처럼 스와핑은 OS 가 항상 우선순위가 높은 프로세스 공간을 만들 수 있어 시스템에 유연성을 제공한다.
바인딩 방법에 따른 스와핑 과정
컴파일이나 적재 바인딩에서 프로세스를 다른 위치로 이동할 수 없으나, 실행 시간 바인딩에서는 스와핑이 가능하다.
스와핑 속도가 빠른 보조기억장치가 필요하며, 특히 문맥 교환 시간이 중요하다.
프로세스가 100KB 이고 보조기억장치의 전송률이 초당 1MB인 디스크일 때 보고기억장치에서 실제로 프로세스를 전송하는 시간은 100KB/초당 1,000KB = 1/10초 = 100밀리초(ms) 가 된다. 회전 지연시간을 평균 8밀리초로 가정하면 한번 스와핑하는 시간은 108밀리초가 된다. 그리고 스왑 아웃과 스왑 인 둘다 해야 하므로 총 스와핑 시간은 약 216 밀리초가 된다.
각 프로세스를 실행하는 시간이 스와핑 시간보다 길어야 프로세서를 효율적으로 사용할 수 있다. 그러므로 이 순환 할당 프로세서 스케줄링 알고리즘에서는 규정 시간량이 216밀리초보다 커야 실제적으로 처리할 수 있다.
전체 전송시간은 직접적으로 교환하는 메모리양에 비례한다. 어떤 컴퓨터의 메모리가 1MB이고 상주 OS가 100KB라면 이 컴퓨터 프로세스의 최대 크기는 900KB가 된다. 그러나 프로세스는 대부분 여럿이므로 100KB보다는 훨씬 더 작을 것이다.
프로세스의 크기가 900KB이면 스와핑하는데 908밀리초가 걸리지만, 100KB라면 9 번 스와핑하기 때문에 972밀리초가 걸린다.
따라서 프로세스들의 스와핑 시간을 줄여야 하는데, 시스템에 메모리를 요청하는 정보의 변화를 확인하는 방법을 사용하면 효과적이다. 메모리를 동적으로 요청하는 프로세스에는 OS에 메모리 요청 변화를 제공하는 시스템 호출이 필요하다.
실제 실행 중인 프로세스를 스와핑
실제 실행 중인 프로세스를 스와핑하려면 유휴 상태라는 것을 확인해야 한다. 특히 입출력 대기 상태인지가 중요하다. 입출력을 대기 중이라면 메모리를 회수하도록 스와핑할 수 있으나, 입출력 버퍼가 사용자 메모리를 비동기적으로 액세스하면 스와핑할 수 없다.
이 두 문제는 대기 입출력이 있는 프로세스는 스와핑하지 않고, 입출력 동작이 있는 OS 의 버퍼에서만 스와핑을 수행하여 해결한다. 그리고 OS와 프로세스 메모리 간에는 프로세스가 스와핑되어 들어올 때만 전송해야 한다. 스와핑은 초기 시분할 시스템에서 채택했는데, 최근에는 페이지로 발전했다.
메모리 적재 방법
메모리에 프로세스를 적재하는 방법은 크게 두 가지로 분류한다.
- 연속 메모리 적재 : 연속적으로 적재하는 방법
- 비연속(분산) 메모리 적재 : 페이지나 세그먼트 단위로 나눠 여러 곳에 적재하는 방법
초기 컴퓨터 시스템은 각 프로그램이 연속된 하나의 블록을 차지하도록 연속 메모리 할당을 사용했다.
- 직접 배치, 중첩(오버레이), 고정 분할 방법 등이 이에 해당.
- 그런데 고정 분할 방법은 크기가 다른 프로세스에 모두 같은 크기의 메모리를 할당하여 메모리 낭비를 초래햇다.
- 프로세스의 크기에 따라 메모리를 다르게 분할하는 가변 분할 메모리 할당을 제안하여 이 문제를 해결했다.
- 이를 다중 프로그래밍 방법에 적용하면서 분산 메모리 할당이 유용해졌다.
- 연속 메모리 할당에서는 고정 분할이나 가변 분할이 효과적인 메모리 활용 방법은 아니다.
- 또 내부나 외부 단편화 문제가 발생할 수 있다.
- 물론 제한된 압축을 활용하여 메모리 할당을 변화시켜서 외부 단편화를 해결함녀 연속적인 공간을 만들 수 있지만, 실행 시간을 낭비하는 결과를 가져온다.
- 그래서 외부 단편화를 해결하고 내부 단편화를 최소화하려고 소개한 새로운 방법이 바로 분산 메모리 할당이다.
분산 메모리 할당은 프로그램 하나가 물리적 주소의 여러 공간에 분산해서 올라갈 수 있도록 하는 방법이다.
이 방법도 크게 고정 분할과 가변 분할로 분류할 수 있다.
- 고정 분할에 해당하는 페이징 : 프로그램 하나를 분할하는 기준에 따라 동일한 크기로 나눠 메모리로 적재하는 방법
- 가변 분할에 해당하는 세그먼테이션 : 크기는 일정하지 않지만 의미 단위로 나눠 메모리로 적재하는 방법
참고
- 운영체제:그림으로 배우는 구조와 원리 - 한빛 아카데미