Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Пишем свою операционную систему. Менеджер памяти


Пришло время начать писать менеджер памяти - важнейший компонент любой операционной системы. Однако до его реализации в коде почему-то не дошла ни одна из известных мне рассылок.

Напишем заголовочный файл memory_manager.h, обозначив задачи на этот выпуск:

#ifndef MEMORY_MANAGER_H
#define MEMORY_MANAGER_H

#include "stdlib.h"

#define PAGE_SIZE 0x1000
#define PAGE_OFFSET_BITS 12
#define PAGE_OFFSET_MASK 0xFFF
#define PAGE_TABLE_INDEX_BITS 10
#define PAGE_TABLE_INDEX_MASK 0x3FF

#define PHYADDR_BITS 32

#define PAGE_VALID 1
#define PAGE_WRITABLE 2
#define PAGE_USER 4

typedef size_t phyaddr;

#define KERNEL_BASE 0xFFC00000
#define KERNEL_PAGE_TABLE 0xFFFFE000
#define TEMP_PAGE 0xFFFFF000
#define TEMP_PAGE_INFO (KERNEL_PAGE_TABLE + ((TEMP_PAGE >> PAGE_OFFSET_BITS) & PAGE_TABLE_INDEX_MASK) * sizeof(phyaddr))

phyaddr kernel_page_dir;
size_t memory_size;

void init_memory_manager(void *memory_map);

void temp_map_page(phyaddr addr);
bool map_pages(phyaddr page_dir, void *vaddr, phyaddr paddr, size_t count, unsigned int flags);
phyaddr get_page_info(phyaddr page_dir, void *vaddr);

size_t get_free_memory_size();
phyaddr alloc_phys_pages(size_t count);
void free_phys_pages(phyaddr base, size_t count);

#endif

Помимо описания функций в этом файле есть и описания адресов и размеров некоторых структур работы с виртуальной памятью, а также некоторые доступные флаги для проекции страниц (PAGE_VALID - обязательный флаг, PAGE_WRITABLE - страница доступна для записи, PAGE_USER - страница доступа для непривилегированного кода).

Начнём писать memory_manager.c с простой функции инициализации:

#include "stdlib.h"
#include "memory_manager.h"

typedef struct {
	uint64 base;
	uint64 length;
	uint32 type;
	uint32 acpi_ext_attrs;
} __attribute__((packed)) MemoryMapEntry;

size_t free_page_count;
phyaddr free_phys_memory_pointer;

void init_memory_manager(void *memory_map) {
	asm("movl %%cr3, %0":"=a"(kernel_page_dir));
	memory_size = 0x100000;
	free_page_count = 0;
	free_phys_memory_pointer = 0;
	MemoryMapEntry *entry;
	for (entry = memory_map; entry->type; entry++) {
		if ((entry->type == 1) && (entry->base >= 0x100000)) {
			free_phys_pages(entry->base, entry->length >> PAGE_OFFSET_BITS);
			memory_size += entry->length;
		}
	}
}

Эта функция сохраняет текущее значение CR3 в переменную kernel_page_dir (потом нам пригодится для вызова map_pages), а также интерпретирует карту памяти, полученную когда-то от BIOS. Для всех блоков памяти, которые выше 1-ого мегабайта, а также доступны для использования (тип 1) вызывается функция free_phys_pages, которая должна помечать указанный регион физической памяти как свободный. Параллельно с этим init_memory_manager вычисляет полный объём оперативной памяти, который будет доступен в глобальной переменной memory_size.

Теперь напишем пару маленьких функций, одна из которых полезна для других модулей, а вторая вообще будет одной из самых часто используемых:

void temp_map_page(phyaddr addr) {
	((phyaddr*)TEMP_PAGE_INFO) = (page & ~PAGE_OFFSET_MASK) | PAGE_VALID | PAGE_WRITABLE;
	asm("invlpg (,%0,)"::"a"(TEMP_PAGE));
}

size_t get_free_memory_size() {
	return free_page_count << PAGE_OFFSET_BITS;
}

Самая главная функция - temp_map_page - она позволяет спроецировать любую физическую страницу по адресу TEMP_PAGE, через это "окно" многие другие функции обращаются к неспроецированным страницам, как это работает мы очень скоро увидим. Вторая функция нужна другим модулям, чтобы получить размер свободной незанятой памяти в байтах, а не страницах.

После того как написана функция temp_map_page, мы можем сделать и две других - map_pages и get_page_info. Первая позволяет спроецировать указанный блок физических страниц по нужному виртуальному адресу, а вторая узнать куда же будет отображён виртуальный адрес и отображён ли он.

