본문 바로가기

C, C++/WINDOWS VIA C,C++

[WINDOWS VIA C/C++] 13. 윈도우 메모리의 구조

https://www.hanbit.co.kr/store/books/look.php?p_code=B2974835990

 

제프리 리처의 Windows via C/C++(복간판)

이 책은 윈도우 XP, 윈도우 비스타, 윈도우 서버 2008까지 내용을 포괄한다. 이미 윈도우 10이 출시된 지 오래지만 윈도우의 기본 구조는 변하지 않아 아직까지도 이 책은 윈도우 시스템 프로그래

www.hanbit.co.kr

 

해당 책을 읽고 학습 목적으로 간단하게 정리한 글입니다.


개요

OS의 메모리 구조는 OS 동작의 핵심이다.

13챕터부터 18챕터까지 이어지는 파트3에서는 Windows가 메모리를 관리하는 방법에 대해서 알아본다.


프로세스의 가상 주소 공간

프로세스는 가상 주소 공간을 가진다. 

32비트 프로세스는 0x00000000 ~ 0xFFFFFFFF까지 표현할 수 있으며, 

64비트 프로세스는 0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF까지 표현할 수 있다.

 

가상 공간은 프로세스마다 개별적으로 고유하게 가지고 있기 때문에

같은 가상 주소공간이라도 서로 다른 프로세스는 완전히 다른 데이터를 가질 수 있다.

 

다만 가상 메모리는 물리 메모리와 매핑되어야 접근, 사용이 가능하다.


가상 주소 공간의 분할

가상 주소 공간은 분할되어 있다.

각 분할 공간을 파티션이라고 부른다.

 

기본적으로는 NULL 포인터 할당 구간, 유저 모드 구간, 커널 보호 구간, 커널 모드 구간으로 나뉜다.

 

nullptr 할당 파티션: 널 포인터 할당 구간은 nullptr 주소공간을 실수로 사용자가 접근하려고 하는 경우 예외를 내기 위한 공간이다.

 

유저 모드 파티션: 유저가 사용하는 데이터의 저장 공간이다. (32비트에서 유저 모드 파티션을 늘리는 방법과 64비트에서 유저 모드 파티션을 적게 쓰는 법을 설명하지만 생략)

 

커널 모드 파티션: OS를 구성하는 코드들이 들어간다. 이곳은 System Call을 통해서가 아니면 접근할 수 없으며 유저 모드에서 직접 접근하려고 하면 Access Violation이 발생한다. 물리 메모리에는 사실상 모든 프로세스가 같은 공간에 매핑된다.


주소 공간 내의 영역

프로세스 생성 이후에 주소 공간은 대부분 Free 상태가 된다.

이를 사용하기 위해서는 Reserve->Commit으로 페이지의 상태를 변화시켜야 하는데

이를 위해 VirtualAlloc을 사용한다.

 

기본적으로 메모리를 Reserve할 때는 64KB 경계에 맞게 시작 주소를 지정하고 4KB단위에 맞게 예약 크기를 지정해야 한다.

Commit을 위해서는 Reserve된 영역에 VirtualAlloc을 다시 호출해야 한다.

 

만약 커밋 페이지를 더 이상 사용하지 않으면 VirtualFree를 통해 디커밋할 수 있다.

LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);

 

https://learn.microsoft.com/ko-kr/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc

 

VirtualAlloc 함수(memoryapi.h) - Win32 apps

호출 프로세스의 가상 주소 공간에서 페이지 영역의 상태를 예약, 커밋 또는 변경합니다. (VirtualAlloc)

learn.microsoft.com

 

 


물리적 저장소와 페이징 파일

최근 OS는 RAM뿐만이 아니라 디스크 공간을 메모리 처럼 사용해 가상의 메모리로 사용한다.

이 메모리를 페이징 파일이라고 한다.

 

페이징 파일을 사용하기 위해서는 CPU의 지원이 선행되어야 한다.

nGB의 RAM과 mGB의 paging file이 있다면 Application은 n + m GB의 RAM이 있다가 판단할 수 있다.

 

만약 thread가 메모리에 접근할 때 RAM에 존재한다면 가상 메모리와 매핑해 접근한다.

