Реализация минимальной поддержки прерываний в ядре ОС на платформе x86

Я сделал уже много предварительных экспериментов. У меня есть загрузчики для дискет и флешек, есть заготовка ядра, есть билд-система для автоматизации сборки.

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/

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