bool map_pages(phyaddr page_dir, void *vaddr, phyaddr paddr, size_t count, unsigned int flags) {
	for (; count; count--) {
		phyaddr page_table = page_dir;
		char shift;
		for (shift = PHYADDR_BITS - PAGE_TABLE_INDEX_BITS; shift >= PAGE_OFFSET_BITS; shift -= PAGE_TABLE_INDEX_BITS) {
			unsigned int index = ((size_t)vaddr >> shift) & PAGE_TABLE_INDEX_MASK;
			temp_map_page(page_table);
			if (shift > PAGE_OFFSET_BITS) {
				page_table = ((phyaddr*)TEMP_PAGE)[index];
				if (!(page_table & PAGE_VALID)) {
					phyaddr addr = alloc_phys_pages(1);
					if (addr) {
						temp_map_page(addr);
						memset((void*)TEMP_PAGE, 0, PAGE_SIZE);
						temp_map_page(page_table);
						((phyaddr*)TEMP_PAGE)[index] = addr | PAGE_VALID | PAGE_WRITABLE | PAGE_USER;
						page_table = addr;
					} else {
						return false;
					}
				}
			} else {
				((phyaddr*)TEMP_PAGE)[index] = (paddr & ~PAGE_OFFSET_BITS) | flags;
				asm("invlpg (,%0,)"::"a"(vaddr));
			}
		}
		vaddr += PAGE_SIZE;
		paddr += PAGE_SIZE;
	}
	return true;
}

phyaddr get_page_info(phyaddr page_dir, void *vaddr) {
	phyaddr page_table = page_dir;
	char shift;
	for (shift = PHYADDR_BITS - PAGE_TABLE_INDEX_BITS; shift >= PAGE_OFFSET_BITS; shift -= PAGE_TABLE_INDEX_BITS) {
		unsigned int index = ((size_t)vaddr >> shift) & PAGE_TABLE_INDEX_MASK;
		temp_map_page(page_table);
		if (shift > PAGE_OFFSET_BITS) {
			page_table = ((phyaddr*)TEMP_PAGE)[index];
			if (!(page_table & PAGE_VALID)) {
				return 0;
			}
		} else {
			return ((phyaddr*)TEMP_PAGE)[index];
		}
	}
} 

Теперь у нас есть функции для проецирования страниц в виртуальное адресное пространство, а также для получения информации о виртуальной странице. Хотя пока у нас нет функций для выделения и освобождения физических страниц, map_pages и get_page_info уже будут работать, пока нет необходимости создавать новую таблицу страниц (то есть мы можем проецировать страницы в первые и последние 4 МБ).

Управление физическими страницами требует отдельного обсуждения и если менеджер физической памяти описать в этом же выпуске, он получится слишком большим, поэтому пока сделаем лишь функции-заглушки, а полный код рассмотрим в следующий раз:

phyaddr alloc_phys_pages(size_t count) {
	return 0;
}

void free_phys_pages(phyaddr base, size_t count) {
	
} 

Теперь мы можем изменить выделение памяти под таблицу прерываний в функции init_interrupts файла interrupts.c на более удобное:

#include "stdlib.h"
#include "memory_manager.h"
#include "interrupts.h"

typedef struct {
	uint16 address_0_15;
	uint16 selector;
	uint8 reserved;
	uint8 type;
	uint16 address_16_31;
} __attribute__((packed)) IntDesc;

typedef struct {
	uint16 limit;
	void *base;
} __attribute__((packed)) IDTR;

IntDesc *idt = (void*)0xFFFFC000;

void timer_int_handler();

void init_interrupts() {
	map_pages(kernel_page_dir, idt, 0x8000, 1, PAGE_VALID | PAGE_WRITABLE);
	memset(idt, 0, 256 * sizeof(IntDesc));
	IDTR idtr = {256 * sizeof(IntDesc), idt};
	asm("lidt (,%0,)"::"a"(&idtr));
	irq_base = 0x20;
	irq_count = 16;
	outportb(0x20, 0x11);
	outportb(0x21, irq_base);
	outportb(0x21, 4);
	outportb(0x21, 1);
	outportb(0xA0, 0x11);
	outportb(0xA1, irq_base + 8);
	outportb(0xA1, 2);
	outportb(0xA1, 1);
	set_int_handler(irq_base, timer_int_handler, 0x8E);
	asm("sti");
} 

Осталось добавить код инициализации в kernel_main (менеджер памяти следует инициализировать самым первым, до всех остальных подсистем ядра):

#include "stdlib.h"
#include "memory_manager.h"
#include "interrupts.h"
#include "tty.h"

typedef struct {
	uint64 base;
	uint64 size;
} BootModuleInfo;

void kernel_main(uint8 boot_disk_id, void *memory_map, BootModuleInfo *boot_module_list) {
	init_memory_manager(memory_map);
	init_interrupts();
	init_tty();
	set_text_attr(15);
	printf("Welcome to MyOS!\n");
	printf("kernel_page_dir = 0x%x\n", kernel_page_dir);
	printf("memory_size = %d MB\n", memory_size / 1024 / 1024);
        printf("get_page_info(kernel_page_dir, 0xB8000) = 0x%x\n", get_page_info(kernel_page_dir, (void*)0xB8000));
}

Помимо собственно инициализации менеджера памяти этот код демонстрирует работу некоторых функций. На экране должно появится:

