메모리

2021. 11. 8. 02:17CS 지식


가상 메모리


32비트 기준,
실제 메모리(RAM)와 가상 메모리의 공간은 4GB(2^32)이고, 이 중 절반인 2GB는 커널이 차지한다.
가상 메모리 속 커널 영역은 모든 프로세스가 공동으로 소유하고, 프로그래머가 접근할 수 없다.


프로그래머가 직접 제어하는 메모리는 RAM이 아니라, 프로세스마다 가지고 있는 가상 메모리이다.

가상 주소와 가상 메모리를 사용함으로써 프로세스마다 4GB의 메모리를 할당해줄 수 있다.

 

(만약 4GB를 하드 디스크에 저장하고 현재 동작하는 프로세스의 메모리를 RAM에 올린다면, 4GB 선 할당해야하는 부담이 있을 것이고, 속도도 매우 느릴 것이다.)

 

 

아래의 그림은 앞으로 설명할 page, page table, TLB, MMU의 동작을 설명한다.

출처: https://velog.io/@gndan4/OS-%EA%B0%80%EC%83%81-%EB%A9%94%EB%AA%A8%EB%A6%AC

 

Paging

가상 메모리와 물리 메모리의 매핑을 위해, 페이징(Paging) 기법을 사용하는데,

메모리를 일정 크기의 페이지 단위로 쪼개서, 가상 메모리에서 실제 사용되는 부분만 페이지 단위로 물리 메모리에 할당해서 사용하는 것이다. 
물리 메모리 공간은 4KB 크기의 page frame으로, 가상 메모리 공간은 4KB 크기의 page나누어져있다.
이 페이지라는 블록 단위로, CPU의 MMU에 의해 가상 메모리 공간에서 실제 메모리 공간으로 이동한다.

요구 페이징(Demand Paging) 방식을 사용해서, 당장 필요한 페이지만 메모리에 올린다. 당장 필요하지 않거나 교체된 페이지들은 하드 디스크의 여유 공간인 스왑 파일에 페이지를 기록한다.

 

Page의 상태

가상 메모리의 페이지의 상태는 Free / Reserve / Commit 이 있다.
Free는 비어있는 상태, Reserve는 예약된 상태, Commit은 할당이 된 상태이다.

Commit 상태는 물리 메모리에까지 할당된 상태이고,

Reserve 상태는 물리 메모리에 할당되지는 않지만, 가상 메모리 상에서 해당 공간의 할당을 막는 상태이다.

이를 통해 메모리의 순차적 접근을 보장하고, 메모리 낭비를 방지할 수 있다.

 

메모리의 지나친 조각화를 막기 위해, Windows 운영체제에서는 Allocation Granularity Boundary (필자 확인 기준 64KB, 달라질 수 있다.) 단위로 메모리를 할당한다.

 

Page Table

가상 주소를 인덱스로, 물리 주소를 알아낼 수 있는 entry 들의 table이다. 프로세스 별로 가상 메모리가 부여되는 개념이기 때문에, 프로세스 별로 존재한다. 각 page table은 메모리 내에 위치한다.

Page Table의 구성요소인 Page Table entry 각각의 구조는 아래와 같다.

  • 물리 메모리 주소의 페이지 번호
  • 플래그 비트
    • 접근 비트(Accessed bit) : 페이지에 대한 접근이 있었는지를 나타낸다.
    • 변경 비트(Dirty bit) : 페이지 내용의 변경이 있었는지를 나타낸다. (Modify bit라고도 한다.)
    • 존재 비트(Present bit) : 현재 페이지에 할당된 프레임이 있는지를 나타낸다. (valid-invalid bit라고도 한다.)
    • 읽기/쓰기 비트(Read/Write bit) : 읽기/쓰기에 대한 권한을 표시한다. (protection bit라고도 한다.)

 

MMU

CPU 내부의, 페이지 단위로 가상 주소를 물리 주소로 변환해주는 장치.

이 MMU 내부의 CR3 register는 현재 동작하는 프로세스에 대한 page table의 물리 주소값을 갖고 있다. 그래서 CR3 레지스터를 통해 page table을 참조하여 가상 주소를 물리 주소로 변환한다.

가상 주소는 테이블의 인덱스 역할을 하는 page 번호와, 페이지 프레임 내의 위치(page offset)로 나뉘는데,

따라서 가상 주소의 페이지 번호만 물리 메모리 페이지 번호로 변환하고 가상 주소의 offset 부분은 물리 주소에도 그대로 유지된다. 

 

TLB (Translation Look-aside Buffer)

위에서 설명한 방법으로만 페이지 변환을 한다면, 주소 변환 시에 page table에 접근해야하기 때문에,

