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

Пишем свою операционную систему. Многозадачность


В этом выпуске мы рассмотрим теорию многозадачности и подготовим наш код к её поддержке. До конца в этом выпуске мы многозадачность не сделаем, потому что это достаточно сложная тема.

Теория

Многозадачность - это способность ОС выполнять несколько программ параллельно. В идеальной ситуации каждое приложение выполняется на отдельном ядре процессора, независимо и полностью параллельно. Однако, это идеальная ситуация и в реальности как правило недостижимая, поэтому сразу много процессов вынуждены делить один процессор. Иллюзия параллельной работы достигается засчёт быстрого переключения контекстов задач. То есть, допустим, в течении 1 миллисекунды процессор выполняет код одной программы, затем её работа прерывается (например, по прерыванию таймера), состояние (регистры процессора, стек) сохраняется в системную область данных, затем оттуда извлекается состояние другой задачи и работа возобновляется, но выполняется уже другой процесс. Когда и его квант времени истечёт, ядро точно так же заберёт у него управление, сохранит его состояние и восстановит состояние первой задачи (если больше процессов нет, иначе управление перейдёт к третьему процессу).

Есть два вида реализации многозадачности: кооперативная и вытесняющая.

При кооперативной многозадачности требуется поддержка приложения - оно само решает, когда прерывать своё выполнение и запустить следующую задачу. То есть, приложение выполняет некоторую работу, которую считает достаточной для одного кванта времени, а затем обращается к ОС: "я закончила, можешь передавать управление следующей задаче". Очевидно, что данный способ может быть применим лишь для небольших специфических систем (где все компоненты написаны и отлажены одной группой разработчиков и приложения считаются доверенными), но никак не для массовых очень сложных программных комплексов. Во-первых, при разработки программ под такую ОС потребуется учитывать все особенности её многозадачности, чтобы она по-прежнему работала эффективно, это требует учёта множества факторов и определённых знаний от программиста. Во-вторых, сбой в любом приложении повлечёт за собой сильное падение производительности или вовсе полное зависание системы. Поэтому такой алгоритм сейчас практически нигде в массовом порядке не применяется и мы тоже не будем его реализовывать.

Вытесняющая многозадачность используется во всех современных многозадачных десктопных и серверных операционных системах. Более того, нет никаких причин, чтобы не использовать её везде, где вообще нужна полноценная многозадачность и, как правило, так и делают. Её суть в том, что приложение не влияет на переключение задач, оно просто непрерывно выполняется, а ОС сама решает, когда отобрать у него управление (обычно это делается по прерыванию таймера). С одной стороны разработка приложений под такую систему становится очень простой (надо просто писать обычное приложение, не думая о том в какой момент будет переключение), с другой ОС получается очень надёжной (её весьма проблематично подвесить или затормозить), потому что её стабильность зависит лишь от ядра, а не от ошибок в приложениях. Мы будем реализовывать именно этот подход.

При написании функций многозадачности следует уделить особое внимание такому компоненту, как планировщик задач. Современные ОС переключают задачи не просто по кругу, а с учётом множества параметров - загруженности процессора, приоритетов, состояния приложения и т. д. В результате этого какие-то задачи получают больше квантов времени, а какие-то меньше. Такая "несправедливость" вполне оправдана: допустим, запущен процесс текстового редактора и одновременно с ним процесс кодирования видео. Первый большую часть времени проводит в ожидании нажатий клавиш, второй занят непрерывными вычислениями. Если делить кванты времени поровну, то половина времени процессора будет расходоваться впустую - на цикл проверки наличия новых событий в текстовом редакторе. Грамотная ОС же не будет давать кванты времени текстовому редактору, пока пользователь не начал с ним работать (ну или не произошло какое-то другое событие, на которое программа должна отреагировать). В таком случае практически все кванты времени будут использованы с пользой - большая часть на кодирование видео и немного на обработку событий редактором. Это лишь один из критериев планировки выполнения процессов - состояние процесса (процессы в состоянии "ожидание события" пропускаются при переключении). Вторым немаловажным параметром является приоритет процесса. Разные задачи требуют различной скорости реакции. Например, человек не в состоянии заметить задержки изображения вплоть до 20 миллисекунд, а задержка звука даже в 1-2 миллисекунды уже будет неприятна. Следовательно, процесс вывода звука должен иметь гораздо больший приоритет, чем процесс воспроизведения видео. При этом обработка звука значительно более простая задача, чем обработка видео, поэтому несмотря на высокий приоритет первый процесс не понизит отзывчивость системы, потому что большую часть времени будет спать. При грамотной разработке системы наиболее приоритетные задачи имеют крайне низкую вычислительную сложность, а действительно ресурсоёмкие задачи имеют пониженный приоритет. В таком случае система будет работать быстро и с малым временем отклика.

