5 DERECHA

Operating System (Memory Manager) 본문

Operating System

Operating System (Memory Manager)

kay30 2023. 4. 17. 18:23

1. Header

(1) </Memory/PhysicalMemoryManager.h>

#pragma once
#include "windef.h"
#include "stdint.h"
#include "MultiBoot.h"
#include "Hal.h"

#define PMM_BLOCKS_PER_BYTE 8	
#define PMM_BLOCK_SIZE	4096	
#define PMM_BLOCK_ALIGN	BLOCK_SIZE
#define PMM_BITS_PER_INDEX	32

namespace PhysicalMemoryManager
{

	void	Initialize(multiboot_info* bootinfo);

	void SetBit(int bit);
	void UnsetBit(int bit);
	uint32_t GetMemoryMapSize(); //메모리맵의 크기를 얻어낸다.
	uint32_t GetKernelEnd(); //로드된 커널 시스템의 마지막 주소를 얻어낸다.

	//물리 메모리의 사용 여부에 따라 초기에 프레임들을 Set하거나 Unset한다	
	void	SetAvailableMemory(uint32_t, size_t);
	void	SetDeAvailableMemory(uint32_t base, size_t);

	void*	AllocBlock();//하나의 블록을 할당한다
	void	FreeBlock(void*);//하나의 블록을 해제한다.

	void*	AllocBlocks(size_t);//연속된 블록을 할당한다.
	void	FreeBlocks(void*, size_t);//사용된 연속된 메모리 블록을 회수한다.

	size_t GetMemorySize();//메모리 사이즈를 얻는다.

	unsigned int GetFreeFrame();
	unsigned int GetFreeFrames(size_t size);

	uint32_t GetUsedBlockCount();//사용된 블록수를 리턴한다
	uint32_t GetFreeBlockCount();//사용되지 않은 블록수를 리턴한다.

	uint32_t	GetFreeMemory();

	uint32_t GetTotalBlockCount();//블록의 전체수를 리턴한다
	uint32_t GetBlockSize();//블록의 사이즈를 리턴한다. 4K

	bool TestMemoryMap(int bit);

	void EnablePaging(bool state);
	bool	IsPaging();

	void LoadPDBR(uint32_t physicalAddr);
	uint32_t GetPDBR();
	
	//Debug
	void Dump();
}

 

(2) </Memory/VirtualMemoryManager.h>

#pragma once
#include "windef.h"
#include "stdint.h"
#include "Hal.h"
#include "PageDirectoryEntry.h"
#include "PageTableEntry.h"

#define USER_VIRTUAL_STACK_ADDRESS				0x00F00000
#define KERNEL_VIRTUAL_HEAP_ADDRESS				0x10000000

using namespace PageTableEntry;
using namespace PageDirectoryEntry;

// I86 아키텍쳐에서는 페이지테이블이나 페이지 디렉토리가 각각 1024개의 엔트리를 가진다
// 32비트에서 엔트리의 크기는 4바이트다. 
// 크기(4 * 1024 = 4K)
// 따라서 한 프로세스는 가상주소 4기가를 전부 표현하기 위해 약 4메가바이트의
// 메모리 공간을 필요로 한다(4K(PDE) + 1024 * 4K(PTE))
// 하나의 페이지 테이블은 4MB를 표현할 수 있고 페이지 디렉토리는 1024개의 페이지 테이블을
// 표현할 수 있으므로 4MB * 1024 = 4GB, 즉 4GB 바이트 전체를 표현할 수 있다.

#define PAGES_PER_TABLE		1024
#define PAGES_PER_DIRECTORY	1024
#define PAGE_TABLE_SIZE		4096

//페이지 테이블 하나당 주소 공간 : 4MB
#define PTABLE_ADDR_SPACE_SIZE 0x400000
//페이지 디렉토리 하나가 표현할 수 있는 있는 주소 공간 4GB
#define DTABLE_ADDR_SPACE_SIZE 0x100000000

#define PAGE_DIRECTORY_INDEX(x) (((x) >> 22) & 0x3ff)
#define PAGE_TABLE_INDEX(x) (((x) >> 12) & 0x3ff)
#define PAGE_GET_PHYSICAL_ADDRESS(x) (*x & ~0xfff)

#define MAX_PAGE_DIRECTORY_COUNT 40