데이터에 접근 시에 메모리에 2번(page table,실제 데이터가 위치한 메모리) 접근해야 한다. 이런 비효율을 개선하기 위해 TLB가 존재한다.

TLB는 page table의 (page, frame) 쌍을 저장하는 일종의 캐쉬이며, TLB에 원하는 (page, frame) 매핑 정보가 있다면 메모리에 1번만 접근하여 원하는 데이터를 얻을 수 있다.

 

 

 

<참고>가상메모리 Win API 함수

VirtualAlloc: 페이지의 상태를 RESERVE 혹은 COMMIT 상태로 만든다.

(메모리의 시작 주소, 할당하고자 하는 메모리 크기 / 메모리 할당 타입(RESERVE or COMMIT) / 페이지 접근 방식)을 인자로 받고, 할당이 이뤄진 시작 번지를 반환한다.

이때, 인자로 전달하는 메모리의 시작 주소로 NULL이나 주소값을 전달하는데, 주소값을 전달한다고 이게 실제로 할당되는 페이지의 주소값으로 적용되는 것은 아니고, 내부적으로 계산을 해서 최종 주소값을 반환해준다.

VirtualFree: 페이지의 상태를 RESERVE 혹은 FREE 상태로 만든다.

(메모리 공간의 시작, 해제할 메모리 크기, RESERVE로=MEM_DECOMMIT, FREE로=MEM_RELEASE)

DECOMMIT 시에는 해제할 메모리 크기를 0을 넣어야 한다.

 


page fault (페이지 부재)


MMU에서 물리 메모리를 가져오지 못했을 때 CPU가 발생시킨다. 

페이지를 가져오지 못했다는 것은 페이지가 물리 메모리에 올라와있지 않은 경우를 의미한다.

MMU는 page table entry의 valid-invalid bit를 통해 물리 메모리 매핑 여부를 확인할 수 있고, 이 때 매핑이 안됐다면(valid-invalid bit 값이 0) page fault를 발생시키는 것이다.

 

page fault의 원인 


1. 대부분의 경우 가상메모리의 페이지가 RAM에 없을 때 발생한다. (page out이 발생해서 swap file에 존재하는 등..)

비어있는 메모리 공간을 할당하거나, 비어있는 메모리 공간이 없다면 페이지 교체(실제 메모리 한 페이지를 하드디스크에 저장하고(페이지 교체) 그 자리에 할당)를 한다.


2. 접근할 수 없는 주소 공간에 접근 시에도 발생한다.
이 경우, 운영체제가 프로그램을 중지시킨다.

 

page fault 발생 / 처리 과정

출처:https://eunjinii.tistory.com/142

(1) CPU가 가상 주소를 MMU에게 요청하면,

(2) MMU는 먼저 TLB로 가서 그 가상주소에 대한 물리주소가 캐싱돼 있는지 확인한다.

(3) TLB에 캐싱된 물리주소가

    - 있으면 MMU가 해당 페이지의 물리 주소로 데이터를 갖고 와서 CPU에게 보내고

    - 없으면 MMU가 CR3 레지스터를 가지고 물리 메모리에 해당 프로세스의 페이지 테이블에 접근한다.

(4) MMU가 페이지 테이블에서 물리주소가 있는지 valid bit를 확인한다.

(5) valid bit의 값이

    - 1이면 MMU가 해당 페이지의 물리 주소로 데이터를 갖고 와서 CPU에게 보내고

    - 0이면 MMU가 페이지 폴트 인터럽트를 운영체제에 발생시킨다.

(6) 운영체제는 해당 페이지를 저장공간에서 가져온다.

(7) 운영체제는 저장공간에서 가져온 데이터를 메모리에 올려주고, 페이지 테이블을 업데이트 해준다. (valid bit를 1로 변경하고, 물리주소를 갱신한다.)

(8) 운영체제는 CPU에게 프로세스를 다시 실행하라고 한다.

(9) CPU는 다시 MMU에 가상 주소를 요청한다.

 

Thrasing

짧은 시간동안 page fault가 많이 발생하는 현상을 Thrasing이라고 한다.

프로그램의 특성인 Locality Model(시간 / 공간 지역성)을 이용하여, Thrasing을 방지할 수 있다.

 


힙 컨트롤


한 방에 메모리를 할당 및 해제하여 메모리 유출을 방지하여 성능을 향상시킬 수 있다.

 

디폴트 힙

malloc, free, new, delete를 사용하여 할당할 때 메모리가 할당되는 곳.

프로세스에 기본적으로 할당되는 힙이기 때문에, 프로세스 힙이라고도 부른다.

기본 크기는 1MB이지만, 링커 옵션을 통해 변경이 가능하다.Reserve 사이즈와 Commit 사이즈를 변경하여 초기 디폴트 힙 크기를 설정한다. 디폴트 힙의 초기 크기는 이렇게 설정하지만, 필요에 따라서 그 크기는 자동으로 늘어난다.