Мы пока не будем писать полноценный планировщик, потому что это достаточно сложная задача и даже в современных ОС порой находят, что улучшить и где его оптимизировать, то есть эта задача не решена до конца. Пока ограничимся примитивным цикличным переключением задач без учёта приоритетов. Когда мы реализуем механизм межпроцессного взаимодействия, добавим пропуск спящих задач (пока у нас нет критериев, по которым задачу можно считать ожидающей события). Когда-нибудь, возможно, я и дойду до поддержки приоритетов, но пока есть задачи поважнее.

Семафоры и синхронизация

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

int variable = 10;

void thread1() {
        variable = 0;
}

void thread2() {
        if (variable != 0) {
                printf("100 / variable = %d\n", 100 / variable);
        } else {
                printf("Cannot divive by zero!\n");
        }
}

Функции thread1 и thread2 выполняются параллельно. Переменная varaible доступна обоим нитям (параллельные задачи в рамках одного адресного пространства). Допустим, первой получает управление нить thread2. Она производит сравнение variable с нулём (дальше будет деление на это значение, а на ноль делить нельзя). Сравнение проходит успешно (10 != 0) и функция заходит в блок if. Но тут, прямо в этот момент, ОС отбирает у thread2 управление (откуда ей знать, что нить была занята чем-то важным? а если система многоядерная и задачи выполняются вообще физически параллельно?) и отдаёт его thread1, которая обнуляет переменную. thread1 закончила свою работу и управление вернулось к thread2. Она думает, что variable не равна нулю (только что же проверили) и выполняет деление. В итоге происходит ошибка и программа завершается некорректно, хотя защита от деления на ноль в функции была! Переключение задач невозможно, либо очень предсказать, поэтому получается, что нельзя надеяться ни на какие проверки. Как же тогда работать с общими данными?

Для удобной работы были придуманы семафоры. Их суть в том, что приложение перед работой с общим ресурсом ставит флаг "я работаю с variable, никому не трогать!", выполняет свои действия, а потом убирает флаг ("я закончил"). В таком случае программист может быть уверен, что никто кроме его программы в данный момент времени не работает с общим ресурсом. Другие задачи, которым нужен доступ к нему будут ждать, а те, которым он не нужен, будут работать как ни в чём не бывало (то есть многозадачность не теряется).

Наш код в потокобезопасном варианте выглядит так:

int variable = 10;
Mutex mutex;

void thread1() {
        get_mutex(&mutex);
        variable = 0;
        release_mutex(&mutex);
}

void thread2() {
        get_mutex(&mutex);
        if (variable != 0) {
                printf("100 / variable = %d\n", 100 / variable);
        } else {
                printf("Cannot divive by zero!\n");
        }
        release_mutex(&mutex);
}

get_mutex присваивает переменной mutex значение true, если оно было false. Если оно было true, то ждёт, пока станет false (как сделать это так быстро, чтобы никто другой не успел вмешаться, обсудим чуть позже). release_mutex это простое обнуление переменной. Теперь в каком бы порядке планировщик не переключал задачи thread1 и thread2 каждая из них отработает именно так, как задумывал автор и ошибок не произойдёт.

Злоупотреблять семафорами тоже не стоит, потому что их неоправданное применение приведёт к падению производительности и отзывчивости системы.

Дополнение стандартной библиотеки

После теории можно приступить к началу практики. В этом выпуске мы добавим синхронизацию везде, где она нужна. Но сначала надо написать функции работы с семафорами. Их мы разместим в stdlib.c.

Модификации заголовочного файла stdlib.h:

#ifndef STDLIB_H
#define STDLIB_H

typedef enum {
	false = 0,
	true = 1
} bool;

#define NULL ((void*)0)

typedef unsigned char uint8;
typedef signed char int8;

typedef unsigned short uint16;
typedef signed short int16;