만약 RAM에 존재하지 않으며 페이징 파일에 존재한다면, page fault가 일어나고, RAM의 Free page에 불러온다.

 

만약 메모리를 과다하게 사용해 RAM과 paging file간 교환 작업이 많아진다면, 원하는 메모리가 페이징 파일에 있을 확률이 높아지고 계속해서 교환해 CPU가 로직 처리 대신 스와핑에 시간을 더 쏟게 될 것이다.

이러한 현상을 Thrashing이라고 한다.

https://en.wikipedia.org/wiki/Thrashing_(computer_science) 

 

Thrashing (computer science) - Wikipedia

From Wikipedia, the free encyclopedia Constant exchange between memory and storage In computer science, thrashing occurs in a system with memory paging when a computer's real memory (RAM) resources are overcommitted, leading to a constant state of paging (

en.wikipedia.org

 

페이지 파일 내에 유지되지 않는 물리적 장소

exe, dll파일은 파일 자체가 메모리이므로 이를 페이지 파일 내에 유지할 필요가 없다.

 

하드 디스크에 존재하는 파일이 주소 공간의 특정 영역에 대한 물리적 저장소로 사용된다면 이를 메모리 맵 파일이라고 한다.

이 경우 일반적인 파일 IO보다 빠르게 처리 될 수 있다.

https://ko.wikipedia.org/wiki/%EB%A9%94%EB%AA%A8%EB%A6%AC_%EB%A7%B5_%ED%8C%8C%EC%9D%BC

 

메모리 맵 파일 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 메모리 맵 파일(Memory mapped file, MMF, 메모리 사상 파일)은 운영 체제에서 파일을 다루는 방법 중 하나이다. 물리 디스크 파일, 장치, 공유 메모리 객체와 같이 운

ko.wikipedia.org

(자세한 설명은 링크 참고)

 

또한 페이징 파일을 여러 개 사용해 페이징 파일로의 접근을 빠르게 할 수 있다.


페이지 보호 특성

매핑된 페이지들은 보호 특성을 지닐 수 있다.

https://learn.microsoft.com/ko-kr/windows/win32/memory/memory-protection-constants

 

메모리 보호 상수(WinNT.h) - Win32 apps

다음은 메모리 보호 옵션입니다. 메모리에서 페이지를 할당하거나 보호할 때 다음 값 중 하나를 지정해야 합니다. 보호 특성은 페이지의 일부에 할당할 수 없습니다. 전체 페이지에만 할당할 수

learn.microsoft.com

읽기, 쓰기, 실행 가능 여부를 페이지 단위로 설정할 수 있다.

 

Copy On Write(COW) 접근

exe파일의 .data의 전역변수 같은 값은 초기값이 존재해 모든 프로세스가 공통적으로 참조할 수 있지만, 만약 변경이 일어나면 고유의 영역으로 복사해 사용할 수 있다.

 

이러한 방식을 Copy On Write라고 한다.

 

특수 접근 보호 플래그

페이지에 보호용 플래그를 설정할 수 있다. 위의 링크를 통해 확인할 수 있다. (PAGE_GUARD의 경우 스레드 스택을 얘기할 때 다시 다룸)


VMMap 앱을 통해 아래와 같은 메모리 상황을 확인해볼 수 있다.

확인할 수 있는 메모리 영역의 타입은 아래와 같다.

Free는 매핑되지 않은 영역이다.

private은 시스템의 페이징 파일에 매핑되어있다.

Image는 이미지 파일(.exe, .dll)에 매핑되어있다.

Mapped 메모리 매핑 파일이 매핑되어있다.

chrome의 메모리 주소 일부


데이터 정렬의 중요성

CPU는 정렬된 데이터를 읽는 것에 특화되어있다.

예를 들어 4바이트 int는 4바이트 경계, 2바이트 short는 2바이트 경계에서 시작해야 데이터를 빨리 읽을 수 있다.

 

만약 데이터가 정렬되어있지 않다면, 예외가 발생하거나, 내부적으로 두번에 나눠서 데이터를 읽는다.

 

x86의 경우 내부적으로 정렬의 어긋남을 처리하기 때문에 OS차원에서 어떻게 처리했는지는 확인할 수 없다.