[OS] 가상메모리의 이해 Part 1

Paging, MMU, page fault

Posted by Sol on February 28, 2021 · 6 mins read

가상메모리란?

가상메모리는 물리 메모리 크기의 한계를 극복하고자 만들어진 기법이다.

어떤 컴퓨터의 실제 RAM 공간이 8GB인데, 한 프로세스의 요구 메모리가 4GB라면?

(리눅스는 한 프로세스의 정해진 용량이 4GB)

우리는 하나의 RAM에 프로세스를 달랑 2개밖에 실행시킬 수 없다.

이건 너무 비효율적이다.

따라서, 멀티 프로세싱 환경에서는

프로세스를 조각으로 나누어서 일부분만 메모리에 올려놓는다.

프로세스는 가상 주소를 사용하고, 실제 해당 주소에서 데이터를 읽고 쓸 때만 물리 주소로 바꾸어주면 된다.

그런데, 프로세스의 가상 주소를 매번 물리주소로 바꾸는데는 오버헤드가 발생한다.

이를 해결하기 위해, CPU코드 실행 시 가상 주소를 물리 주소로 바꾸어주는 하드웨어 장치가 존재한다.

(하드웨어 장치를 이용해야 가상 - 물리 주소 변환이 빠르다)

바로 MMU(Memory Management Unit)이다.

즉, CPU는 가상메모리만을 다루며, CPU가 가상메모리에 접근할 때 MMU 장치를 통해 실제 물리메모리에 접근하는 방식이다.

MMU는 가상-물리 주소값 변환기라 할 수 있다.

CPU는 프로세스 조각의 물리 주소값을 알지 못하며, 가상 주소를 MMU에 전달하면 변환된 값만 input으로 받아 연산을 수행한다.

가상 메모리 구축에는 다양한 메커니즘이 활용되는데,

그 중 가장 많이 활용되는 것이 페이징 시스템이다.


페이징 시스템(Paging system)

페이징이란 무엇일까?

프로세스의 조각 단위페이지라는 단위로 다루겠다는 것이다.

페이징 시스템에서는 크기가 동일한 페이지로 가상 주소 공간과 이에 매칭되는 물리 주소 공간을 관리한다.

페이징 시스템을 구동하기 위해서는 하드웨어 지원이 필요한데,

한 예로 Intel x86 32비트 시스템에서는 페이징 단위를 4KB, 2MB, 1GB로 지원한다.

즉, 하나의 조각 페이지 용량이 4KB, 2MB, 1GB가 될 수 있다는 것이다.

리눅스에서는 4KB로 페이징을 하며, 이는 4GB의 프로세스가 약 2^20개의 조각으로 쪼개진다는 소리다..

페이지를 쪼개면 페이지를 인덱싱하여 페이지 번호를 만드는데,

페이지 번호를 기반으로 가상주소/물리주소 매핑 정보를 기록하여 사용한다.

여기서 물리메모리의 각 조각을 Frame, 가상메모리의 한 조각을 Page라고 하며

프레임의 크기와 페이지의 크기는 같다.

image

출처 : https://www.guru99.com/paging-in-operating-system.html


프로세스의 PCB에는 Page Table 구조체의 주소값이 저장되어있으며,

Page Table에는 가상 - 물리 주소 매핑 정보가 있다.

따라서 CPU가 프로세스의 특정 가상주소에 접근하려고 하면 가상주소는 페이지 번호를 바로 get할 수 있다.

32비트의 가상주소 v는 [가상메모리 페이지의 base 주소 + 해당 페이지에서 참조될 항목의 위치] 로 되어있는데,

  • 가상주소 v = (p, d) // p는 페이지 번호, d는 변위

와 같이 나타낼 수있다.

즉, CPU는 우선 가상주소v의 페이지주소 p를 통해 page table에서 해당 페이지의 실제 physical 주소f 를 얻고,

실제 주소 f에 d라는 변위가 더해진 값으로 실제 명령의 주소값을 찾는다.

페이지번호를 get하면, PCB의 Page Table에 접근하여 CPU가 비로소 실제 물리주소를 얻을 수 있다.

또한, Physical Address에 더 이상 존재하지 않는 명령은 Page Table에도 있을 필요가 없다.

그래서 Page Table에은 해당 페이지가 Valid한지 Invalid한지를 나타내는 valid-invalid bit가 존재하며,

invalid하다면 물리 주소에서 해당 프레임을 찾지 않거나, 인터럽트를 건다(Page fault - 아래에서 설명).