typedef unsigned int uint32;
typedef signed int int32;

typedef unsigned long long uint64;
typedef signed long long int64;

#ifdef __x86_64__
	typedef uint64 size_t;
#else
	typedef uint32 size_t;
#endif

typedef bool Mutex;

#define min(a, b) (((a) > (b)) ? (b) : (a))
#define max(a, b) (((a) > (b)) ? (a) : (b))

#define outportb(port, value) asm("outb %b0, %w1"::"a"(value),"d"(port));
#define outportw(port, value) asm("outw %w0, %w1"::"a"(value),"d"(port));
#define outportl(port, value) asm("outl %0, %w1"::"a"(value),"d"(port));

#define inportb(port, out_value) asm("inb %w1, %b0":"=a"(out_value):"d"(port));
#define inportw(port, out_value) asm("inw %w1, %w0":"=a"(out_value):"d"(port));
#define inportl(port, out_value) asm("inl %w1, %0":"=a"(out_value):"d"(port));

void memset(void *mem, char value, size_t count);
void memset_word(void *mem, uint16 value, size_t count);
void memcpy(void *dest, void *src, size_t count);
int memcmp(void *mem1, void *mem2, size_t count);
void *memchr(void *mem, char value, size_t count);

size_t strlen(char *str);
void strcpy(char *dest, char *src);
void strncpy(char *dest, char*src, size_t max_count);
int strcmp(char *str1, char *str2);
char *strchr(char *str, char value);

bool mutex_get(Mutex *mutex, bool wait);
void mutex_release(Mutex *mutex);

#endif 

Тип Mutex объявлен не только для читабельности, но и для возможности в будущем расширения структуры семафора (например, добавить дескриптор нити, которая его захватила и т. д.), если это потребуется.

Использовать только средства языка Си не получится, иначе мы столкнёмся с той же проблемой, что и в примере выше - нельзя быть уверенным, что после проверки семафора на захваченность, это состояние не изменится. Но нам на помощь вновь приходят ассемблерные вставки.

bool mutex_get(Mutex
*mutex, bool wait) {
	bool old_value = true;
	do {
		asm("xchg (,%1,), %0":"=a"(old_value):"b"(mutex),"a"(old_value));
	} while (old_value && wait);
	return !old_value;
}

void mutex_release(Mutex *mutex) {
	*mutex = false;
}

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

Потокобезопасный менеджер памяти 

Ну а теперь можно наконец внести поддержку семафоров в менеджер памяти.

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_PRESENT		(1 << 0)
#define PAGE_WRITABLE		(1 << 1)
#define PAGE_USER		(1 << 2)
#define PAGE_WRITE_THROUGH	(1 << 3)
#define PAGE_CACHE_DISABLED	(1 << 4)
#define PAGE_ACCESSED		(1 << 5)

#define PAGE_MODIFIED		(1 << 6)
#define PAGE_GLOBAL		(1 << 8)

void KERNEL_BASE();
void KERNEL_CODE_BASE();
void KERNEL_DATA_BASE();
void KERNEL_BSS_BASE();
void KERNEL_END();

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

#define USER_MEMORY_START ((void*)0)
#define USER_MEMORY_END ((void*)0x7FFFFFFF)
#define KERNEL_MEMORY_START ((void*)0x80000000)
#define KERNEL_MEMORY_END ((void*)(KERNEL_BASE - 1))

typedef size_t phyaddr;

typedef enum {
	VMB_RESERVED,
	VMB_MEMORY,
	VMB_IO_MEMORY
} VirtMemoryBlockType;

typedef struct {
	VirtMemoryBlockType type;
	void *base;
	size_t length;
} VirtMemoryBlock;

typedef struct {
	phyaddr page_dir;
	void *start;
	void *end;
	size_t block_table_size;
	size_t block_count;
	VirtMemoryBlock *blocks;
	Mutex mutex;
} AddressSpace;

phyaddr kernel_page_dir;
size_t memory_size;
AddressSpace kernel_address_space;

void init_memory_manager(void *memory_map);

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

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);

void *alloc_virt_pages(AddressSpace *address_space, void *vaddr, phyaddr paddr, size_t count, unsigned int flags);
bool free_virt_pages(AddressSpace *address_space, void *vaddr, unsigned int flags);

#endif 

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;