typedef struct tag_PageTable 
{
	PTE m_entries[PAGES_PER_TABLE];
}PageTable;

typedef struct tag_PageDirectory 
{
	PDE m_entries[PAGES_PER_DIRECTORY];
}PageDirectory;

typedef struct tag_TaskSwitch
{
	int entryPoint;
	unsigned int procStack;
	LPVOID param;
}TaskSwitch;

namespace VirtualMemoryManager
{
	//가상 메모리를 초기화한다.		
	bool Initialize();

	//페이지를 할당한다.
	bool AllocPage(PTE* e);
	//페이지를 회수한다.
	void FreePage(PTE* e);

	PageDirectory* CreateCommonPageDirectory();
	void SetPageDirectory(PageDirectory* dir);

	//페이지 디렉토리를 PDTR 레지스터에 세트한다
	bool SetCurPageDirectory(PageDirectory* dir);

	bool SetKernelPageDirectory(PageDirectory* dir);

	//현재 페이지 디렉토리를 가져온다
	PageDirectory* GetCurPageDirectory();

	PageDirectory* GetKernelPageDirectory();

	//캐쉬된 TLS 락 버퍼를 비운다.
	void FlushTranslationLockBufferEntry(uint32_t addr);

	//페이지 테이블을 초기화한다.
	void ClearPageTable(PageTable* p);
	//페이지 테이블 엔트리(PTE)를 가져온다	
	PTE* GetPTE(PageTable* p, uint32_t addr);
	//주소로부터 PTE를 얻어온다
	uint32_t GetPageTableEntryIndex(uint32_t addr);
	//주소로부터 페이지 테이블을 얻어온다
	uint32_t GetPageTableIndex(uint32_t addr);
	//페이지 디렉토리를 초기화한다
	void ClearPageDirectory(PageDirectory* dir);
	//주소로부터 페이지 디렉토리 엔트리를 얻어온다	
	PDE* GetPDE(PageDirectory* p, uint32_t addr);
	//
	//페이지 테이블을 생성한다. 페이지 테이블의 크기는 4K다.
	bool CreatePageTable(PageDirectory* dir, uint32_t virt, uint32_t flags);
	//가상주소를 물리주소에 매핑한다. 이 과정에서 페이지 테이블 엔트리에 정보가 기록된다.
	void MapPhysicalAddressToVirtualAddresss(PageDirectory* dir, uint32_t virt, uint32_t phys, uint32_t flags);
	void MapPhysicalAddressToVirtualAddresss2(PageDirectory* dir, uint32_t virt, uint32_t phys, uint32_t flags);
	
	//가상주소로부터 실제 물리주소를 얻어낸다
	void* GetPhysicalAddressFromVirtualAddress(PageDirectory* directory, uint32_t virtualAddress);
	//페이지 디렉토리에 매핑된 페이지 디렉토리를 해제한다
	void UnmapPageTable(PageDirectory* dir, uint32_t virt);
	void UnmapPhysicalAddress(PageDirectory* dir, uint32_t virt);
	void FreePageDirectory(PageDirectory* dir);

	//페이지 디렉토리를 생성한다. 즉 가상주소공간을 생성한다는 의미다
	PageDirectory* CreatePageDirectory();

	bool CreateVideoDMAVirtualAddress(PageDirectory* pd, uintptr_t virt, uintptr_t phys, uintptr_t end);

	//Debug
	void Dump();
}

(3) PageTableEntry.h

#pragma once
#include <stdint.h>

namespace PageTableEntry
{	
	typedef uint32_t PTE;

	enum PAGE_PTE_FLAGS 
	{

		I86_PTE_PRESENT = 1,			//0000000000000000000000000000001
		I86_PTE_WRITABLE = 2,			//0000000000000000000000000000010
		I86_PTE_USER = 4,			//0000000000000000000000000000100
		I86_PTE_WRITETHOUGH = 8,			//0000000000000000000000000001000
		I86_PTE_NOT_CACHEABLE = 0x10,		//0000000000000000000000000010000
		I86_PTE_ACCESSED = 0x20,		//0000000000000000000000000100000
		I86_PTE_DIRTY = 0x40,		//0000000000000000000000001000000
		I86_PTE_PAT = 0x80,		//0000000000000000000000010000000
		I86_PTE_CPU_GLOBAL = 0x100,		//0000000000000000000000100000000
		I86_PTE_LV4_GLOBAL = 0x200,		//0000000000000000000001000000000
		I86_PTE_FRAME = 0x7FFFF000 	//1111111111111111111000000000000
	};
		
