5 DERECHA
Operating System (Memory Manager) 본문
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 |