정리하자면, 위 과정은 아래와 같이 요약할 수 있다.

  • 프로세스 생성 시, Page Table에 페이지 정보를 생성하고 메모리에 페이지 정보가 적재된다.
  • CPU가 가상주소를 MMU에 요청한다.
  • 요청시 MMU가 PCB의 Page Table에 접근, 물리주소를 가져온다.
  • 해당 물리주소의 명령어를 CPU에 전달한다.
  • CPU가 명령어를 수행한다.

참고로, MMU가 페이지 테이블에 접근하여 실제 주소를 알아낸 다음(1번),

다시 실제 물리주소에 접근하여 명령어를 알아내는 과정(2번)은 2번의 작업이 필요하다.

여기서, TLB(Translation Lookaside Buffer)라는 캐쉬를 두는데,

이 캐쉬는 MMU가 페이지 주소 - 실제 주소 Mapping정보를 한 번 알아내면 그 매핑정보를 따로 저장하여

다음 번에는 CPU가 가상메모리를 요청할 때 바로 TLB를 참조할 수 있도록 하여 성능을 높인다.


다중단계 페이징 시스템

메모리에서 실제로 필요한 명령어 정보는 사실 별로 큰 부분을 차지 하지 않겠지만,

메모리에 모든 페이지 정보가 들어있는 Page Table을 적재하면, 그것은 낭비가 될 수 있다.

리눅스를 예로 들어보면, 4GB 중 1GB는 커널영역, 나머지 3GB가 사용자 영역인데

페이지 테이블을 메모리에 전부 올려두면 3GB중 쓸데없이 많은 공간을 점유할 수 있다.

이를 해결하기 위해,

페이징 정보를 단계를 나누어 생성하여 필요없는 페이지는 생성하지 않으면, 공간 절약이 가능하다.

How?

가상주소는 원래 아래와 같이 구분된다고 했다.

  • 가상주소 v = (p, d) // p는 페이지 번호, d는 변위(offset)

여기에, Page Directory 정보를 넣어서 어떤 페이지 dir -> 그 dir의 Page Table -> Offset 의 순서로

실제 Physical Frame에 접근하게 한다면, ( v = (dir, p, d) )

Layer가 한 층 더 생기는 것이므로 효율이 향상된다.


페이징 시스템과 공유메모리

페이징 시스템은 물리 메모리를 사용하는 데 있어 굉장히 효과적인 경우가 많다.

서로 다른 프로세스가 동일한 물리 주소를 가리키는 경우, Page Table이 동일한 물리주소를 가리킬 것이므로

공간절약, 메모리절약, 시간절약이 가능하다.

리눅스에서 fork() 명령어로 프로세스를 복사할 때도,

실제 물리메모리에서 4GB의 데이터를 모두 복사할 필요가 없이,

Page Table이 같은 물리주소를 가리키기만 하면 되므로 복사 시간이 줄어든다.

단, Read가 아니라 Write 요청이 들어왔을 때에만 해당 부분을 실제 메모리에 복사하게 된다.

(같은 주소를 가리키고 있으므로 Child Process에 Write가 발생하면 Parent에도 Write되므로)


페이지 폴트 인터럽트(Page Fault Interrupt)

어떤 프로세스에서 가상메모리를 요청했는데

Page Table에서 해당 페이지의 Valie bit가 i(Invalid)라면,

물리주소에 있는 Frame이 RAM에 페이지 형태로 적재되지 않았음을 의미한다.

이 경우, 운영체제에서 interrupt를 걸고, Physical Memory에 있는 정보를 Page로 만들어 메모리에 적재하도록 한다.

적재가 끝나면 다시 해당 Page를 재실행하도록 한다.

image

(출처 : https://velog.io/@underlier12 )

페이지 폴트가 자주 일어나게 되면 인터럽트를 걸고, 저장매체에 갔다와야 되므로,

페이지 폴트가 발생하게 되면 시간이 오래 걸린다.

이를 방지하려면 향후 실행, 참조될 코드 및 데이터를 미리 메모리에 적재하면 되는데,

그걸 어떻게 예측하겠는가? 예측은 거의 불가능하다.

과거 컴퓨터를 쓸 때, 프로그램을 많이 띄워놓으면 HDD를 읽는 소리가 많이 발생한다…

왜냐하면, 프로그램을 많이 띄운 상태에서 다른 프로그램을 실행하면

페이지 폴트가 많이 발생하여 HDD에 자주 접근해야 하며, Page 교체가 빈번하게 발생하기 때문이다.


위 내용은 ‘패스트캠퍼스’의 컴퓨터공학 강좌 내용을 요약 정리한 것임을 밝힙니다. (https://www.fastcampus.co.kr/)