Эксперимент с настройкой страничного режима в ОС

Сегодня провел эксперимент по включению страничного режима (paging-а) в ядре тестовой операционной системы. Ниже результаты.

Некоторое время назад я переводил фрагмент 4 главы “PAGING” руководства “Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3 (3A & 3B):System Programming Guide”

Я задержался с теоретическими изысканиями, перечитал гору разных мануалов, форумов, статей. Затеял сам писать статью про менеджмент виртуальной памяти. В ней и перечислены многие ссылки. Кроме того пересмотрял ряд исходников операционных систем и чем дальше, тем становилось грустнее. Слишком тема виртуальной памяти обширна. Потом наконец наткнулся на замечательную статью Пишем свою операционную систему. Страничная адресация. Огромный респект автору за проделанную работу. Статья рассеяла мои сомнения. Я в очередной раз пролистал мануал от Intel и решил провести, наконец, практический эксперимент со страничным режимом.

Страничный механизм является настолько важным и основополагающим, что, в частности, в 64-битный режим (IA-32e) процессор Intel переводится включением соответствующего страничного режима. Страничный режим управляется иерархическими структурами. У современных процессоров есть несколько модификаций страничных режимов. Самым простым страничным режимом является обычный 32-битный страничный режим, появившийся еще в i80386. В этом режиме вся память разбивается на 4K байтные страницы. Иерархические структуры введены для экономии. Основная хитрость состоит в том, что в каждый отдельный момент времени был описан лишь необходимый минимум доступных текущей выполняющейся программе страниц.

linear-address-translation-to-4kbyte-page-using-32-paging Intel illustration(Иллюстрация из руководства Intel)

Трансляция адреса в 32-битном режиме осуществляется таким образом:

  • Процессор берет адрес страницы с названием Page Directory, из регистра CR3
  • Page Directory занимает 1 страницу (4096 байт) состоит из 32-битных записей. Каждая запись контролирует доступ к 4M-байтам памяти. Соответственно, если обращение происходит к логическому адресу в пределах 0 — 0x100000, то берется 0-я запись в Page Directory, для 4-8M первая запись, 16-32 — 2-я, и т.д. Последняя запись 1023.
  • Запись в Page Directory хранит физический адрес Page Table. Page table (размером также 4096 байт) в свою очередь хранит записи контролирующие доступ к 4K-байтным страницам. Каждая запись хранит физический адрес (кратный 4096) первого байта соответствующей страницы.

У меня ядро операционной системы загружается по физическому адресу 0x10000 (После первых 64K). Сначала идет кодовый сегмент, за ним сегмент данных и стек.

os-memory-1

В начале оперативной памяти находится таблица векторов прерываний реального режима, я её не трогаю. Далее идет область данных BIOS в промежутке 0x400-0x500. Выше 0x500 и до стартового адреса ядра 0x10000 образуется свободное пространство. На самом деле оно не совсем свободно. В начале этого пространства по адресу 0x600 с помощью BIOS перед переходом в защищенный режим загружается таблица доступной памяти.

Это дает мне возможность по адресу 0x1000 разместить Page Directory. Я описываю в инклудниках константу PAGE_DIRECTORY equ 0x1000. Так как у меня в текущий момент исходники небольшие, весь мой код умещается в первый 1M памяти. По этой причине, внутри Page Directory мне нужно описать единственную запись, контролирующую доступ к первым 4M памяти процессора. Внутри этой единственной записи должна быть ссылка на физический адрес таблицы страниц для 4K байтных страниц начиная с адреса 0 до 4M.

Я эту таблицу размещаю сразу следом за Page Directory. Т.е. по адресу 0x2000. По этой таблице все линейные адреса транслируются в физические. Так как мне сейчас не нужно сложностей, адреса транслируются один к одному. Т.о. после включения страничного режима, ядро продолжает работать, как будто ничего не произошло.

page_directory_and_table

Для включения страничного режима, сначала создаются структуры Page Directory и Page Table. Потом в регистр CR3 загружается адрес Page Directory, потом в регистре CR0 выставляется 31 бит PG, включающий страничный режим.

Ниже исходники функции, написанной в итоге. Функция включает страничный механизм в процессоре. Для этого формируется страничный директорий и таблица страниц. (Подробнее см. комментарии в исходниках).

; @see http://subscribe.ru/archive/comp.soft.myosdev/201207/15185538.html
%include 'kernel/consts.inc'
;
PAGE_SIZE equ 0x1000
;
; Page directory entry flags
;
PDE_P equ 1b
PDE_W equ 10b
PDE_U equ 100b
;
; Page table entry flags
;
PTE_P equ 1b
PTE_W equ 10b
PTE_U equ 100b
;
; first parameter - base address of page directory
; second parameter - entry number
; last parameter - entry value
;
; Flags are added automatically
; P | W | U 
;
%macro assign_pd_entry 3
    mov eax, (%3) | PDE_P | PDE_W | PDE_U
    mov [ %1 + (%2 * 4) ], eax
%endmacro

%macro turn_pg_on 0
;
; Turn on PG
;
  mov eax, cr0
  or eax, 0x80000000
  mov cr0, eax
%endmacro


    section .text
    [BITS 32]
    global turn_on_paging
    
;
; This function turns paging on
; Clobbers eax, ecx, edi
turn_on_paging:
;
; Use two subsequent pages to store a page directory (PD)
; and a page table (PT)
;
; The first page is the page directory (PD)
; The second is the page table (PT)
;  
  xor eax, eax
;
; Place edi the address of PD, use it as base address
; when access PD and PTs
;
  mov edi, PAGE_DIRECTORY    
;
; three pages of double words
;
  mov ecx, 2 * PAGE_SIZE/4
; save edi for future use
  push edi
  rep stosd
  pop edi
;
; Now we have 2 empty subsequent pages filled with 0s
;
; Each page directory entry contains reference to 4M region
; or page table. If entry is not marked with P (present) flag 
; then such entry is ignored. We filled pages with 0s so all
; entries in the page directory are absent
;
; assign first entry reference to the first page table
  assign_pd_entry edi, 0, PAGE_DIRECTORY + PAGE_SIZE
; assign last entry reference to the second page table
; Now we have page directory with 1 entry...
;
; Fill the first page table
; Fill only 1st Megabyte of pages (256 entries)
  mov ecx, 0x100000 / 4096
; Each entry is present and writable
  mov eax, PTE_P | PTE_W
; edi contains pointer to the PD
; add page size to have a pointer to the PT
  add edi, PAGE_SIZE
;
.l1:
	stosd
; Increment by 0x1000 to have 0, 0x1000, 0x2000, ...
; sequence in page table entries
	add eax, PAGE_SIZE
	loop .l1
;
; Turn on paging
;
  mov eax, PAGE_DIRECTORY
  mov cr3, eax
  
  turn_pg_on
  
  ret

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

Другие материалы по экспериментам с разработкой ОС, см в разделе OSDev

Продолжение…

Оставьте комментарий