메모리는 RAM을 의미한다. SRAM은 캐시 메모리에 사용되고 DRAM이 메인 메모리에 사용된다.
운영체제가 등장하고 운영체제의 역할이 프로세스 관리도 중요했지만 메모리 관리가 매우 크게 차지했다. 메모리가 매우 비싼 자원으로 속했기 때문이다.
현재 메모리 용량이 매우 크게 증가했지만 여전히 메모리를 관리하는 역할은 매우 중요하다.
메모리 용량이 증가했지만 프로그램의 크기 또한 계속해서 증가하고 있기 때문에 메모리의 용량은 언제나 부족한 상태이다.
프로그램이 처음 시작할 때는 기계어나 어셈블리언어로만 작성을 하는 형태가 많이 있었다. 하지만 시대를 넘어오면서 수많은 다른 언어를 사용하기 시작했다. 프로그램의 크기가 점차 커지기 시작하는 것이다. 요즘에는 숫자 처리, 문자 처리뿐만 아니라 멀티미디어 처리, 빅데이터 처리 등을 위해 프로그램이 사용되므로 프로그램의 크기가 매우 커지고 있기 때문에 메모리의 큰 공간을 차지하게 된다. 이에 중요한 문제가 어떻게 하면 메모리를 효과적으로 사용할 수 있는가에 대한 질문이다. 그래서 메모리 사용의 낭비를 없애는 방법을 많이 찾게 되었다. 이러한 형태 중 하나가 가상 메모리라는 도구가 있다.
메모리는 주소와 데이터로 구성되어 있다. CPU가 원하는 데이터의 주소를 메모리에 보내주게 되면 메모리는 CPU에게 해당하는 데이터를 보내준다. 또한 CPU에서 계산된 결과를 메모리의 특정 주소에 저장하고 명령을 보내면 메모리에서 해당 주소에 데이터를 저장한다.
프로그램을 개발할 때는 여러 가지의 파일 형태를 가진다. 소스 파일은 고수준언어 또는 어셈블리언어로 개발된 파일을 말한다. 소스 파일은 컴파일러와 어셈블러에 의해 목적 파일로 전환된다. 목적 파일은 소스 파일에 대한 컴파일 또는 어셈블 결과를 나타내는 파일로 기계어로 나타내어진다. 목적 파일을 링크가 실행파일로 바꾼다. 실행파일은 링크의 결과로 나타난 파일이다. 링크는 하드디스크에 들어가 있는 다양한 내장 함수(Library)들을 실행하기 위해 연결해주는 과정이다. 만들어진 실행 파일을 메모리에 적재하는 과정을 하는 것은 로더이다. 실행 파일은 로더에 의해 적재되어야만 실행이 가능하다.
하나의 프로그램이 실행되기 위해서는 코드와 데이터, 스택을 가지고 있어야한다. 코드의 경우에는 앞에서 작성한 파일들이 될 수 있다. 데이터의 경우는 프로그램이 실행될 때 넣어주어야 할 값이 된다. 예를 들어 두 수 중 큰 수를 나타내어 주는 프로그램이 있으면 큰 수를 나타내게 해주는 작업을 해주는 부분이 코드이고 두 수를 입력하는 값이 데이터이다. 스택은 함수를 호출했을 때 돌아오는 줏와 지역변수를 저장하는 공간이다.
실행 파일이 만들어지면 로더에 의해 메모리에 올려진다고 했다. 운영체제는 이 실행 파일을 메모리의 어디 부분에 올릴지를 결정한다. 다중 프로그래밍 환경에서 메모리에 프로그램을 넣어주고 다시 하드디스크로 보내주고 하는 과정들을 모두 운영체제에서 담당하여 처리한다. 사실 고수준언어를 작성할 때에는 주소를 사용하여 작성하진 않지만 목적 파일로 바뀌어 실행 파일을 사용하면 주소의 값을 통해 코드를 이동하고 작동을 하게 된다. 따라서 메모리에 적재할 때 적절한 메모리 위치에 프로그램을 넣지 않으면 문제가 발생할 수 있다. 이러한 문제를 해결하는 것이 MMU이다. MMU는 주소의 영역을 정해주는 역할을 한다. MMU에는 재배치 레지스터가 존재하는데 이는 코드가 원하는 주소를 만들어주는 역할을 한다. 예를 들어 코드에서 0번지에 들어가야하는 프로그램이 작성되어 있는데 메모리에서 빈 공간이 주소가 500번지인 곳에 있다고 했을 때, 재배치 레지스터에서 500이라는 값을 더해주어 500번지에서 실행하는 것이 맞게 해주는 것이다.
하드디스크에 존재하는 프로그램들은 메모리의 특정 부분에 적재되어 실행된다. 프로그램이 만들어지기까지 소스 파일, 목적 파일, 실행 파일을 거쳐 로더에 의해 메모리에 올려지게 되는데, 적절한 위치에 적재를 해야 실행시킬 수 있다. 앞장에서 MMU의 재배치 레지스터를 이용하면 부분적으로 적절한 위치에 프로그램을 올릴 수 있다.
MMU가 메모리를 관리하는 방법에 대해 조금 더 상세히 들어가 보면,
MMU는 프로그램이 동작할 때 필요한 데이터나 동작들을 메모리의 특정 주소 위치로부터 가지고와야 하는데 이는 프로그램이 만들어질 때 코드화되어 있다. 따라서 항상 같은 위치의 메모리 주소로부터 데이터를 불러오게 된다. 하지만 메모리에 프로그램을 적재할 때 항상 같은 위치에 프로그램을 올릴 수는 없다. 어떤 날에는 0번지에 올리기도 하고 다른 날에는 500번지에 올리기도 한다.
이는 메모리 공간에서 빈 공간을 찾아 프로그램을 올리기 때문에 알 수가 없다. 그래서 이를 맞추어 조절해주는 역할을 하는 것이 바로 MMU의 재배치 레지스터이다. 재배치 레지스터는 CPU가 프로그램을 실행시킬 때 코드를 읽어서 들어가는데 코드에서 특정 위치의 메모리 주소로부터 데이터를 가지고 오라는 동작을 할 때 도움을 준다. CPU는 분명 특정 위치를 가르쳐 줬지만 재배치 레지스터는 이 주소에서 메모리에 프로그램이 있는 위치 주소를 더해서 계산해서 CPU에서 원하는 데이터 위치와 메모리상에 프로그램의 위치가 맞게 맞추어 준다. 여기서 CPU에서 MMU로 보내는 주소를 논리 주소라 하고, MMU에서 메모리로 보내는 주소를 물리 주소라고 한다.
동적 적재(Dynamic Loading)
동적 연결(Dynamic Linking)
스와핑(Swapping)
위의 과정을 swap(스왑시킨다)이라 한다. 주 기억장치(RAM)로 불러와 메모리로 적재해서 올려주는 과정을 swap-in, 보조 기억장치(backing store)로 내보내는 과정을 swap-out 이라고 한다. MMU의 재배치 레지스터를 사용하므로 적재 위치는 메모리의 빈 공간 어디에도 적재가 가능하다. 이런 작용으로 메모리의 활용도를 높일 수 있어 효율이 높아진다.
하지만 프로세스의 크기가 크면 backing store 입출력에 따른 부담이 크다. 다시 말하면, swap에는 큰 디스크 전송시간이 필요하기 때문에 현재에는 메모리 공간이 부족할 때 스와핑이 시작된다.
즉, 주기억장치는 일정 크기의 프레임으로 나뉜다. 각 프로세스는 프레임들과 같은 크기를 가진 페이지들로 나뉜다. 같은 크기로 페이지와 프레임이 잘라져 있기 때문에 페이지를 프레임에 할당하면 딱 맞아 떨어진다. 이 때 페이지를 관리하는 MMU는 페이지 테이블이 된다. 페이지 테이블 안에 있는 개수는 프로세스를 몇 등분 하는가에 따라 결정된다. 프로세스의 모든 페이지가 프레임에 적재되어야 하며 이 페이지를 저장하는 프레임들은 연속적일 필요는 없다.
CPU가 내는 주소는 논리 주소(Logical address)라고 한다. CPU에서 보낸 논리 주소는 MMU 인 페이지 테이블을 통해 물리 주소(Physical address)로 바뀌어서 메모리에서 찾게 된다.
메모리 공간이 충분함에도 프로세스가 메모리에 적재되지 못해 메모리가 낭비되는 현상이다.
위의 메모리 현황을 압축 작업을 하게 되면 아래와 같이 바뀐다.
각 프로세스에 대해 일부 블록들만 적재하므로 많은 프로세스를 적재할 수 있다. 이로 인해 임의 시점에 하나 이상의 프로세스가 준비 상태에 있을 가능성이 커지므로 프로세서의 활용도가 높아진다.
프로그래밍에 있어 가장 근본적인 제약점 중 하나가 제거된다. 이 기법을 적용하지 않을 경우, 프로그래머는 얼마나 많은 메모리 공간을 사용할 수 있는지 정확히 인식해야한다. 프로그램이 클 경우, 오버레이 같은 기법을 적용하여 분할 적재할 수 있도록 프로그램을 여러 블록으로 구조화할 수 있는 방법을 모색해야 한다.
요구 페이징에서는 프로그램에 대한 모든 내용이 물리 메모리에 올라오지 않기 때문에 메모리에 없는 페이지에 접근하려할 때 페이지 폴트가 발생한다.
페이지 폴트가 발생하면 원하는 페이지를 저장 장치에서 가져오게 되고, 만약 물리 메모리가 가득 차있는 상태라면 페이지 교체가 이루어진다.
페이지 폴트 처리 과정
1) 페이지 폴트 발생
2) 디스크에서 해당 페이지 찾음
3) 빈 페이지 프레임을 찾음
4) 페이지 교체 알고리즘을 통해 Victim 페이지 선택
5) Victim 페이지를 디스크에 저장
6) 비워진 페이지 프레임에 새 페이지를 읽어옴
7) 재시작
CPU가 프로세스 내의 명령어 및 데이터에 대한 참조가 군집화 경향이 있음을 뜻한다.
메모리 접근은 시간적, 공간적으로 지역성을 가진다.
지역성의 원리에 의해 페이지 결함으로 인한 컴퓨터의 효율이 떨어지는 확률이 많이 낮아진다.
코드를 읽을 때 현재 코드의 주변에 있는 코드를 읽을 확률이 높다는 것
= 참조된 메모리의 근처 메모리는 참조될 확률이 높다
페이지 결함이 일어나 하드디스크에서 페이지를 가져올 때 주변 코드들을 블록 단위로 가져오게 되면 주변 코드를 읽을 확률이 높으니 페이지 결함의 확률이 낮아지는 것이다.
지역성의 원리는 참조된 메모리의 근처 메모리를 참조하는 공간 지역성의 원리, 참조했던 메모리는 빠른 시간내에 다시 참조될 확률이 높다는 시간 지역성의 원리, 데이터가 순차적으로 액세스되는 것을 말하는 순차 지역성 등이 있다.
요구 페이징은 요구되어지는 페이지만 backing store에서 가져와 메인 메모리에 적재하는 방법이다.
필요한 페이지만 메인 메모리에 올리므로 메모리의 낭비를 줄이는 방법으로 사용되었다.
하지만 프로그램 실행이 계속 진행되면서 요구 페이지가 늘어나게 되고 언젠가는 메모리가 가득 차게 될 것이다.
페이지를 backing store에서 가져와 메모리에 올려야 되는데 메모리에 자리가 없는 경우, 메모리에 있는 특정 페이지를 내보내고 그 자리에 필요한 다른 페이지를 올려야 한다. 이를 페이지 교체라고 한다.
메모리가 가득차면 추가로 페이지를 가져오기 위해 어떤 페이지는 backing store로 몰아내고 그 빈 공간으로 페이지를 가져온다.
backing store로 페이지를 몰아내는 것을 page-out이라고 하고, 반대로 빈 공간으로 페이지를 가져오는 것을 page-in이라고 한다. 여기서 몰아내는 페이지를 victim paged라고 말한다.
- Belady의 모순 : 페이지 프레임의 개수를 늘려도 페이지 부재가 줄어들지 않는 모순 현상
크게 두가지 원인이 있다.
운영체제는 CPU 이용률이 너무 낮아질 때, 새로운 프로세스를 추가해 멀티프로그래밍 정도를 높이려 하는데, 이때 전역 페이지 교체 알고리즘으로 어떤 프로세스의 페이지인지에 대한 고려없이 페이지 교체를 수행한다.
그런데, 교체된 페이지들이 해당 프로세스에서 필요로 하는 자원이 있으면 페이지 폴트를 발생시키게 되고 그러면 swap in/out을 위해 페이징 장치를 사용해야 하는데 프로세스들이 페이징 장치를 기다리는 동안 CPU 이용률은 떨어지게 되고, CPU 스케줄러는 이용률이 떨어지는 것을 보고 멀티프로그래밍 정도를 더 높이려 하게 되어 쓰레싱이 일어나게 된다.
멀티 프로그래밍 정도가 높아짐에 따라 CPU 이용률이 최대값에 도달했을 때, 멀티프로그래밍 정도가 그 이상으로 커지게 되면 쓰레싱이 일어나게 되고 CPU 이용률은 급격히 떨어진다.
지역 교체 알고리즘이나 우선순위 교체 알고리즘을 사용해 제한한다.
지역 교체 알고리즘에서는 한 프로세스가 쓰레싱을 유발하더라도 다른 프로세스로부터 페이지 프레임을 뺏어올 수 없으므로 다른 프로세스는 쓰레싱에서 자유로울 수 있다.
https://sunday5214.tistory.com/7
https://velog.io/@deannn/CS-%EA%B8%B0%EC%B4%88-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%AC-%EC%A0%84%EB%9E%B5
https://copycode.tistory.com/88?category=740133
https://dheldh77.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B0%80%EC%83%81%EB%A9%94%EB%AA%A8%EB%A6%ACVirtual-Memory
https://jwprogramming.tistory.com/56