Operating System/Windows 시스템 프로그래밍 - 기본

메모리 시스템

Tony Lim 2023. 7. 15. 20:52

Windows 시스템 메모리 운용 방법

  • 가상 메모리 시스템 직접 활용
    • 메모리 맵
      • File -> char * 로 맵핑해서 쓰는것 (비동기 쓰기)
      • 크기가 작은 데이터 (4kb 이하) 할당 시 매우 우용
      • 할당 단위나 페이지 경계를 고려하지 않음
      • heap data structure 로 관리되고 있는 메모리를 의미

ram 이 16기가 라 하더라도 32 bit cpu processor 에서 process 는 4gb 정도로 최대로 할당 받기 때문에 4개의 program(process) 만 띄우더라도 꽉차게 된다.

process가 할당 받은 VMS(virutal memeroy space) 에서 받은 user , 나머지 반은 kernel 이 가지게 되고 이 작업을 OS 나 compiler가 하게 된다.

user space에 static, stack , heap 이런 것들이 다 들어 있는 것이다.

 

여러개의 process 가 동작할 때 kernel 부분은 다 동일 phyiscal memory를 가르키고 있다.

user 부분만 실제로 다른 physical memory 를 가르키게 된다.

 

thread에서 메모리할당 해제 하는것은 process에게 할당된 VMS 어딘가를 쓰게 되는 것이다.

VMS의 단위는 Page 이고 4KB이다. ssd, ram 도 마찬가지이다.

notepad에서 A하나 작성하고 저장하면 실제 쓰는 용량은 1Byte이지만 단위가 4KB 페이지로 끊기기 때문에 4KB-1byte만큼 쓸 수 없는 메모리 영역이 생기게 된다.

 개발자 입장에서는 그냥 pointer로 VMS에 접근하여 사용하는 메모리 공간이지만 실제 physical memory는 Ram , SSD  뭐가 될지 모르고 몰라도 된다.

같은 0x0019F4DD라는 메모리 주소를 각 프로세스가 같게 되지만 그것이 실제 physical memory가 같음을 의미하지 않는다.

RAM -> SSD로 가는것을 page out 반대는 page in 다른말로 swap in , swap out

malloc 한 상태에서 process 가 죽었을 때 free안해도 os가 resource들을 다 회수하게 된다.

 


Windows 환경 메모리 할당 함수

malloc() , new

  • 프로세스 기본 Heap 영역 (1MB) 사용

Local / GlobalAlloc() 

  • Heap 영역 내부에서 다시 Local , Global 영역을 사용
  • Win16 호환성을 위해 존재하는 구형
  • Clipboard , OLE 에서 여전히 사용중

HeapAlloc()

  • 가상 메모리 공간 중 일부를 할당 받아 빠르게 사용
  • malloc 같은것이 해당됨 (1MB)
  • 1mb 정도는 이미 확보가 되어있다

VirtualAlloc()

  • 대용량 메모리 사용시 활용
  • HeapAlloc() 함수가 내부적으로 호출

 

Windows 메모리 시스템

  • 모든 프로세스는 private address space 를 가진다 (VMS)
  • 가상 주소 공간은 분할되어 있으며 이를 (메모리) 파티션이라함
    • Null pointer와 User mode
    • 64kb 접근 금지(Application 의 0부터 64kb는 os가 쓰는 영역) 와 kernel mode
  • 가상 메모리 공간(영역)을 사용하기 위해서는 예약 해야 함
    • 예약후에 확정이 되어야 실제로 사용을 할 수 있다.
    • VMS를 따라갔을 때 실제로 Physical memory로 가야 commit이 된것이다.

  • 대부분의 CPU는 보통 64KB단위 (Allocation Size)로 관리
    • VMS에서 관리하는 단위는 page단위인 4KB 
    • 즉 RAM에서는 64KB로 OS에 나눠주고 OS는 process에게 4KB로 1page 단위로 나눠준다
  • 할당하는 물리적 메모리 영역의 시작 주소는 64KB로 나누어 떨어지는 위치에서 시작
  • 메모리 공간 예약시 반드시 시스템 페이지 단위를 사용함
    • x86, x64  는 4KB
    • IA-64는 8KB
  • 10KB 영역 예약시 OS는 12KB(4KB의 배수에 맞춰서) 영역을 예약한다.

 

  • 예약된 메모리 공간을 사용하려면 반드시 실제 물리 메모리 영역을 매핑해야 함
    • 예약만 하게 되면 실제로 VMS에서 pyhsical로 접근시도하면 Page fault가 나게 된다.
  • 이를 물리 메모리를 commit한다 라고 표현함
  • commit  된 물리 메모리를 사용할 필요가 없다면 Decommit하여 해제

 

User mode 파티션

  • 실행파일(.exe 와 .dll) 모듈이 로드되는 메모리 공간
    • 4GB의 반인 2GB 이 가용공간인것이고
    • page commit을 해야 실제로 사용이 가능
  • 각 프로세스 마다 독립성 보장
  • 각종 데이터 영역 메모리(Stack, Heap 등) 가 위치하는 가용 공간
  • 가용 공간은 할당되지 않은 상태로 존재

 

Kernel mode 파티션

  • OS를 구성하는 코드들이 위치하고 사용하는 영역
  • 이 영역은 모든 프로세스가 공유하며 User mode 응용 프로그램이 절대로 접근 할 수 없음

힙 메모리 활용

 

프로세스 기본 힙

  • 프로세스 실행 시 OS는 프로세스 주소 공간에 기본 힙을 생성
  • 여러 스레드가 동시에 메모리 할당 / 해제시 기본 힙의 처리 대기열을 활용해 요청을 순차처리
  • 한 프로세스 에 여러 힙을 만들 수 있음 