typedef struct {
	phyaddr next;
	phyaddr prev;
	size_t size;
} PhysMemoryBlock;

size_t free_page_count = 0;
phyaddr free_phys_memory_pointer = -1;
Mutex phys_memory_mutex;

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;
		}
	}
	map_pages(kernel_page_dir, KERNEL_CODE_BASE, get_page_info(kernel_page_dir, KERNEL_CODE_BASE),
		((size_t)KERNEL_DATA_BASE - (size_t)KERNEL_CODE_BASE) >> PAGE_OFFSET_BITS, PAGE_PRESENT | PAGE_GLOBAL);
	map_pages(kernel_page_dir, KERNEL_DATA_BASE, get_page_info(kernel_page_dir, KERNEL_DATA_BASE), 
		((size_t)KERNEL_END - (size_t)KERNEL_DATA_BASE) >> PAGE_OFFSET_BITS, PAGE_PRESENT | PAGE_WRITABLE | PAGE_GLOBAL);
	map_pages(kernel_page_dir, KERNEL_PAGE_TABLE, get_page_info(kernel_page_dir, KERNEL_PAGE_TABLE), 1, PAGE_PRESENT | PAGE_WRITABLE | PAGE_GLOBAL);
	kernel_address_space.page_dir = kernel_page_dir;
	kernel_address_space.start = KERNEL_MEMORY_START;
	kernel_address_space.end = KERNEL_MEMORY_END;
	kernel_address_space.block_table_size = PAGE_SIZE / sizeof(VirtMemoryBlock);
	kernel_address_space.blocks = KERNEL_MEMORY_START;
	kernel_address_space.block_count = 1;
	map_pages(kernel_page_dir, kernel_address_space.blocks, alloc_phys_pages(1), 1, PAGE_PRESENT | PAGE_WRITABLE);
	kernel_address_space.blocks[0].type = VMB_RESERVED;
	kernel_address_space.blocks[0].base = kernel_address_space.blocks;
	kernel_address_space.blocks[0].length = PAGE_SIZE;
}

/* Physical memory manager */

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

phyaddr alloc_phys_pages(size_t count) {
	if (free_page_count < count) return -1;
	phyaddr result = -1;
	mutex_get(&phys_memory_mutex, true);
	if (free_phys_memory_pointer != -1) {
		phyaddr cur_block = free_phys_memory_pointer;
		do {
			temp_map_page(cur_block);
			if (((volatile PhysMemoryBlock*)TEMP_PAGE)->size == count) {
				phyaddr next = ((volatile PhysMemoryBlock*)TEMP_PAGE)->next;
				phyaddr prev = ((volatile PhysMemoryBlock*)TEMP_PAGE)->prev;
				temp_map_page(next);
				((volatile PhysMemoryBlock*)TEMP_PAGE)->prev = prev;
				temp_map_page(prev);
				((volatile PhysMemoryBlock*)TEMP_PAGE)->next = next;
				if (cur_block == free_phys_memory_pointer) {
					free_phys_memory_pointer = next;
					if (cur_block == free_phys_memory_pointer) {
						free_phys_memory_pointer = -1;
					}
				}
				result = cur_block;
				break;
			} else if (((volatile PhysMemoryBlock*)TEMP_PAGE)->size > count) {
				((volatile PhysMemoryBlock*)TEMP_PAGE)->size -= count;
				result = cur_block + (((volatile PhysMemoryBlock*)TEMP_PAGE)->size << PAGE_OFFSET_BITS);
				break;
			}
			cur_block = ((volatile PhysMemoryBlock*)TEMP_PAGE)->next;
		} while (cur_block != free_phys_memory_pointer);
		if (result != -1) {
			free_page_count -= count;
		} 
	}
	mutex_release(&phys_memory_mutex);
	return result;
}

