현대의 운영체제는 가상 메모리의 개념을 사용하여 메모리를 관리하고 있습니다.
가상메모리는 가상의 주소공간을 제공하여서 프로그래머와 프로세스가 현재 다루고 있는
주소공간을 거의 무한한 수준의 공간처럼 사용할 수 있게해줍니다.
프로세스는 아래와 같은 속성이 있음이 관찰되었습니다
즉, 프로세스를 실행하기 위해서 프로그램 전체가 메모리에 항상 상주해야할 필요가 없다는 사실입니다
당장 사용해야하는 부분의 코드, 데이터만 메모리에 올려두고 사용되지 않으면 다른것을
메모리에 올려두어서 사용하면 되는것입니다.
가상 메모리가 가능한 이유는, 페이징과 관련이 있고 특히 디멘드 페이징과 밀접한 연관이 있습니다
페이징이란 운영체제가 메모리를 페이지라는 단위로 관리하는 것을 의미합니다.
운영체제가 페이지란 개념을 사용하기 전에는 아래와 같은 세가지 중 한 가지 방식으로 메모리를 할당했습니다.
worst fit 메모리에서 빈 공간들을 크기순서로 정렬해서 그 중에 가장 큰 공간에
메모리를 할당하는 방식이며
best fit 은 메모리의 빈 공간들 중에 현재 프로세스를 할당할 수 있는 최소한의 공간을 찾아서 할당하는 방식이고
first fit이란 메모리를 처음부터 읽어가면서 크던 작던 프로세스를 할당하기에 충분하면
그곳에 할당하는 방식입니다.
세가지중 어떠한 방법을 사용하더라도 피할 수 없는 문제가 있었는데요.
바로 프레그멘테이션이 발생하는것입니다. 아래의 그림과 같은 상황인데요
특히, 이러한 프래그멘테이션을 외부 프레그멘테이션(external fragmentation)이라고 합니다
조금 다른 방식을 살펴보면 애초에 메모리를 동일한 사이즈의 블록으로 나누어두고
메모리를 할당해주는 것이지요. 그러면 할당된 메모리가 실제 프로세스가 필요한 양보다
조금 큰 경우가 있고, 이것은 내부 프레그멘테이션(internal fragmentation)이라고 합니다.
페이징이 바로 이러한 방식을 사용하는것입니다.
페이징은 물리 메모리를 고정된 크기의 '프레임' 이라는 블록으로 나누게되고
논리 메모리는 블록으로 나누어서 '페이지'라고 부르게됩니다.
프로세스가 실행될 때, 페이지가 메모리에서 남은 프레임에 할당되게 됩니다.
이렇게 고정된 사이즈의 블록들로 나누게 되면 장점이 있는데요
더 이상 메모리를 연속적(contiguous)하게 할당해줄 필요가 없이
블록의 위치만 알고 있다면 이곳저곳에 흩어져도 연속적인것처럼 사용할 수 있기때문입니다.
페이징에 대한 하드웨어 지원은 아래와 같이 이루어지는데요
CPU에서 생성되는 주소는 두 개의 파트로 나뉘어지게 됩니다.
1. page number(p)
2. page offset(d)
CPU에서 만들어낸 주소를 가지고 페이지 테이블 에서 물리 메모리 주소를 찾아낸 후
실제 메모리에 접근할 수 있게 됩니다.
그런데, 페이지 테이블을 사용하는데 조금 문제가 있는데요, 현대의 컴퓨터는
굉장히 큰 메모리를 가지고 있기 때문에, 페이지 테이블 자체 사이즈가
엄청나게 커지게 되는 문제가 있다는 것입니다. 운영체제는 페이지 테이블이
메인메모리에서의 시작위치를 저장하는 page table base register(PTBR)을 가지고 있고
아래와 같은 방식으로 실제 물리 메모리 주소를 받아옵니다.
1. i 위치에 접근하고자 하면 PTBR을 이용하여 계산 후 페이지 테이블에 접근
-> 메모리 엑세스 발생
2. 페이지의 물리 메모리 상 프레임을 얻게된다
3. (2)에서 얻은 프레임으로 물리 메모리에 접근한다
-> 메모리 엑세스 발생
이렇게 매우 매우 느린 메모리 액세스가 2번이나 발생합니다.
이것을 해결하기 위해 고안된 것이, 물리 메모리 주소를 빨리 얻는 용도의 하드웨어인
translation look-aside buffer(TLB) 입니다. TLB는 캐시와 매우 비슷한데요
아래와 같이 작동합니다
여기서 TLB miss의 경우 LRU 알고리즘에 따라서 기존에 있는 페이지를 쫓아내고
새로 참조된 페이지가 등록되게 됩니다!
여기까지 페이징이 어떻게 이뤄지는지에 대한 설명이었습니다.
이제 디멘드 페이징에 대해서 이야기 할 수 있습니다.
디멘드 페이징은 굉장히 직관적인 이름인데요, 앞에서 프로세스는 오직 필요한 부분만
메모리에 올라가고, 필요한데 현재 메모리에 없는 부분은 그 때 그 때 올리자는 아이디어가
있다고 했는데요. 그것을 실현해주는 것이 바로 디멘드 페이징입니다.
디멘드 페이징을 수행해주는 하드웨어를 페이저라고 합니다.
디멘드 페이징 아래와 같은 방식으로 이뤄집니다.
페이지 테이블의 각 엔트리에 valid-invalid bit 를 두어서 페이지 테이블에 접근했을 때
이 페이지가 현재 메모리 위에 올라가 있다면 valid 이고
부재중이거나 프로세스의 가상주소공간에 없는 페이지인 경우인지 invalid 를 보고
알 수 있는것입니다.
여기서 invalid 인 페이지 테이블에 접근했을 때, 모두가 아는 Page fault가 일어납니다.
여기서 조금 부수적인 지식으로 왜 page fault가 fault인지, 내부적으로 어떻게 처리되는지 다뤄보겠습니다
fault 는 exception의 한 종류입니다.
Exception 은 세부적으로 보면 아래와 같은 클래스로 나뉘게 되는데
1. Interrupts
-> I/O 장치의 시그널로 인해 발생하며 비동기적으로 처리됩니다
2. Trap
-> 의도적은 예외이며 동기적으로 처리됩니다
3. fault
-> 복구 가능한 수준의 에러입니다 동기적으로 처리되며, fault가 일어난 인스트럭션으로 되돌아옵니다
4. Abort
-> 복구가 불가능한 수준의 에러입니다.
여기서 관심사는 fault 이며 이것은 복구 가능한 수준의 에러입니다.
Page fault의 경우 페이지를 다시 backing store에서 가져온다면 복구가 되는것이므로
fault로 분류되게 되는것입니다.
다시 디멘드 페이징으로 돌아와서, 절차를 정리해보면 아래와 같습니다
이렇게 디멘드 페이징이 이루어지게 되며, 운영체제는 위에 설명드린 방법대로 메모리를 관리하게 됩니다.