malloc은 thread safe하다. heap에서는 malloc 요청을 큐에다 쌓아놓고 순차적으로 처리하기 때문이다.
이때 Queue가 동기화되어 있기 때문에 문제가 생기지 않는것이다.

 

추가 힙을 생성하는 이유

  • 컴포넌트 보호
    • 연결리스트와 2진 트리 동시 운영시 연결리스트에서 실수했는데 , 2진 트리까지 영향을 줄 수 있음
    • 이것을 스레드 마다 사용할 수 있는 힙을 생성해줘서 막을 수 있음
  • 효율적인 메모리 관리 (단편화 방지)
    • Allocation Size = 64KB(RAM)
    • Page = 4KB
    • 쓰지 못하는 공간이 많이 생기지 않는다.
  • 지역적인 접근
  • 스레드 동기화 비용 회피
  • 빠른 메모리 해제

 

힙 관련 핵심 API

GetProcessHeap
HeapCreate/Destroy
HeapAlloc/ Free
HeapReAlloc
HeapSize

1MB가 미리 확보 되어있지만 런타임에 100MB를 요청해도 램에 공간이있으면 가능하다. 다만 오래 걸릴뿐이다.

HEAP_NO_SERIALIZE = 옵션을 주게 되면 동기화를 하지 않겠다는 것이다. free, allocate가 동시에 접근해도 원래는 힙에서 보호해줬는데 개발자 자신이 락을 잡아서 동기화를 하겠다는 의미

HEAP_GENERATE_EXCEPTION = 실제 램의 공간이 부족하면 실패하게 된다.


프로세스 힙 확인

int main()
{
	//HANDLE hProcHeap = ::GetProcessHeap();
    HANDLE hProcHeap = ::HeapCreate(0, 0, 0);
	void *pMem1 = ::HeapAlloc(hProcHeap, 0, 4);
    void* pMem2 = ::HeapAlloc(hProcHeap, 0, 1024 * 1024 * 4);

    PROCESS_HEAP_ENTRY Entry = { 0 };
	while (::HeapWalk(hProcHeap, &Entry) != FALSE)
	{
        if ((Entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
            _tprintf(TEXT("Allocated block"));

            if ((Entry.wFlags & PROCESS_HEAP_ENTRY_MOVEABLE) != 0) {
                _tprintf(TEXT(", movable with HANDLE %#p"), Entry.Block.hMem);
            }

            if ((Entry.wFlags & PROCESS_HEAP_ENTRY_DDESHARE) != 0) {
                _tprintf(TEXT(", DDESHARE"));
            }
        }
        else if ((Entry.wFlags & PROCESS_HEAP_REGION) != 0) {
            _tprintf(TEXT("Region\n  %d bytes committed\n") \
                TEXT("  %d bytes uncommitted\n  First block address: %#p\n") \
                TEXT("  Last block address: %#p\n"),
                Entry.Region.dwCommittedSize,
                Entry.Region.dwUnCommittedSize,
                Entry.Region.lpFirstBlock,
                Entry.Region.lpLastBlock);
        }
        else if ((Entry.wFlags & PROCESS_HEAP_UNCOMMITTED_RANGE) != 0) {
            _tprintf(TEXT("Uncommitted range\n"));
        }
        else {
            _tprintf(TEXT("Block\n"));
        }

        _tprintf(TEXT("  Data portion begins at: %#p\n  Size: %d bytes\n") \
            TEXT("  Overhead: %d bytes\n  Region index: %d\n\n"),
            Entry.lpData,
            Entry.cbData,
            Entry.cbOverhead,
            Entry.iRegionIndex);
	}

	::HeapFree(hProcHeap, 0, pMem1);
    ::HeapFree(hProcHeap, 0, pMem2);
    ::HeapDestroy(hProcHeap);
}

HeapWalk를 통해 Heap을 순회하면서 entry상태를 조사해서 참조할 수 있게 된다.

Region
  8192 bytes committed
  57344 bytes uncommitted
  First block address: 000002376F9A0750
  Last block address: 000002376F9AF000
  Data portion begins at: 000002376F9A0000
  Size: 1856 bytes
  Overhead: 0 bytes
  Region index: 0

Allocated block  Data portion begins at: 000002376F9A0860
  Size: 4 bytes
  Overhead: 28 bytes
  Region index: 0

Block
  Data portion begins at: 000002376F9A0890
  Size: 5936 bytes
  Overhead: 32 bytes
  Region index: 0

Uncommitted range
  Data portion begins at: 000002376F9A2000
  Size: 53248 bytes
  Overhead: 0 bytes
  Region index: 0

Heap은 region 이라는 개념을 가지고 있다. 

기본 Heap은 1MB , region #0 처럼 번호를 가지고 있다.

4byte만 allocate 하려했으나 실제로 관리되고 있는 block size가 32 byte단위라 28byte의 overhead가 생긴것이다.


가상 메모리와 보호 모드

문자배열상수는 원래는 read only인데 VirutalProtect 함수로 해당 메모리 페이지의 보호 모드를 RW으로 바꾼후에 덮어 쓰기를 할 수 있다.

 

 

 

 

 

 

 

'Operating System > Windows 시스템 프로그래밍 - 기본' 카테고리의 다른 글

DLL  (0) 2023.07.20
프로세스 관리  (0) 2023.07.18
Win32 파일 입/출력  (0) 2023.07.10
스레드 동기화  (0) 2023.07.07
스레드 생성 및 제어  (0) 2023.07.05