Welcome to MyOS!
kernel_page_dir = 0x1000
memory_size = 63 MB
get_page_info(kernel_page_dir, 0xB8000) = 0xB8063

Для компиляции следует дописать Makefile:

ifdef OS
	LDFLAGS = -mi386pe
else
	LDFLAGS = -melf_i386
endif

CFLAGS = -m32 -ffreestanding

all: script.ld startup.o stdlib.o main.o memory_manager.o interrupts.o tty.o
	ld $(LDFLAGS) -T script.ld -o kernel.bin startup.o stdlib.o main.o memory_manager.o interrupts.o tty.o
	objcopy kernel.bin -O binary
startup.o: startup.i386.asm
	fasm startup.i386.asm startup.o
stdlib.o: stdlib.c stdlib.h
	gcc -c $(CFLAGS) -o stdlib.o stdlib.c
main.o: main.c stdlib.h interrupts.h tty.h
	gcc -c $(CFLAGS) -o main.o main.c
memory_manager.o: memory_manager.c memory_manager.h stdlib.h
	gcc -c $(CFLAGS) -o memory_manager.o memory_manager.c
interrupts.o: interrupts.c interrupts.h stdlib.h
	gcc -c $(CFLAGS) -o interrupts.o interrupts.c
tty.o: tty.c tty.h stdlib.h
	gcc -c $(CFLAGS) -o tty.o tty.c
clean:
	rm -v *.o kernel.bin 

Вот и всё! В заключение приведу полный код memory_manager.c:

#include "stdlib.h"
#include "memory_manager.h"

typedef struct {
	uint64 base;
	uint64 length;
	uint32 type;
	uint32 acpi_ext_attrs;
} __attribute__((packed)) MemoryMapEntry;

size_t free_page_count = 0;
phyaddr free_phys_memory_pointer = 0;

void init_memory_manager(void *memory_map) {
	asm("movl %%cr3, %0":"=a"(kernel_page_dir));
	memory_size = 0x100000;
	MemoryMapEntry *entry;
	for (entry = memory_map; entry->type; entry++) {
		if ((entry->type == 1) && (entry->base >= 0x100000)) {
			free_phys_pages(entry->base, entry->length >> PAGE_OFFSET_BITS);
			memory_size += entry->length;
		}
	}
}

void temp_map_page(phyaddr addr) {
	*((phyaddr*)TEMP_PAGE_INFO) = (addr & ~PAGE_OFFSET_MASK) | PAGE_VALID | PAGE_WRITABLE;
	asm("invlpg (,%0,)"::"a"(TEMP_PAGE));
}

bool map_pages(phyaddr page_dir, void *vaddr, phyaddr paddr, size_t count, unsigned int flags) {
	for (; count; count--) {
		phyaddr page_table = page_dir;
		char shift;
		for (shift = PHYADDR_BITS - PAGE_TABLE_INDEX_BITS; shift >= PAGE_OFFSET_BITS; shift -= PAGE_TABLE_INDEX_BITS) {
			unsigned int index = ((size_t)vaddr >> shift) & PAGE_TABLE_INDEX_MASK;
			temp_map_page(page_table);
			if (shift > PAGE_OFFSET_BITS) {
				page_table = ((phyaddr*)TEMP_PAGE)[index];
				if (!(page_table & PAGE_VALID)) {
					phyaddr addr = alloc_phys_pages(1);
					if (addr) {
						temp_map_page(paddr);
						memset((void*)TEMP_PAGE, 0, PAGE_SIZE);
						temp_map_page(page_table);
						((phyaddr*)TEMP_PAGE)[index] = addr | PAGE_VALID | PAGE_WRITABLE | PAGE_USER;
						page_table = addr;
					} else {
						return false;
					}
				}
			} else {
				((phyaddr*)TEMP_PAGE)[index] = (paddr & ~PAGE_OFFSET_BITS) | flags;
				asm("invlpg (,%0,)"::"a"(vaddr));
			}
		}
		vaddr += PAGE_SIZE;
		paddr += PAGE_SIZE;
	}
	return true;
}

phyaddr get_page_info(phyaddr page_dir, void *vaddr) {
	phyaddr page_table = page_dir;
	char shift;
	for (shift = PHYADDR_BITS - PAGE_TABLE_INDEX_BITS; shift >= PAGE_OFFSET_BITS; shift -= PAGE_TABLE_INDEX_BITS) {
		unsigned int index = ((size_t)vaddr >> shift) & PAGE_TABLE_INDEX_MASK;
		temp_map_page(page_table);
		if (shift > PAGE_OFFSET_BITS) {
			page_table = ((phyaddr*)TEMP_PAGE)[index];
			if (!(page_table & PAGE_VALID)) {
				return 0;
			}
		} else {
			return ((phyaddr*)TEMP_PAGE)[index];
		}
	}
}

size_t get_free_memory_size() {
	return free_page_count << PAGE_OFFSET_BITS;
}

phyaddr alloc_phys_pages(size_t count) {
	return 0;
}

void free_phys_pages(phyaddr base, size_t count) {
	
} 

В избранное