그렇다해도 크기를 확장할 때는 새로운 메모리 영역을 RESERVE 상태로 예약해야하기 때문에 성능이 필요한 일이다.

 

동적 힙

디폴트 힙 이외에 시스템 함수 호출을 통해 생성되는 힙.

 

동적 힙을 사용하는 이유

  1. 메모리 단편화의 최소화에 따른 성능 향상. 메모리 단편화가 심해지면 프로그램의 로컬리티 특성이 낮아져서 Page Fault가 발생할 확률이 늘어남으로 성능에 많은 영향을 미친다.
  2. 동기화 문제에서 자유로워짐으로 인한 성능 향상. 쓰레드 별로 사용할 힙을 별도로 할당하면, 동시에 여러 쓰레드가 같은 주소 번지에 메모리를 할당 및 해제하는 상황을 방지할 수 있다. (디폴트 힙은 메모리를 할당할 때 내부적으로 동기화 처리를 한다.

 

MMF(Memory Mapped File) : File을 Memory에 Mapping한다는 의미.

직접 파일에 접근하는 것보다 프로그래밍이 용이해지고, 성능이 좋다. (디스크의 캐쉬의 역할을 한다.)

파일 개방(CreateFile) - 파일 연결 오브젝트 생성(CreateFileMapping) - 가상 메모리에 파일 연결(MapViewOfFile)

FlushViewOfFile로 메모리에 캐쉬된 데이터를 파일에 저장하게 명령할 수 있다.

 

COW(Copy-On-Write) 기법: 파일을 수정할 때 복사하는 것. (쓰레드마다 하나의 공간을 가리키다가, 하나의 쓰레드가 그 공간에 무언가 작성할 때, 원본을 복사한 후에 자신만의 테이블에 작성하는 것. - 효율적인 구조를 만들 수 있다.

 

 

<참고> 동적 힙 Win API

HeapCreate : 동적 힙을 생성한다. 호출 시에 HEAP_NO_SERIALIZE 옵션 설정 시에, 생성된 힙에 메모리를 할당/해제 시에 쓰레도 동기화 처리를 하지 않는다.(디폴트 힙에선 내부적으로 동기화를 진행한다.)

dwMaximumSize 인자에 RESERVE 크기를 입력하는데, 0을 입력 시에 증가가능한 힙이 된다.

HeapDestory : 동적 힙을 제거한다. RESERVE, COMMIT 상태의 페이지를 FREE 상태로 되돌린다.

HeapAlloc : 동적 힙에 메모리를 할당한다. 요청 크기에 해당하는 페이지 수만큼 COMMIT 상태로 변경시킨다.

HeapFree : 동적 힙에 할당된 메모리를 해제한다

 

 


메모리 단편화 (Memory Fragmentation)



메모리 중간중간에 해제된 공간이 있음에도 이를 쓰지 못할 수 있다. 이를 메모리 단편화라고 한다.

메모리 단편화에는 내부 단편화외부 단편화가 있는데,

내부 단편화란 실제 필요한 공간보다 큰 공간을 할당할 때 남는 공간을 쓰지 못하는 것이고,

외부 단편화란 메모리 할당과 해제를 반복하며 할당된 메모리 사이사이에 작은 메모리 공간을 쓰지 못하는 것이다.

페이징 기법을 사용하면 페이지 단위로 할당하기 때문에 내부 단편화가 발생할 것이고,

세그멘테이션 기법을 사용하면 필요한 공간만큼 할당하기 때문에 외부 단편화가 발생한다.

 

이 문제를 해결하기 위해, 최신 Windows 운영체제는 Low-Fragment-Heap 이라는 힙 관리 정책을 사용한다.

LFH의 front-end manager는
힙의 공간을 Bucket 이라는 12개의 서로 다른 크기를 가진 단위로 나누어서,
크기 별로 Free-List라는 공간에 저장한다.
그리고 이 Free-List들을 Look-Aside-List에 저장한다.
할당할 때 크기에 맞는 Bucket에 할당한다. 그리고 free상태가 되면 다시 해당 free-list에 들어간다.

할당할 크기가 규격의 크기를 넘으면, back-end manager가 기존의 free-list 대로 처리한다.

이를 통해 할당이 해제되어도 해당 규격에 맞는 것을 다시 넣을 수 있게 한다.

'CS 지식' 카테고리의 다른 글

프로세스, 스레드, 동기화  (1) 2023.05.10
캐시 메모리  (0) 2021.11.08
호출 규약  (0) 2021.10.25
endianness (엔디안)  (0) 2021.10.23
코드, 데이터, 힙, 스택 영역  (0) 2021.10.23