суббота, 17 августа 2013 г.

PhantomEx: Карта распределения физической памяти, или снова Multiboot

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

В реальном режиме на этот вопрос отвечает функция 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-заголовок
 *----------------------------------------------*/
 
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

Как видно из листинга, в заголовке мультизагрузки передается масса различной информации, из которой нас пока что интересует карта памяти, формируемая GRUB2. Доступ к ней дают поля multiboot-заголовка mmap_addr - адрес начала карты памяти и mmap_length - размер карты памяти.

Сама карта памяти - это структура состоящая из записей вида memory_map_entry_t. В каждой записи (см. таблицу 19) хранится информация о блоке памяти.  Кроме адреса начала блока и его размера там имеется поле type определяющее возможность использование данного блока памяти.

Таблица 19. Формат записи в карте памяти

СмещениеПолеНазначение
-4sizeРазмер данной структуры (20 байт)
0addrАдрес начала блока памяти
8lenРазмер блока памяти
16typeТип блока памяти (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  /* Проталкиваем в стек указатель на */
                      /* заголовок multiboot */
       
        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;

    /* Читаем информацию из карты памяти и выводим её на экран */
    /* с одновременным подсчетом объема физической памяти */
    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");
}

Проверим что же у нас получилось
Виртуальная машина запущена с настройкой объема памяти в 64 Мб. Как видно из скриншота эти 64 Мб видны нашему ядру вместе с назначением блоков памяти. Проведем ещё один эксперимент - дадим нашей ВМ 8 Гб оперативной памяти. Для этого придется модифицировать функции вывода 16-ричных чисел на экран, для отображения адресов выше 4 Гб. Имеем такую картину


Вот так! В 32-разрядной системе мы видим 8 Гб физической памяти, причем область за пределами 4 Гб помечена как доступная. Однако без активации режима PAE в процессоре обратится к этой памяти мы не можем, хотя BIOS видит что она установлена на материнской плате.

Заключение


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