В предыдущей статье мы перешли в режим страничной адресации, разметив только ту память, что необходима для нормальной работы ядра. Для того чтобы грамотно управлять памятью в дальнейшем нам необходимо знать сколько физической памяти установлено на компьютере, и как изначально она распределяется.
В реальном режиме на этот вопрос отвечает функция BIOS INT 15h/E820h, однако для того чтобы воспользоваться ей, нам необходимо вернуться в реальный режим, а это, мягко говоря для нас неприемлемо.
К счастью на помощь нам приходит загрузчик GRUB2, который уже проделал за нас эту работу, сформировал карту памяти и может передать её нам через Multiboot-заголовок.
1. Заголовок Multiboot
В прошлый раз, когда мы рассматривали спецификацию мультизагрузки, я ссылался на официальную документацию проекта GNU. В этот раз мы подробнее рассмотрим формат заголовка multiboot
Листинг 46. Заголовок Multiboot (multiboot.h)
/*------------------------------------------------
//
// Multiboot header
//
//----------------------------------------------*/
#ifndef MULTIBOOT_H
#define MULTIBOOT_H
#include "common.h"
#define MBOOT_FLAG_MEM 0x001
#define MBOOT_FLAG_DEVICE 0x002
#define MBOOT_FLAG_CMDLINE 0x004
#define MBOOT_FLAG_MODS 0x008
#define MBOOT_FLAG_AOUT 0x010
#define MBOOT_FLAG_ELF 0x020
#define MBOOT_FLAG_MMAP 0x040
#define MBOOT_FLAG_CONFIG 0x080
#define MBOOT_FLAG_LOADER 0x100
#define MBOOT_FLAG_APM 0x200
#define MBOOT_FLAG_VBE 0x400
//
// Multiboot header
//
//----------------------------------------------*/
#ifndef MULTIBOOT_H
#define MULTIBOOT_H
#include "common.h"
#define MBOOT_FLAG_MEM 0x001
#define MBOOT_FLAG_DEVICE 0x002
#define MBOOT_FLAG_CMDLINE 0x004
#define MBOOT_FLAG_MODS 0x008
#define MBOOT_FLAG_AOUT 0x010
#define MBOOT_FLAG_ELF 0x020
#define MBOOT_FLAG_MMAP 0x040
#define MBOOT_FLAG_CONFIG 0x080
#define MBOOT_FLAG_LOADER 0x100
#define MBOOT_FLAG_APM 0x200
#define MBOOT_FLAG_VBE 0x400
/*------------------------------------------------
* Multiboot-заголовок
*----------------------------------------------*/
struct multiboot_header
{
u32int flags; /* Флаги заголовка */
u32int mem_lower; /* Размер базовой памяти */
u32int mem_upper; /* Размер памяти выше 1 Мб */
u32int boot_device;
u32int cmdline;
u32int mods_count;
u32int mods_addr;
u32int num;
u32int size;
u32int addr;
u32int shndx;
u32int mmap_length; /* Размер карты памяти */
u32int mmap_addr; /* Адрес начала карты памяти */
u32int drives_length;
u32int drives_addr;
u32int config_table;
u32int boot_loader_name;
u32int apm_table;
u32int vbe_control_info;
u32int vbe_mode_info;
u32int vbe_mode;
u32int vbe_interface_seg;
u32int vbe_interface_off;
u32int vbe_interface_len;
}__attribute__((packed));
typedef struct multiboot_header multiboot_header_t;
/*------------------------------------------------
* Структура описания блока памяти в карте памяти
*----------------------------------------------*/
struct memory_map_entry
{
u32int size; /* Размер данной структуры */
u64int addr; /* Адрес начала блока */
u64int len; /* Размер блока в байтах */
u32int type; /* Тип блока */
}__attribute__((packed));
typedef struct memory_map_entry memory_map_entry_t;
#endif
{
u32int flags; /* Флаги заголовка */
u32int mem_lower; /* Размер базовой памяти */
u32int mem_upper; /* Размер памяти выше 1 Мб */
u32int boot_device;
u32int cmdline;
u32int mods_count;
u32int mods_addr;
u32int num;
u32int size;
u32int addr;
u32int shndx;
u32int mmap_length; /* Размер карты памяти */
u32int mmap_addr; /* Адрес начала карты памяти */
u32int drives_length;
u32int drives_addr;
u32int config_table;
u32int boot_loader_name;
u32int apm_table;
u32int vbe_control_info;
u32int vbe_mode_info;
u32int vbe_mode;
u32int vbe_interface_seg;
u32int vbe_interface_off;
u32int vbe_interface_len;
}__attribute__((packed));
typedef struct multiboot_header multiboot_header_t;
/*------------------------------------------------
* Структура описания блока памяти в карте памяти
*----------------------------------------------*/
struct memory_map_entry
{
u32int size; /* Размер данной структуры */
u64int addr; /* Адрес начала блока */
u64int len; /* Размер блока в байтах */
u32int type; /* Тип блока */
}__attribute__((packed));
typedef struct memory_map_entry memory_map_entry_t;
#endif
Как видно из листинга, в заголовке мультизагрузки передается масса различной информации, из которой нас пока что интересует карта памяти, формируемая GRUB2. Доступ к ней дают поля multiboot-заголовка mmap_addr - адрес начала карты памяти и mmap_length - размер карты памяти.
Сама карта памяти - это структура состоящая из записей вида memory_map_entry_t. В каждой записи (см. таблицу 19) хранится информация о блоке памяти. Кроме адреса начала блока и его размера там имеется поле type определяющее возможность использование данного блока памяти.
Таблица 19. Формат записи в карте памяти
Смещение | Поле | Назначение |
-4 | size | Размер данной структуры (20 байт) |
0 | addr | Адрес начала блока памяти |
8 | len | Размер блока памяти |
16 | type | Тип блока памяти (type = 1 - блок доступен для использования) |
Надо сказать, что спецификация Multiboot дает достаточно расплывчатую информацию о типах блоков памяти, более точную информацию можно узнать в спецификации ACPI
Таблица 20. Типы блоков памяти
Тип | Назначение |
1 | Доступная оперативная память |
2 | Зарезервированная область памяти, использоваться программами не должна |
3 | Область памяти, занятая таблицами ACPI. Может использоваться ОС после того как информация в ней станет ненужной. |
4 | Зарезервированная область памяти, использоваться программами не должна. В отличие от памяти типа 2, должна сохранятся при переходе в спящий режим и восстанавливаться при выходе из него |
5 | Область памяти содержащая ошибки. Использоваться не должна. |
другое | Зарезервировано для будущего использования. ОС использоваться не должна. |
Таким образом, чтобы не поломать систему при выделении памяти нам необходимо ограничится использованием блоков памяти с type = 1. А для этого необходимо считать и проанализировать карту памяти.
2. Передача ядру заголовка Multiboot и указателя стека.
При передаче управления ядру GRUB2 помещает адрес структуры заголовка Multiboot в регистр базы EBX, то есть по сути он дает нам указатель на эту структуру. Данный указатель необходимо передать в функцию main(), а для этого модифицируем её заголовок таким образом
Листинг 47. Изменение стартовой функции ядра (main.c)
int main(multiboot_header_t* mboot, u32int initial_esp)
{
.
.
.
return 0;
}
Добавляем в заголовок функции main() два параметра: mboot - указатель на multiboot-заголовок и init_esp - начальное значение указателя стека ядра, которое нам в последствии пригодится. Теперь при вызове функции main() из ассемблерной "обертки" ядра необходимо передать эти параметры функции main() согласно декларации C о вызове подпрограмм - в обратном порядке, начиная с последнего.
Листинг 48. Передача параметров ядру (init.s)
.
.
.
init:
cli /* Запрещаем прерывания */
push %esp /* Проталкиваем в стек указатель стека */
push %ebx /* Проталкиваем в стек указатель на */
cli /* Запрещаем прерывания */
push %esp /* Проталкиваем в стек указатель стека */
push %ebx /* Проталкиваем в стек указатель на */
/* заголовок multiboot */
call main /* Передаем управление ядру */
hlt
loop:
call main /* Передаем управление ядру */
hlt
loop:
jmp loop
Вуаля! Теперь из ядра мы имеем простой доступ ко всем параметрам multiboot-заголовка через поля структуры mboot.
Незамедлительно воспользуемся этим и добавим в менеджер памяти ещё одну функцию, считывающую и анализирующую карту памяти.
Листинг 49. Считывание карты памяти (memory.c)
memory_map_entry_t* mentry = 0;
u64int phys_memory_size = 0;
/*----------------------------------------------------------
// Чтение и анализ карты памяти
//--------------------------------------------------------*/
void check_memory_map(memory_map_entry_t* mmap_addr, u32int length)
{
int i = 0;
/* Вычисляем число записей в карте памяти */
int n = length / sizeof(memory_map_entry_t);
/* Устанавливаем значение указателя на структуру карты */
/* памяти в нашем ядре */
mentry = mmap_addr;
/* Читаем информацию из карты памяти и выводим её на экран */
// Чтение и анализ карты памяти
//--------------------------------------------------------*/
void check_memory_map(memory_map_entry_t* mmap_addr, u32int length)
{
int i = 0;
/* Вычисляем число записей в карте памяти */
int n = length / sizeof(memory_map_entry_t);
/* Устанавливаем значение указателя на структуру карты */
/* памяти в нашем ядре */
mentry = mmap_addr;
/* Читаем информацию из карты памяти и выводим её на экран */
/* с одновременным подсчетом объема физической памяти */
print_text("Physical memory map\n");
for (i = 0; i < n; i++)
{
/* Проверяем тип блока памяти */
if ((mentry + i)->type == 1)
print_text("Available: |");
else
print_text("Reserved: |");
print_text(" addr: ");
print_hex_value((mentry + i)->addr);
print_text(" length: ");
print_hex_value((mentry + i)->len);
print_text("\n");
/* Подсчитываем объем установленной памяти */
phys_memory_size += (mentry + i)->len;
}
/* Выводим на экран информацию об установленной памяти */
print_text("Physical memory map\n");
for (i = 0; i < n; i++)
{
/* Проверяем тип блока памяти */
if ((mentry + i)->type == 1)
print_text("Available: |");
else
print_text("Reserved: |");
print_text(" addr: ");
print_hex_value((mentry + i)->addr);
print_text(" length: ");
print_hex_value((mentry + i)->len);
print_text("\n");
/* Подсчитываем объем установленной памяти */
phys_memory_size += (mentry + i)->len;
}
/* Выводим на экран информацию об установленной памяти */
print_text("Installed memory size: ");
print_dec_value(phys_memory_size / 1024);
print_text(" KB\n");
}
print_dec_value(phys_memory_size / 1024);
print_text(" KB\n");
}
Проверим что же у нас получилось
Виртуальная машина запущена с настройкой объема памяти в 64 Мб. Как видно из скриншота эти 64 Мб видны нашему ядру вместе с назначением блоков памяти. Проведем ещё один эксперимент - дадим нашей ВМ 8 Гб оперативной памяти. Для этого придется модифицировать функции вывода 16-ричных чисел на экран, для отображения адресов выше 4 Гб. Имеем такую картину
Вот так! В 32-разрядной системе мы видим 8 Гб физической памяти, причем область за пределами 4 Гб помечена как доступная. Однако без активации режима PAE в процессоре обратится к этой памяти мы не можем, хотя BIOS видит что она установлена на материнской плате.
Заключение
Итак, мы модифицирования наше ядро таким образом, что нам доступна информация о распределении физической памяти на конкретном компьютере где загружена наша ОС. Это позволит нам в дальнейшем грамотно распределять память для нужд ядра и прикладных программ.
Комментариев нет:
Отправить комментарий