void free_phys_pages(phyaddr base, size_t count) {
	mutex_get(&phys_memory_mutex, true);
	if (free_phys_memory_pointer == -1) {
		temp_map_page(base);
		((volatile PhysMemoryBlock*)TEMP_PAGE)->next = base;
		((volatile PhysMemoryBlock*)TEMP_PAGE)->prev = base;
		((volatile PhysMemoryBlock*)TEMP_PAGE)->size = count;
		free_phys_memory_pointer = base;
	} else {
		phyaddr cur_block = free_phys_memory_pointer;
		do {
			temp_map_page(cur_block);
			if (cur_block + (((volatile PhysMemoryBlock*)TEMP_PAGE)->size << PAGE_OFFSET_BITS) == base) {
				((volatile PhysMemoryBlock*)TEMP_PAGE)->size += count;
				if (((volatile PhysMemoryBlock*)TEMP_PAGE)->next == base + (count << PAGE_OFFSET_BITS)) {
					phyaddr next1 = ((volatile PhysMemoryBlock*)TEMP_PAGE)->next;
					temp_map_page(next1);
					phyaddr next2 = ((volatile PhysMemoryBlock*)TEMP_PAGE)->next;
					size_t new_count = ((volatile PhysMemoryBlock*)TEMP_PAGE)->size;
					temp_map_page(next2);
					((volatile PhysMemoryBlock*)TEMP_PAGE)->prev = cur_block;
					temp_map_page(cur_block);
					((volatile PhysMemoryBlock*)TEMP_PAGE)->next = next2;
					((volatile PhysMemoryBlock*)TEMP_PAGE)->size += new_count;
				}
				break;
			} else if (base + (count << PAGE_OFFSET_BITS) == cur_block) {
				size_t old_count = ((volatile PhysMemoryBlock*)TEMP_PAGE)->size;
				phyaddr next = ((volatile PhysMemoryBlock*)TEMP_PAGE)->next;
				phyaddr prev = ((volatile PhysMemoryBlock*)TEMP_PAGE)->prev;
				temp_map_page(next);
				((volatile PhysMemoryBlock*)TEMP_PAGE)->prev = base;
				temp_map_page(prev);
				((volatile PhysMemoryBlock*)TEMP_PAGE)->next = base;
				temp_map_page(base);
				((volatile PhysMemoryBlock*)TEMP_PAGE)->next = next;
				((volatile PhysMemoryBlock*)TEMP_PAGE)->prev = prev;
				((volatile PhysMemoryBlock*)TEMP_PAGE)->size = count + old_count;
				break;
			} else if ((cur_block > base) || (((volatile PhysMemoryBlock*)TEMP_PAGE)->next == free_phys_memory_pointer)) {
				phyaddr prev = ((volatile PhysMemoryBlock*)TEMP_PAGE)->next;
				((volatile PhysMemoryBlock*)TEMP_PAGE)->prev = base;
				temp_map_page(prev);
				((volatile PhysMemoryBlock*)TEMP_PAGE)->next = base;
				temp_map_page(base);
				((volatile PhysMemoryBlock*)TEMP_PAGE)->next = cur_block;
				((volatile PhysMemoryBlock*)TEMP_PAGE)->prev = prev;
				((volatile PhysMemoryBlock*)TEMP_PAGE)->size = count;
				break;
			}
			cur_block = ((volatile PhysMemoryBlock*)TEMP_PAGE)->next;
		} while (cur_block != free_phys_memory_pointer);
		if (base < free_phys_memory_pointer) {
			free_phys_memory_pointer = base;
		}
		
	}
	free_page_count += count;
	mutex_release(&phys_memory_mutex);
}

/* Lowlevel virtual memory manager */

 ...

/* Highlevel virtual memory manager */

static inline bool is_blocks_overlapped(void *base1, size_t size1, void *base2, size_t size2) {
	return ((base1 >= base2) && (base1 < base2 + size2)) || ((base2 >= base1) && (base2 < base1 + size1));
}

