1. 메모리 관련 개념 - 종류, 계층, 워드와 블록, 주소 바인딩(address binding), Dynamic loading, Swapping
2. 메모리 할당
- 연속적 메모리 할당 : Uni programming / Multi programming (FPM, VPM)과 배치 전략
- 비연속적 메모리 할당( 다음에 다룰 것)
00 개요
운영체제는 자원을 잘 관리해 사용자와 응용 프로그램에 서비스를 제공하는 역할을 한다. 지금까지는 운영체제의 기능 중 프로세스 관리에 대해 살펴봤고, 다음에 나올 부분은 메모리 관리다. 운영체제가 메모리를 관리한다는 것은 뭔지, 어떻게 관리를 하는지에 대해 살펴보기 위한 기본적인 개념을 우선 살펴보고 메모리 할당 방식에 대해 알아보았다.
01 메모리, 메모리 관련 개념들
01-1 메모리의 종류
메모리는 레지스터, 캐시, 메인 메모리, 보조기억장치로 구분할 수 있다. 오른쪽으로 갈수록 용량은 크지만 속도가 느리고 저렴하다는 특징이 있다. 이렇게 메모리를 여러 종류로 나눠서 사용하는 이유는 병목 현상을 해소하기 위함이다.
- I/O, 즉 필요한 데이터를 메모리에서 가져와 cpu에서 활용해야 하는데, cpu의 속도는 매우 빠른 반면 메모리에 접근해 데이터를 읽어오는 속도는 상대적으로 매우 느린 편이다.(병목 현상) 따라서 중간중간에 메모리를 추가로 두어 필요한 메모리를 미리 올려놓고 더 빠르게 읽어올 수 있게 하기 위함이다.
- cpu 내부에 존재하는 메모리 : 레지스터, 캐시 - 하드웨어가 관리
- cpu 외부에 존재하는 메모리 : 메인 메모리, 보조기억장치 - 소프트웨어(os)가 관리
01-2 블록과 워드
블록과 워드는 모두 데이터를 읽어오는 단위를 의미하는 개념
- 블록 : 보조기억장치(디스크, SSD 등)에서 메인 메모리로 데이터를 읽어오는(올리는) 단위 - 약 1~4KB 정도
- 워드 : 메인 메모리에서 cpu연산을 위해 레지스터 공간 내부로 올리는 데이터 단위 - 요즘엔 대부분 64비트
흔히들 말하는 '내 컴퓨터는 64bit야'에서 이 64bit가 의미하는 바가 워드라 생각해도 무방함
01-3 address binding
데이터는 실제 메모리 공간의 특정 주소에 저장된다. 코드를 짤 때는 특정 변수를 만들지만, 실제 물리적인 공간의 주소를 이 변수와 연결시키는 작업을 컴퓨터가 하는 것이다. 이때, 이 변수는 데이터의 논리적 주소, 메모리 상의 실제 위치는 물리적 주소라고 하고 이 둘을 연결시키는 작업을 address binding(=mapping)이라고 한다. 당연히 이 주소를 연결시키는 작업을 하지 않는다면, 메모리는 변수의 존재에 대해 모를 수밖에 없다.
이 binding 작업은 매핑을 언제 수행하는지에 따라 크게 세 가지로 구분할 수 있다.
- compile time binding : 코드를 컴파일하는 시점에 바로 주소와 매핑
- 컴파일 하는 시점이란? 소스코드를 오브젝트 모듈로 변환하는 시점
- 초창기 컴퓨터들. 컴파일 타임 바인딩을 하기 위해서는 컴파일러가 현 메모리의 상태를 완벽히 이해하고 있어야 한다는 것과, 한번 메모리에 적재된 이후에는 주소의 변화가 없다는 한계가 있는 방식
- 한 번에 메모리에 바로 올려야 하기 때문에 프로그램 전체를 모두 메모리에 올려야만 함
- load time binding : 컴파일 이후 로드 타임(링커 + 로더)에 매핑
- 링킹이란? 컴파일한 뒤 모듈로 묶고 필요한 라이브러리 모듈도 불러와 연결시켜서 살행 가능한 형태인 모듈로 만드는 과정
- 올라가게 될 메모리 상의 정확한 위치를 알 수 없기 때문에 상대 주소를 활용해 매핑함
- 컴파일 시점에는 상대 주소 t를 연결시키고, 실제 메모리에 적재되었을 때의 위치 h를 가져와 h+t로 주소를 재설정해주는 작업을 함
- 마찬가지로 상대 주소를 미리 정해놓기 때문에, 프로그램을 나누어 올릴 수 없고 전체를 한 번에 올려야 함
- run time binding : 메모리에 올린 뒤 프로그램을 실행하는 도중에 매핑
- 로드 상태까지도 그냥 두었다가 실제 수행이 될 때(ready => running) 시점에 주소를 매핑해줌
- 현재 대부분의 os가 활용하는 방법
- 프로세스는 ready, running, blocked, ready, running 과정을 반복하는 경우가 많기 때문에 런타임 바인딩은 매번 주소가 다시 지정된다. 즉, 수행 도중에 다른 메모리 주소로 이동할 수도 있다. 이를 위해서는 하드웨어의 도움이 필요하기 때문에 MMU(memory management unit)가 이 작업을 수행한다.
/* 참고 */
소스코드가 메모리에 올라 실행되기까지의 과정
1. 소스코드 작성
2. 컴파일 (소스코드를 오브젝트 모듈로 변환)
3. 활용한 라이브러리와 위의 모듈을 묶어 실행 가능한 형태의 모듈로 변환 (=링킹)
4. 위 모듈을 실행해(더블클릭) 메모리에 올리기 (=로더)
(링킹부터 로더까지의 과정이 로드 타임)
01-4 Dynamic loading
사실 메모리에 데이터를 연속되게 올리지 못할 수도 있고, 한 번에 모든 데이터를 올릴 필요가 없기도 하다. 따라서 모든 function을 교체 가능한 형태로 디스크에 저장해놓고 호출이 되었을 경우에만 해당 모듈을 메모리에 올리는 방법이 있는데 이를 다이내믹 로딩이라고 한다. 다이나믹 로딩을 활용하면 메모리 공간을 효율적으로 활용할 수 있다.
01-5 Swapping
- swap out : 프로세스가 레디 상태에 있다가 메모리를 빼앗기는 경우.
- swap device : 메모리를 빼앗긴 시점의 메모리 상태를 저장해두는 곳
- swap in : 프로세스가 다시 메모리에 올라오는 것.
02 메모리 할당 - 연속 할당
(비연속 할당은 다음에 다룰 것)
메모리 할당은 크게 연속 할당과 비연속 할당으로 구분할 수 있다. 이때 연속 할당의 경우, 한 번에 하나의 프로세스만 메모리에 올리는 상황에서의 연속 할당인 유니 프로그래밍과 여러 프로세스를 동시에 올리는 멀티프로그래밍 방식으로 세분화 할 수 있다. 또 멀티 프로그래밍 방식의 경우, 메모리를 할당할 때 어떤 방식으로 메모리를 나누어 할당하는 지에 따라 FPM,VPM으로 나눌 수 있다.
- Uni-programming
- Multi-programming
- Fixed Partition Multi-programming
- Variable Partition Multi-programming
연속 할당이란?
프로세스를 메모리에 올릴 때, 메모리 상의 연속된 공간에 올리는 것. 즉 프로세스를 하나의 연속된 메모리 공간에 할당하는 정책. 이때 고려해야 할 사항들을 구성 정책이라고 함.
- 구성 정책 : 멀티 프로그래밍 디그리(몇 개의 프로세스를 올릴 수 있는가), 메모리 분할 방식, 메모리 할당 방식
02-1 Uni-programming
프로세스가 한 번에 하나만 메모리에 올라가는 경우에 해당되며, 메모리에서 커널이 사용하는 부분을 제외한 유저 사용 영역에 적재해주면 된다. 다만 이때 고려해야 할 점은, 사용할 수 있는 메모리의 크기보다 프로그램의 크기가 클 경우와 커널 보호에 관한 부분이다. 먼저 크기 문제를 해결하기 위해서는 프로그램을 분할해서 메모리에 올리면 되는데 운영체제가 할 수 없는 일이기 때문에 프로그래머가 직접 해야 한다. 따라서 프로그래머가 공통적으로 필요한 부분과 그렇지 않은 부분을 잘 나누기 위해 프로그램을 완벽히 이해하고 있어야 한다.
- Overlay structure : 프로그램을 번갈아 가면서 메모리에 올리는 구조. 현재 필요한 영역만 적재하되 필요 없으면 다시 내리고 다른 영역을 올린다. 이를 위해서는 프로그래머가 프로그램의 자료 구조 및 흐름을 잘 숙지하고 있어야만 한다.
커널 보호의 경우 메모리 상의 커널 영역을 건드리지 않도록 커널 영역에 boundary register를 활용해 해당 주소 영역은 넘지 못하도록 장벽을 쳐 놓는 방법이 있다.
하지만 만약 프로그램의 크기가 가용 메모리의 크기보다 작을 경우 남은 메모리 공간이 낭비된다는 단점이 있다. 메모리 낭비는 곧 시스템 활용도 저하와 시스템 퍼포먼스 저하를 의미하기 때문에 이를 해결하기 위해 멀티프로그래밍 방법을 사용할 수 있다.
02-2 Multi-programming
멀티 프로그래밍 방식의 메모리 할당은 프로세스를 여러 개 메모리에 올리는 경우를 전제한다. 이들 프로세스들에 적절히 메모리를 할당해주기 위해 메모리 공간을 프로세스마다 할당해 줄 필요가 있는데, 이때 메모리를 고정된 크기로 나눌지, 프로세스가 요구하는 크기에 따라 가변적으로 나눌지에 따라 FPM, VPM으로 구분할 수 있다.
02-2-1 Fixed Partition Multi-programming
- Fixed : 고정된
- 고정된 크기로 메모리 공간을 나눠 각 프로세스에 할당해주는 방식
- 각 프로세스가 들어갈 수 있는 메모리 공간에 프로세스를 할당해주는 것으로, 한 공간에는 하나의 프로세스만 할당.
- 결국 파티션의 수(나눈 메모리 공간의 개수)가 멀티프로그래밍 디그리(동시에 돌릴 수 있는 프로세스의 수)를 의미.
- 구현 방법 : 테이블 만들기 - 파티션의 id, 파티션 시작 위치, 파티션의 크기, 사용 중인지 여부, 사용중이라면 어떤 프로세스가 사용중인지 등의 정보를 담아 자료 구조화
- 커널 침범과 서로 다른 파티션으로의 침범을 막기 위해 마찬가지로 boundary register를 활용
하지만, 단편화가 발생할 수 있다.
- 단편화(Fragmentation) : 메모리 상에 남는 공간을 활용하지 못해 낭비하는 것
- 내부 단편화 : 메모리 파티션의 크기가 들어갈 프로세스의 크기보다 커서 남는 공간이 낭비되는 것
- 외부 단편화 : 남은 메모리들의 크기가 프로세스의 크기보다 크지만, 이 공간들이 연속되어있지 않아 사용할 수 없어 메모리가 낭비되는 것
정리해보면, FPM은 메모리 공간을 특정 크기로 분할한 뒤 한 공간에 하나의 프로세스가 사용할 메모리를 올려 돌리는 방법이고, 단순히 크기에 따라 분할하면 되기 때문에 관리나 분할이 간편하지만 (low overhead) 단편화 현상이 발생해 시스템 자원(메모리 공간)을 낭비할 수 있다는 단점이 있다.
02-2-2 Variable Partition Multi-programming
- FPM이 고정된 크기라면, VPM은 variable, 크기를 유동적으로 바꿔 분할하는 메모리 분할 방식
- 처음에는 하나의 영역이지만, 메모리 요청에 따라 동적으로 공간을 분할해 할당해주는 방법으로, 앞서 언급한 내부 단편화 현상은 발생하지 않음.
- 만약 처음에 VPM으로 메모리 공간을 할당한 이후 해당 프로세스가 종료되어 사용하던 메모리 공간을 반납한다면 중간에 빈 공간들이 생기고 이 공간을 어떻게 활용해 다른 프로세스를 넣어줄지를 결정하는 기준이 배치 전략
배치 전략
- 최초 적합 : 메모리 공간 위에서부터 내려오면서 빈 공간이 있을 때 넣어줌. 간단하지만 공간 활용률이 낮음.
- 최적 적합 : 새로 할당할 프로세스가 요구하는 메모리의 크기와 가장 비슷한 공간을 찾아 넣어줌. 탐색 시간이 오래 걸려 오버헤드가 생기고 오히려 비슷한 크기라는 이유에서 자투리 공간이 많이 생긴다는 단점이 있음.
- 최악 적합 : 적당한 크기를 남겨 할당해주거나 가장 큰 공간을 할당해주는 방식. 자투리 공간은 줄일 수 있으나 큰 규모의 파티션 확보는 반대로 어려워지는 단점이 있음.
- 순차 최초 적합 : 메모리 공간을 항상 위에서부터 탐색하다 보면 아래 부분의 메모리 활용률이 떨어지기 때문에, 이를 개선하기 위해 일단 메모리를 할당했다면, 다음에는 그 이후부터 탐색하는 방식으로 배치.
만약 외부 단편화(남아있는 메모리 공간이 충분하고 들어갈 프로세스가 요구하는 용량도 적은데, 메모리 파티션들이 연속된 공간에 있지 않아 할당해줄 수 없는) 경우에는?
배치 전략 2 - 외부 단편화
- coalescing holes : 인접한 빈 영역을 하나의 파티션으로 통합하는 방식으로 사용 중이던 프로세스가 작업 완료 후 메모리를 반납(release)하고 나갈 경우 바로 합쳐줄 수 있음. 하지만 완료될 때까지 기다려야 한다는 단점.
- storage compaction : 연속된 공간이 아니더라고 사용 중이지 않은 메모리 공간을 모두 아래로 내려서 다시 합쳐주는 메모리 압축 방법. 옮겨주는 과정에서 오버헤드가 크고, 사이사이에 사용 중인 메모리 공간을 할당받은 프로세스의 작업들을 일시적으로 중단해야 하기 때문에 오버헤드가 더 커지므로 자주 사용하기엔 부담.
메모리에 대해 공부할 필요가 있는 이유
- 메모리에 대한 고려 없이 매번 메모리 할당 요청을 보내거나 필요할 때마다 동적 할당시키면 컴퓨터 입장, 프로그램 입장에서는 빈번한 호출로 인한 부하나 속도 저하 문제가 발생한다. 하지만 메모리가 어느 정도 필요할지 예측해 미리 할당받아놓으면(메모리 풀) 한 번의 호출로 필요한 작업을 편하게 수행할 수 있기 때문에 속도 면에서나 시스템 면에서나 훨씬 좋다.
참고로 동적 할당은 시스템 콜까지 가서 메모리를 운영체제로부터 얻어와야 하는 아주 비싼 연산이다. 하지만 메모리풀을 활용하면, 운영체제에 한 번만 요청을 해 할당받은 뒤 받은 메모리에서 포인터를 활용해 필요할 때마다 해당 메모리 주소는 카피하는 방식으로 활용할 수 있고 이 경우 수시로 메모리 공간을 요청하고 반납하더라도 시간이 오래 걸리지 않는다.
** KOREATECH 무료 OS강의(클릭) 학습 후 나름대로 이해한 뒤 정리한 내용입니다. **
'CS > Operating System' 카테고리의 다른 글
[ OS 기초 ] 09. Virtual Memory (2) (0) | 2021.07.13 |
---|---|
[ OS 기초 ] 09. Virtual Memory (0) | 2021.07.12 |
[ OS 기초 ] 07. Deadlock (0) | 2021.07.06 |
[ OS 기초 ] 06. 프로세스 동기화, 상호배제 (2) - 상호배제 기법 (0) | 2021.07.02 |
[ OS 기초 ] 06. 프로세스 동기화, 상호배제 (0) | 2021.06.30 |
댓글