Я сделал уже много предварительных экспериментов. У меня есть загрузчики для дискет и флешек, есть заготовка ядра, есть билд-система для автоматизации сборки.
Boot loader грузит файл ядра в память в реальном режиме по адресу 0x10000 и передаёт управление на начало ядра.
Ядро в реальном режиме с помощью функций BIOS получает карту памяти, после этого производит переключение в защищенный режим, выводит на экран полученную карту памяти, а после этого включает обработку прерываний.
Собственно включение обработки прерываний и является сутью моего последнего эксперимента и темой данного поста. Ниже код, это исполняющий. На начальный момент приведенного фрагмента процессор уже переведен в защищённый режим. Про переход в защищенный режим читайте Переход в защищённый режим в ядре
; ; set up interrupt handlers ; call idt_set_handlers ; ; initialize i8259 cascade mode ; remap interrupt 0 to vector 0x20 ; remap interrupt 8 to vector 0x28 ; i8259_init_cascade 0x20,0x28 i8259_unmask_all sti kernel_loop: hlt mov esi, kernel_loop_message call strPrint call updateCursor jmp kernel_loop kernel_loop_message:
Я применил макросы. На мой взгляд при грамотном применении они сильно повышают читаемость ассемблерного кода. Код становится сильно похожим на язык высокого уровня. Теперь о приведенном коде подробнее.
Первым шагом я настраиваю таблицу обработчиков прерываний. Я делаю это функцией idt_set_handlers. Таблица прерываний защищённого режима является обычным массивом 8-байтных дескрипторов прерываний (IDT).
(Картинка взята из руководства Intel)
За подробностями в перевод руководства Intel: Дескрипторы прерываний IDT или посмотрите замечательную статью Прерывания в защищенном режиме процессора IA-32 /18.03.2007/.
Установка дескрипторов прерываний реализована так:
%include 'kernel/consts.inc' %include 'kernel/idt/idt.inc' section .text global idt_set_handlers [bits 32] extern global_idt extern global_idtr extern idt_irq_0_handler extern idt_handler_gp idt_set_handlers: ; set interrupt handlers idt_set_interrupt_gate 0x20, idt_irq_0_handler ; load idt address register lidt [ds:global_idtr] ret
Макро idt_set_interrupt_gate заполняет содержимое дескриптора прерывания под номером 0x20. Реализация макро такая:
; ; This file contains definitions of macros for IDT manipulation ; ; Parameters ; vector number ; handler address ; ; Clobbers eax, esi ; %macro idt_set_interrupt_gate 2 ; copy address of interrupt handler mov eax, %2 mov esi, global_idt mov [esi + 8*%1], ax shr eax, 16 mov [esi + 8*%1 + 6 ], ax ; set interrupt segment mov word [esi + 8*%1 + 2], 0x8 ; KERNEL_CODE_SEGMENT ; set interrupt_gate_parameters mov word [esi + 8*%1 + 4], 1000111000000000b %endmacro
После имени макро указывается количество параметров. В данном случае два. Первый параметр — номер вектора прерывания. Второй — адрес функции обработчика. В коде параметры записываются в виде %1, %2
и т.д. %1 заменяется текстом первого параметра, %2 — второго, и т.д.
После настройки 0x20-го вектора прерывания, я вызываю функцию загрузки регистра IDTR, который и содержит адрес таблицы векторов прерываний IDT. На этом функция idt_set_handlers завершается.
Собственно описывается в отдельном файле. Она лежит в сегменте данных моего ядра:
; ; The IDT table ; section .data global global_idt global global_idtr align 16 global_idt: align 1 dd 0, 0 ; 0x0 dd 0, 0 ; 0x1 dd 0, 0 ; 0x2 dd 0, 0 ; 0x3 dd 0, 0 ; 0x4 dd 0, 0 ; 0x5 dd 0, 0 ; 0x6 dd 0, 0 ; 0x7 dd 0, 0 ; 0x8 dd 0, 0 ; 0x9 dd 0, 0 ; 0xa dd 0, 0 ; 0xb dd 0, 0 ; 0xc dd 0, 0 ; 0xd dd 0, 0 ; 0xe dd 0, 0 ; 0xf dd 0, 0 ; 0x10 dd 0, 0 ; 0x11 dd 0, 0 ; 0x12 dd 0, 0 ; 0x13 dd 0, 0 ; 0x14 dd 0, 0 ; 0x15 dd 0, 0 ; 0x16 dd 0, 0 ; 0x17 dd 0, 0 ; 0x18 dd 0, 0 ; 0x19 dd 0, 0 ; 0x1a dd 0, 0 ; 0x1b dd 0, 0 ; 0x1c dd 0, 0 ; 0x1d dd 0, 0 ; 0x1e dd 0, 0 ; 0x1f dd 0, 0 ; 0x20 (hardware intr 0) dd 0, 0 ; 0x21 dd 0, 0 ; 0x22 dd 0, 0 ; 0x23 dd 0, 0 ; 0x24 dd 0, 0 ; 0x25 dd 0, 0 ; 0x26 dd 0, 0 ; 0x27 dd 0, 0 ; 0x28 dd 0, 0 ; 0x29 dd 0, 0 ; 0x2a dd 0, 0 ; 0x2b dd 0, 0 ; 0x2c dd 0, 0 ; 0x2d dd 0, 0 ; 0x2e dd 0, 0 ; 0x2f dd 0, 0 ; 0x30 dd 0, 0 ; 0x31 dd 0, 0 ; 0x32 global_idt_size equ $-global_idt global_idtr: dw global_idt_size-1 dd global_idt
global_idtr — указатель на структуру в памяти, которая загрузится в регистр idtr. За подробностями: Таблица дескрипторов прерываний IDT
Дескрипторов прерываний много, однако я загружаю только дескриптор с номером 0x20. Этот дескриптор является обработчиком прерывания от таймера. Таймер на аппаратном уровне приходит по линии прерывания с номером 0. Но за счёт настройки контроллера прерываний, можно настроить трансляцию номеров аппаратных прерываний. Что и сделано в данном случае c помощью инициализации контроллера прерываний. Прерывания первого контроллера 0x20-0x27, прерывания второго 0x28-0x2f.
i8259_init_cascade 0x20,0x28
; ; init i8259a for cascade mode ; ; Parameters ; an interrupt vector number for irq 0 ; the interrupt vector number for irq 8 %macro i8259_init_cascade 2 mov al, 0x11 ; send ICW1 to both controllers out 0x20, al out 0xa1, al mov al, %1 ; ICW2 out 0x21, al ; set vector numbers mapping mov al, %2 out 0xa1, al mov al, 0x4 ; ICW3 out 0x21, al ; set line for master and slave mov al, 0x2 out 0xa1, al mov al, 0x1 ; ICW4 special fully nested 8086 mode out 0x21, al mov al, 0x1 ; ICW4 fully nested 8086 mode out 0xa1, al %endmacro
Про инициализацию контроллера прерываний читайте Структура и инициализация контроллера прерываний Intel 8259A. Единственное отличие. Я в ICW4 заслал в оба контроллера 1-ки.
После инициазации я разрешаю аппаратные прерывания:
; ; unmask all irq of 8259a-1 and 8259a-2 ; %macro i8259_unmask_all 0 xor al, al out 0x21, al ; unmask all irqs of 8259a-1 out 0xa1, al ; unmask all irqs of 8259a-2 %endmacro
Далее идёт разрешение прерываний в процессоре и цикл:
sti kernel_loop: hlt mov esi, kernel_loop_message call strPrint call updateCursor jmp kernel_loop kernel_loop_message:
Данный код в цикле останавливает процессор инструкцией hlt до получения аппаратного прерывания. При получении аппаратного прерывания происходит переход на следующую за hlt инструкцию. Таким образом, этот код, ждет прерывания, выводит на экран сообщение и снова ждёт прерывания.
Единственный, как я уже говорил обработчик прерывания — обработчик прерывания от таймера. Он такой:
; ; timer interrupt handler ; %include 'i8259.inc' %include 'kernel/idt/idt.inc' section .text global idt_irq_0_handler [bits 32] align 16 idt_irq_0_handler: align 1 push ax ; send end of interrupt i8259_1_eoi pop ax iretd
Он ничего не делает, кроме посылки на контроллер команты EOI (End of Interrupt). После этого он завершается командой iretd. Посылка EOI снова макро (т.к. контроллеров 2, то и макро 2):
i8259_1_eoi и i82959_2_eoi
; ; send EOI (End Of Interrupt) ; %macro i8259_1_eoi 0 mov al, 0x20 out 0x20, al %endmacro %macro i8259_2_eoi 0 mov al, 0x20 out 0xa0, al %endmacro
Собираю это все, компилирую, 20-ть раз отлаживаюсь, нахожу опечатку в инициализации контроллера прерывания и всё, начинает работать при загрузке и на виртуальной машине и с флешки. Выглядит где-то так:
Краткое summary: чтобы реализоавать минимальный тест с обработкой прерываний, нужно описать таблицу прерываний. Перенастроить контроллер прерываний, чтобы аппаратные прерывания не перекрывались с исключениями защищенного режима. Поместить в таблицу прерываний хотя бы один обработчик прерывания. Этого вполне достаточно. На git все исходники эксперимента доступны по адресу: https://github.com/chesnokov/dev64-os/tree/master/experiments/exp.000005
Продолжение… Эксперимент с настройкой страничного механизма в ядре ОС
Другие статьи на тему экспериментов с разработкой ОС смотреть здесь: https://dev64.wordpress.com/osdev/