메모리 관리와 페이징 기법
목표
- 프로그래밍할 때 physical 메모리를 신경 쓰지 않도록 abstraction을 제공한다.
physical memory address와 분리된 logical memory address를 사용해서
프로세스마다 0번지부터 최대 번지까지의 메모리를 사용할 수 있다고 가정하고 프로그래밍한다.
- 메모리 자원을 오버헤드를 줄이면서 최적의 성능을 내도록 할당한다.
- 프로세스 간 메모리 isolation을 제공한다. 공유 메모리를 설정하지 않으면 별개의 메모리를 가지고 동작한다.
시스템 구분
- Batch Programming: 메모리 관리가 필요 없고 실행되어야 하는 태스크를 실행시키고 실행이 끝나면 내려주는 작업만 한다. I/O가 발생하면 처리하는 동안 CPU Utilization이 떨어진다.
- Multiprogramming: 여러 개의 프로세스가 동시에 메모리를 점유한다. 각각의 프로세스는 자기만의 메모리 공간이 필요하다. 프로세스들마다 격리된 메모리 공간을 할당해주는 로직이 필요하다. Partitioning, Paging, Segmentation 등이 있다.
- 메모리 관리에 있어서 필요한 사항들
Protection: 특정 프로세스가 다른 프로세스 메모리공간 침해하면서 의도치 않은 동작 방지.
Fast translation: 필요한 메모리를 빨리 찾을 수 있어야 함.
Fast Context Switching: 컨텍스트 스위칭 빨라야함.
메모리 관리 이슈
- 다수의 프로세스에 격리된 메모리를 할당하고 Protection, 데이터 쉐어링 등의 기능을 제공해야 한다.
- Virtual Memory: 프로그램은 항상 모든 코드나 데이터가 메모리에 올라가 있을 필요는 없다. 읽히지 않는 메모리도 존재하고, 실행되지 않는 branch도 있다. 가상 메모리를 사용하면 이러한 특성을 통해 physical 메모리를 적게 사용하면서 프로그램을 실행할 수 있다. 각 프로세스의 메모리 공간을 격리시키고, 세컨더리 스토리지(디스크)의 일부를 메모리처럼 사용하면서 swap in, swap out을 통해 physical 메모리보다 큰 메모리 공간을 할당할 수 있는 기능을 제공한다. 보통 physical memory의 두 배 이상을 가상메모리로 설정한다.
가상 메모리에서는 메모리를 Physical Memory와 Logical Memory로 구분한다.
Physical Memory는 실제 하드웨어이고 Logical Memory는 Physical Memory를 직접 건드리지 않도록 추상화해놓은 것이다.
메모리 주소 바인딩
- 프로그램이 실행될 때 프로세스가 동작할 수 있는 코드, 데이터 섹션을 메모리에 로드시키는 시점에 따라 3가지로 구분된다.
- Compile Time: 컴파일되면 변수 이름은 사라지고 주소값으로 대체된다. 컴파일하고 어셈블리 코드 실행을 보면 변수 이름이 없는 이유이다. 메모리 주소로 매핑되어서 직접 메모리 주소를 사용한다. 컴파일 타임에 메모리 주소를 바인딩한다는 것은 바이너리 이미지를 만드는 시점에 결정한다는 것이다. 컴파일할 때 어디서부터 어디까지의 주소를 사용한다고 지정한다. 실제로 메모리에 올라갈 때 컴파일 타임에 지정된 주소로 할당된다. 실제로는 잘 사용되지 않는다. 펌웨어 레벨이나 임베디드 시스템에서는 사용된다. 다른 프로그램이 실행될 때 똑같은 메모리 주소를 가진다면 프로그램을 올리지 못한다.
- Load Time: 컴파일 타임의 문제를 해결하기 위해서 로드하는 시점에 피지컬 메모리에 만들어질 위치를 결정한다. 프로그램 크기를 지정하고 운영체제가 메모리를 보고 연속적으로 할당할 수 있는 공간을 찾아서 넣어준다. 그럼 시작 위치를 base address로 하고 프로세스 내에서의 위치에 base address를 더하면 정상적으로 동작한다. 메모리 참조값을 base가 어딘지에 따라서 로드 타임에 주소 연산해줘야 하고, 실행되지 않을 수도 있어도 모든 분기에 대해서 처리를 해줘야 한다. 따라서 로드 시간이 굉장히 길어질 수 있다.
- Execution Time: 동일하게 로드 타임에 적절한 위치에 할당받고 코드에 있는 주소값을 그대로 가져와서 수행 시에 base address와 계산해서 처리한다. 실행 시간이 늘어날 위험이 있지만 최적의 방법이다. 주소 연산 오버헤드를 줄이기 위해서 MMU라는 유닛을 만들었다. 특수한 목적의 하드웨어이다. 주소 연산에 특화되어 있으며 로지컬 주소를 피지컬 주소로 변경하는 역할을 한다. 저사양 컴퓨터는 MMU가 없다. MPU라는 프로텍션 기능만 가진 유닛이 존재한다. MMU는 CPU 대부분에 존재하고 로지컬 메모리를 MMU에 CPU가 전달하면 피지컬 메모리로 전환해준다. 피지컬 메모리는 프로그램 실행 시마다 달라질 수 있다. 하지만 변수의 주소값을 출력해보면 항상 같게 나오는 것을 확인할 수 있다. 따라서 로지컬 메모리를 통해 abstraction되어 있고 프로그래밍 시에 실제로 접근하는 주소는 로지컬 메모리인 것을 확인할 수 있다.

메모리 주소 할당
- Contiguous Allocation: 연 연속적으로 메모리를 할당하는 방법이다. 다른 프로세스의 메모리 영역을 침범하지 않는지 계산하고 메모리를 할당한다. 단점은 프로세스가 종료되고 메모리를 반납했을 때 빈 공간이 생기는데, 다른 프로세스를 빈 공간에 할당하면 프로세스 크기 차이에 따라서 남는 공간이 발생한다. 이 공간이 하나의 프로세스를 돌리기에 부족하다면 해당 공간은 사용할 수 없다. 프로세스가 생겼다 없어지는 과정에서 hole이 다수 발생하면 memory Utilization이 떨어진다. 이를 해결하기 위해서 hole들을 모아서 하나의 큰 블록으로 재배치하는 방법이 있다. 이를 compaction이라고 한다. 하지만 I/O가 많이 발생하고 프로세스들을 복사해서 재배치하는 과정의 오버헤드가 크기 때문에 잘 쓰이지 않는다. Hole의 사이즈를 최대한 줄이는 것이 중요하므로 새로 올라오는 프로세스를 어느 hole에다 할당할 것이냐가 중요하고, first fit, best fit, worst fit 방식이 존재한다. First fit은 메모리를 스캔하다가 첫 번째 발생하는 빈 공간에 메모리를 할당하는 방법이다. Best fit은 가장 크기가 비슷한 hole에다가 프로세스를 할당하는 방식으로 홀의 개수, 위치, 사이즈를 운영체제가 관리해야 하는 오버헤드가 존재한다. Worst fit은 가장 큰 홀에 먼저 프로세스를 할당하는 방식이다. 실제로는 first와 best fit의 성능이 비슷하게 나온다. 어떤 방식을 사용해도 hole이 발생하고 프로세스 사이에서 생기는 홀로 인해 메모리가 단편화되는 현상을 external fragmentation이라고 한다. Compaction으로 해결하는 방법이 있으나 I/O로 인한 오버헤드 문제가 발생한다.