	void AddAttribute(PTE* entry, uint32_t attr);
	void DelAttribute(PTE* entry, uint32_t attr);
	void SetFrame(PTE* entry, uint32_t addr);
	bool IsPresent(PTE entry);
	bool IsWritable(PTE entry);
	uint32_t GetFrame(PTE entry);

};

2. CPP

(1) </Memory/PhyscialMemoryManager.cpp>

#include "SkyOS.h"
#include "Exception.h"

namespace PhysicalMemoryManager
{
	uint32_t	m_memorySize = 0;
	uint32_t	m_usedBlocks = 0;

	//이용할 수 있는 최대 블럭 갯수
	uint32_t	m_maxBlocks = 0;

	//비트맵 배열, 각 비트는 메모리 블럭을 표현, 비트맵처리
	uint32_t*	m_pMemoryMap = 0;
	uint32_t	m_memoryMapSize = 0;	

	// memorySize : 전체 메모리의 크기(바이트 사이즈)
	//bitmapAddr : 커널다음에 배치되는 비트맵 배열
	uint32_t g_totalMemorySize = 0;

	uint32_t GetTotalMemory(multiboot_info* bootinfo)
	{
		uint64_t endAddress = 0;

		uint32_t mmapEntryNum = bootinfo->mmap_length / sizeof(multiboot_memory_map_t);

		multiboot_mmap_entry* mmapAddr = (multiboot_mmap_entry*)bootinfo->mmap_addr;

#ifdef _SKY_DEBUG
		SkyConsole::Print("Memory Map Entry Num : %d\n", mmapEntryNum);
#endif
		
		for (uint32_t i = 0; i < mmapEntryNum; i++)
		{
			uint64_t areaStart = mmapAddr[i].addr;
			uint64_t areaEnd = areaStart + mmapAddr[i].len;

			//SkyConsole::Print("0x%q 0x%q\n", areaStart, areaEnd);
		
			if (mmapAddr[i].type != 1)
			{
				continue;
			}
					
			if (areaEnd > endAddress)
				endAddress = areaEnd;
		}

		if (endAddress > 0xFFFFFFFF) {
			endAddress = 0xFFFFFFFF;
		}

		return (uint32_t)endAddress;
	}
    
	void Initialize(multiboot_info* bootinfo)
	{
		SkyConsole::Print("Physical Memory Manager Init..\n");

		g_totalMemorySize = GetTotalMemory(bootinfo);
		
		m_usedBlocks = 0;
		m_memorySize = g_totalMemorySize;
		m_maxBlocks = m_memorySize / PMM_BLOCK_SIZE;

		int pageCount = m_maxBlocks / PMM_BLOCKS_PER_BYTE / PAGE_SIZE;
		if (pageCount == 0)
			pageCount = 1;

		m_pMemoryMap = (uint32_t*)GetKernelEnd(bootinfo);

		SkyConsole::Print("Total Memory (%dMB)\n", g_totalMemorySize / 1048576);
		SkyConsole::Print("BitMap Start Address(0x%x)\n", m_pMemoryMap);
		SkyConsole::Print("BitMap Size(0x%x)\n", pageCount * PAGE_SIZE);		

		//블럭들의 최대 수는 8의 배수로 맞추고 나머지는 버린다
		//m_maxBlocks = m_maxBlocks - (m_maxBlocks % PMM_BLOCKS_PER_BYTE);

		//메모리맵의 바이트크기
		m_memoryMapSize = m_maxBlocks / PMM_BLOCKS_PER_BYTE;
		m_usedBlocks = GetTotalBlockCount();

		// align -> 4KB
		int tempMemoryMapSize = (GetMemoryMapSize() / 4096) * 4096;

		if (GetMemoryMapSize() % 4096 > 0)
			tempMemoryMapSize += 4096;

		m_memoryMapSize = tempMemoryMapSize;

		//모든 메모리 블럭들이 사용중에 있다고 설정한다.	
		unsigned char flag = 0xff;
		memset((char*)m_pMemoryMap, flag, m_memoryMapSize);
		SetAvailableMemory((uint32_t)m_pMemoryMap, m_memorySize);
	}