void *alloc_virt_pages(AddressSpace *address_space, void *vaddr, phyaddr paddr, size_t count, unsigned int flags) {
	VirtMemoryBlockType type = VMB_IO_MEMORY;
	size_t i;
	mutex_get(&(address_space->mutex), true);
	if (vaddr == NULL) {
		vaddr = address_space->end - (count << PAGE_OFFSET_BITS) + 1;
		for (i = 0; i < address_space->block_count; i++) {
			if (is_blocks_overlapped(address_space->blocks[i].base, address_space->blocks[i].length, vaddr, count)) {
				vaddr = address_space->blocks[i].base - (count << PAGE_OFFSET_BITS);
			} else {
				break;
			}
		}
	} else {
		if ((vaddr >= address_space->start) && (vaddr + (count << PAGE_OFFSET_BITS) < address_space->end)) {
			for (i = 0; i < address_space->block_count; i++) {
				if (is_blocks_overlapped(address_space->blocks[i].base, address_space->blocks[i].length, vaddr, count << PAGE_OFFSET_BITS)) {
					vaddr = NULL;
					break;
				} else if (address_space->blocks[i].base < vaddr) {
					break;
				}
			}
		} else {
			vaddr = NULL;
		}
	}
	if (vaddr != NULL) {
		if (paddr == -1) {
			paddr = alloc_phys_pages(count);
			type = VMB_MEMORY;
		}
		if (paddr != -1) {
			if (map_pages(address_space->page_dir, vaddr, paddr, count, flags)) {
				if (address_space->block_count == address_space->block_table_size) {
					size_t new_size = address_space->block_table_size * sizeof(VirtMemoryBlock);
					new_size = (new_size + PAGE_OFFSET_MASK) & ~PAGE_OFFSET_MASK;
					new_size += PAGE_SIZE;
					new_size = new_size >> PAGE_OFFSET_BITS;
					if (&kernel_address_space != address_space) {
						VirtMemoryBlock *new_table = alloc_virt_pages(&kernel_address_space, NULL, -1, new_size,	
							PAGE_PRESENT | PAGE_WRITABLE);
						if (new_table) {
							memcpy(new_table, address_space->blocks, address_space->block_table_size * sizeof(VirtMemoryBlock));
							free_virt_pages(&kernel_address_space, address_space->blocks, 0);
							address_space->blocks = new_table;
						} else {
							goto fail;
						}
					} else {
						phyaddr new_page = alloc_phys_pages(1);
						if (new_page == -1) {
							goto fail;
						} else {
							VirtMemoryBlock *main_block = &(address_space->blocks[address_space->block_count - 1]);
							if (map_pages(address_space->page_dir, main_block->base + main_block->length, new_page, 1,
									PAGE_PRESENT | PAGE_WRITABLE)) {
								main_block->length += PAGE_SIZE;
							} else {
								free_phys_pages(new_page, 1);
							}
						}
					}
					address_space->block_table_size = (new_size << PAGE_OFFSET_BITS) / sizeof(VirtMemoryBlock);
				}
				memcpy(address_space->blocks + i + 1, address_space->blocks + i,
					(address_space->block_count - i) * sizeof(VirtMemoryBlock));
				address_space->block_count++;
				address_space->blocks[i].type = type;
				address_space->blocks[i].base = vaddr;
				address_space->blocks[i].length = count << PAGE_OFFSET_BITS;
			} else {
fail:
				map_pages(address_space->page_dir, vaddr, 0, count, 0);
				free_phys_pages(paddr, count);
				vaddr = NULL;
			}
		}
	}
	mutex_release(&(address_space->mutex));
	return vaddr;
}

bool free_virt_pages(AddressSpace *address_space, void *vaddr, unsigned int flags) {
	size_t i;
	mutex_get(&(address_space->mutex), true);
	for (i = 0; i < address_space->block_count; i++) {
		if ((address_space->blocks[i].base <= vaddr) && (address_space->blocks[i].base + address_space->blocks[i].length > vaddr)) {
			break;
		}
	}
	if (i < address_space->block_count) {
		if (address_space->blocks[i].type = VMB_MEMORY) {
			free_phys_pages(get_page_info(address_space->page_dir, vaddr) & ~PAGE_OFFSET_MASK, address_space->blocks[i].length >> PAGE_OFFSET_BITS);
		}
		address_space->block_count--;
		memcpy(address_space->blocks + i, address_space->blocks + i + 1, (address_space->block_count - i) * sizeof(VirtMemoryBlock));
		mutex_release(&(address_space->mutex));
		return true;
	} else {
		mutex_release(&(address_space->mutex));
		return false;
	}
}

Теперь, менеджер памяти сможет корректно обрабатывать ситуацию, когда к нему обращаются одновременно сразу несколько нитей.

Вот и всё на сегодня! В следующем выпуске можно будет начать реализацию переключения задач.

Кстати, в функции map_pages содержится ошибка (ну не совсем ошибка, а отсутствие volatile в нужном месте или ещё что) из-за которой при уровне оптимизации -O2 она не способна корректно примонтировать страницу, если при этом приходится создавать новую таблицу страниц. Если вы сможете её найти, я буду вам благодарен :-) 


В избранное