	uint32_t GetMemoryMapSize() { return m_memoryMapSize; }
	uint32_t GetKernelEnd() { return (uint32_t)m_pMemoryMap; }
    
    void* AllocBlock() {

		if (GetFreeBlockCount() <= 0)
			return NULL;

		unsigned int frame = GetFreeFrame();

		if (frame == -1)
			return NULL;

		SetBit(frame);
		//SkyConsole::Print("free frame : 0x%x\n", frame);

		uint32_t addr = frame * PMM_BLOCK_SIZE + (uint32_t)m_pMemoryMap;
		m_usedBlocks++;

		return (void*)addr;
	}

	void FreeBlock(void* p) {

		uint32_t addr = (uint32_t)p;
		int frame = addr / PMM_BLOCK_SIZE;

		UnsetBit(frame);

		m_usedBlocks--;
	}
}

(2) VirtualMemoryManager.cpp

#include "VirtualMemoryManager.h"
#include "PhysicalMemoryManager.h"
#include "string.h"
#include "memory.h"
#include "SkyConsole.h"
#include "MultiBoot.h"	
#include "SkyAPI.h"


PageDirectory* g_pageDirectoryPool[MAX_PAGE_DIRECTORY_COUNT];
bool g_pageDirectoryAvailable[MAX_PAGE_DIRECTORY_COUNT];

namespace VirtualMemoryManager
{
	//! current directory table

	PageDirectory*		_kernel_directory = 0;
	PageDirectory*		_cur_directory = 0;	

	//가상 주소와 매핑된 실제 물리 주소를 얻어낸다.
	void* VirtualMemoryManager::GetPhysicalAddressFromVirtualAddress(PageDirectory* directory, uint32_t virtualAddress)
	{
		PDE* pagedir = directory->m_entries;
		if (pagedir[virtualAddress >> 22] == 0)
			return NULL;

		return (void*)((uint32_t*)(pagedir[virtualAddress >> 22] & ~0xfff))[virtualAddress << 10 >> 10 >> 12];
	}

	//페이지 디렉토리 엔트리 인덱스가 0이 아니면 이미 페이지 테이블이 존재한다는 의미
	bool CreatePageTable(PageDirectory* dir, uint32_t virt, uint32_t flags)
	{
		PDE* pageDirectory = dir->m_entries;
		if (pageDirectory[virt >> 22] == 0)
		{
			void* pPageTable = PhysicalMemoryManager::AllocBlock();
			if (pPageTable == nullptr)
				return false;					

			memset(pPageTable, 0, sizeof(PageTable));
			pageDirectory[virt >> 22] = ((uint32_t)pPageTable) | flags;					
		}
		return true;
	}

	//PDE나 PTE의 플래그는 같은 값을 공유
	//가상주소를 물리 주소에 매핑
	void MapPhysicalAddressToVirtualAddresss(PageDirectory* dir, uint32_t virt, uint32_t phys, uint32_t flags)
	{
		kEnterCriticalSection();
		PhysicalMemoryManager::EnablePaging(false);
		PDE* pageDir = dir->m_entries;				

		if (pageDir[virt >> 22] == 0)
		{
			CreatePageTable(dir, virt, flags);
		}

		uint32_t mask = (uint32_t)(~0xfff);
		uint32_t* pageTable = (uint32_t*)(pageDir[virt >> 22] & mask);

		pageTable[virt << 10 >> 10 >> 12] = phys | flags;

		PhysicalMemoryManager::EnablePaging(true);
		kLeaveCriticalSection();
	}
    
    bool Initialize()
	{
		SkyConsole::Print("Virtual Memory Manager Init..\n");

		for (int i = 0; i < MAX_PAGE_DIRECTORY_COUNT; i++)
		{
			g_pageDirectoryPool[i] = (PageDirectory*)PhysicalMemoryManager::AllocBlock();
			g_pageDirectoryAvailable[i] = true;
		}

		PageDirectory* dir = CreateCommonPageDirectory();

		if (nullptr == dir)
			return false;

		//페이지 디렉토리를 PDBR 레지스터에 로드한다
		SetCurPageDirectory(dir);
		SetKernelPageDirectory(dir);

		SetPageDirectory(dir);

		//페이징 기능을 다시 활성화시킨다
		PhysicalMemoryManager::EnablePaging(true);
		
		return true;
	}
    
    PageDirectory* CreateCommonPageDirectory()
	{
				
		//페이지 디렉토리 생성. 가상주소 공간 
		//4GB를 표현하기 위해서 페이지 디렉토리는 하나면 충분하다.
		//페이지 디렉토리는 1024개의 페이지테이블을 가진다
		//1024 * 1024(페이지 테이블 엔트리의 개수) * 4K(프레임의 크기) = 4G		
		int index = 0;
		for (; index < MAX_PAGE_DIRECTORY_COUNT; index++)
		{
		
			if (g_pageDirectoryAvailable[index] == true)
				break;
		}

		if (index == MAX_PAGE_DIRECTORY_COUNT)
			return nullptr;

		PageDirectory* dir = g_pageDirectoryPool[index];
	
		if (dir == NULL)
			return nullptr;

		g_pageDirectoryAvailable[index] = false;
		memset(dir, 0, sizeof(PageDirectory));

		uint32_t frame = 0x00000000;
		uint32_t virt = 0x00000000;

		//페이지 테이블을 생성
		for (int i = 0; i < 2; i++)
		{			
			PageTable* identityPageTable = (PageTable*)PhysicalMemoryManager::AllocBlock();
			if (identityPageTable == NULL)
			{
				return nullptr;
			}

			memset(identityPageTable, 0, sizeof(PageTable));
			
			//물리 주소를 가상 주소와 동일하게 매핑시킨다
			for (int j = 0; j < PAGES_PER_TABLE; j++, frame += PAGE_SIZE, virt += PAGE_SIZE)
			{
				PTE page = 0;
				PageTableEntry::AddAttribute(&page, I86_PTE_PRESENT);

				PageTableEntry::SetFrame(&page, frame);
				
				identityPageTable->m_entries[PAGE_TABLE_INDEX(virt)] = page;
			}

			//페이지 디렉토리에 페이지 디렉토리 엔트리(PDE)를 한 개 세트한다
			//0번째 인덱스에 PDE를 세트한다(가상주소가 0X00000000일시 참조됨)
			//앞에서 생성한 아이덴티티 페이지 테이블을 세트한다
			//가상주소 = 물리주소
			PDE* identityEntry = &dir->m_entries[PAGE_DIRECTORY_INDEX((virt - 0x00400000))];
			PageDirectoryEntry::AddAttribute(identityEntry, I86_PDE_PRESENT | I86_PDE_WRITABLE);			
			PageDirectoryEntry::SetFrame(identityEntry, (uint32_t)identityPageTable);
		}

		return dir;
	}
}

(3) PageTableEntry.cpp

#include "PageTableEntry.h"

namespace PageTableEntry
{
	void AddAttribute(PTE* entry, uint32_t attr)
	{
		*entry |= attr;
	}

	void DelAttribute(PTE* entry, uint32_t attr)
	{
		*entry &= ~attr;
	}

	void SetFrame(PTE* entry, uint32_t addr)
	{
		*entry = (*entry & ~I86_PTE_FRAME) | addr;
	}

	bool IsPresent(PTE entry)
	{
		return entry & I86_PTE_PRESENT;
	}

	bool IsWritable(PTE entry)
	{
		return (entry & I86_PTE_WRITABLE) > 0;
	}

	uint32_t GetFrame(PTE entry)
	{
		return entry & I86_PTE_FRAME;
	}
};

(4) VirtualMemoryManager

bool CreateVideoDMAVirtualAddress(PageDirectory* pd, uintptr_t virt, uintptr_t phys, uintptr_t end)
	{
		//void* memory = PhysicalMemoryManager::AllocBlocks((end - start)/ PAGE_SIZE);
		for (int i = 0; virt <= end; virt += 0x1000, phys += 0x1000, i++)
		{
			MapPhysicalAddressToVirtualAddresss(pd, (uint32_t)virt, (uint32_t)phys, I86_PTE_PRESENT | I86_PTE_WRITABLE);
		}

		return true;
	}

'Operating System' 카테고리의 다른 글

Interview Questions  (0) 2023.10.22
Operating System (Hardware Initialization)  (0) 2023.04.11
Operating System  (0) 2023.03.06