Главная страница

3. 5Исследование готовых файлов 5


Скачать 438.97 Kb.
Название3. 5Исследование готовых файлов 5
Дата12.09.2018
Размер438.97 Kb.
Формат файлаdocx
Имя файла1. MiniOS doc.docx
ТипДокументы
#50456
страница4 из 6
1   2   3   4   5   6

4.3Исходные коды ядра ОС

4.3.1boot.s

4.3.2main.c

5.Проектирование исходного кода ядра ОС

5.1Таблицы IDT и GDT


Файлы, в которых реализован код этой главы

  • descriptor_tables.c / .h
    Начальная инициализация (заполнение) таблиц IDT и GDT

  • gdt.s
    Функции на ассемблере для загрузки в соответствующие регистры новых IDT и GDT

  • interrupt.s
    Низкоуровневые (на ассемблере) обработчики прерываний. На них указывают записи в таблице IDT.

  • isr.c / .h
    Высокоуровневые (на Си) обработчики прерываний

На следующей странице приведена общая диаграмма процесса инициализации таблиц. Там видно, что в начале вызывается функция init_descriptor_tables. В ней происходит вызов функции инициализации GDT. А затем и IDT.

5.1.1Инициализация таблицы дескрипторов сегментов GDT


Структура для одной строки таблицы GDT

// This structure contains the value of one GDT entry.

// We use the attribute 'packed' to tell GCC not to change

// any of the alignment in the structure.

struct gdt_entry_struct

{

u16int limit_low; // The lower 16 bits of the limit.

u16int base_low; // The lower 16 bits of the base.

u8int base_middle; // The next 8 bits of the base.

u8int access; // Access flags, determine what ring this segment can be used in.

u8int granularity;

u8int base_high; // The last 8 bits of the base.

} __attribute__((packed));
typedef struct gdt_entry_struct gdt_entry_t;

Объявляем массив из структур gdt_entry_t, это и будет наша таблица GDT.

gdt_entry_t gdt_entries[5];

Функция заполнения строки под номером “num” в таблице GDT

static void gdt_set_gate(s32int num, u32int base, u32int limit, u8int access, u8int gran)

{

gdt_entries[num].base_low = (base & 0xFFFF);

gdt_entries[num].base_middle = (base >> 16) & 0xFF;

gdt_entries[num].base_high = (base >> 24) & 0xFF;
gdt_entries[num].limit_low = (limit & 0xFFFF);

gdt_entries[num].granularity = (limit >> 16) & 0x0F;

gdt_entries[num].granularity |= gran & 0xF0;

gdt_entries[num].access = access;

}

Чтобы загрузить новую таблицу в регистр, мы будем использовать специальную команду ассемблера lgdt. Этой команде нужно передавать адрес структуры, в которой содержится указатель на первый элемент нашей таблицы GDT и указатель на конец таблицы. Вот такой формат имеет эта структура:

// This struct describes a GDT pointer. It points to the start of

// our array of GDT entries, and is in the format required by the

// lgdt instruction.

struct gdt_ptr_struct

{

u16int limit; // The upper 16 bits of all selector limits.

u32int base; // The address of the first gdt_entry_t struct.

} __attribute__((packed));
typedef struct gdt_ptr_struct gdt_ptr_t;

Функция загрузки новой таблицы в регистр. В ней выполняем команду ldtr и, затем сразу устанавливаем в сегментные регистры селекторы из новой таблицы GDT.

gdt_flush:

mov eax, [esp+4] ; Get the pointer to the GDT, passed as a parameter.

lgdt [eax] ; Load the new GDT pointer
mov ax, 0x10 ; 0x10 is the offset in the GDT to our data segment

mov ds, ax ; Load all data segment selectors

mov es, ax

mov fs, ax

mov gs, ax

mov ss, ax

jmp 0x08:.flush ; 0x08 is the offset to our code segment: Far jump!

.flush:

ret

Создаем структуру для команды ldtr.

gdt_ptr_t gdt_ptr;

Основная функция. Здесь заполняется структура gdt_ptr. Далее заполняются строки таблицы GDT через функцию gdt_set_gate(). Когда заполнены все строки, вызывается функция gdt_flush для загрузки новой таблицы в регистр.

static void init_gdt()

{

gdt_ptr.limit = (sizeof(gdt_entry_t) * 5) - 1;

gdt_ptr.base = (u32int)&gdt_entries;
gdt_set_gate(0, 0, 0, 0, 0); // Null segment

gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); // Code segment

gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); // Data segment

gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // User mode code segment

gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // User mode data segment
gdt_flush((u32int)&gdt_ptr);

}

Смещение

Base

Limit

Access

Granularity

Описание сегмента

0x0

0x0

0x0

0

0x0

Пустая запись

0x8

0x000000000

0xFFFFFFFF

DPL=0 Type=Code

0xCF

Кода в режиме ядра

0x10

0x000000000

0xFFFFFFFF

DPL=0 Type=Data

0xCF

Данные в режиме ядра

0x18

0x000000000

0xFFFFFFFF

DPL=3 Type=Code

0xCF

Код в режиме пользователя

0x20

0x000000000

0xFFFFFFFF

DPL=3 Type=Data

0xCF

Данные в режиме пользователя

В результате получаем такую таблицу GDT:

5.1.2Инициализация таблицы прерываний IDT


Код инициализации IDT достаточно похож на код инициализации GDT: используются аналогичные функции, но их реализация переписана под таблицу прерываний.

Структура строки IDT

// A struct describing an interrupt gate.

struct idt_entry_struct

{

u16int base_lo; // The lower 16 bits of the address to jump to when this interrupt fires.

u16int sel; // Kernel segment selector.

u8int always0; // This must always be zero.

u8int flags; // More flags. See documentation.

u16int base_hi; // The upper 16 bits of the address to jump to.

} __attribute__((packed));
typedef struct idt_entry_struct idt_entry_t;

Структура для передачи команде lidt

// A struct describing a pointer to an array of interrupt handlers.

// This is in a format suitable for giving to 'lidt'.

struct idt_ptr_struct

{

u16int limit;

u32int base; // The address of the first element in our idt_entry_t array.

} __attribute__((packed));

typedef struct idt_ptr_struct idt_ptr_t;

Объявляем массив idt_entries из 256 элементов. Это и будет наша IDT. И объявляем структуру для передачи команде lidt.

idt_entry_t idt_entries[256];

idt_ptr_t idt_ptr;

Функция, заполняющая строку таблицы

static void idt_set_gate(u8int num, u32int base, u16int sel, u8int flags)

{

idt_entries[num].base_lo = base & 0xFFFF;

idt_entries[num].base_hi = (base >> 16) & 0xFFFF;
idt_entries[num].sel = sel;

idt_entries[num].always0 = 0;

// We must uncomment the OR below when we get to using user-mode.

// It sets the interrupt gate's privilege level to 3.

idt_entries[num].flags = flags /* | 0x60 */;

}

Функция загрузки в регистр

idt_flush:

mov eax, [esp+4] ; Get the pointer to the IDT, passed as a parameter.

lidt [eax] ; Load the IDT pointer.

ret

На ассемблере напишем функции для обработки прерываний. Т.к. они все будут однотипными, то напишем макрос для их генерации. Функции сохраняют свой номер и код ошибки в стек. Причем у некоторых прерываний нет кода ошибки, в таком случае мы запишем в стек 0. А затем передают управление следующей функции. Макросы имеют один параметр – номер, который они будут сохранять в стек.

ISR_NOERRCODE – обработчик программных прерываний без кода ошибки

%macro ISR_NOERRCODE 1

global isr%1

isr%1:

cli ; Disable interrupts firstly.

push byte 0 ; Push a dummy error code.

push byte %1 ; Push the interrupt number.

jmp isr_common_stub ; Go to our common handler code.

%endmacro

ISR_ERRCODE – обработчик программных прерываний с кодом ошибки

; This macro creates a stub for an ISR which passes it's own

; error code.

%macro ISR_ERRCODE 1

global isr%1

isr%1:

cli ; Disable interrupts.

push byte %1 ; Push the interrupt number

jmp isr_common_stub

%endmacro

IRQ number – обработчик аппаратных прерываний

; This macro creates a stub for an IRQ - the first parameter is

; the IRQ number, the second is the ISR number it is remapped to.

%macro IRQ 2

global irq%1

irq%1:

cli

push byte 0

push byte %2

jmp irq_common_stub

%endmacro

Создаем по этим макросам функции, указываем значения параметров:

ISR_NOERRCODE 0

ISR_NOERRCODE 1

ISR_NOERRCODE 2

ISR_NOERRCODE 3

ISR_NOERRCODE 4

ISR_NOERRCODE 5

ISR_NOERRCODE 6

ISR_NOERRCODE 7

ISR_ERRCODE 8

ISR_NOERRCODE 9

ISR_ERRCODE 10

ISR_ERRCODE 11

ISR_ERRCODE 12

ISR_ERRCODE 13

ISR_ERRCODE 14

ISR_NOERRCODE 15

ISR_NOERRCODE 16

ISR_NOERRCODE 17

ISR_NOERRCODE 18

ISR_NOERRCODE 19

ISR_NOERRCODE 20

ISR_NOERRCODE 21

ISR_NOERRCODE 22

ISR_NOERRCODE 23

ISR_NOERRCODE 24

ISR_NOERRCODE 25

ISR_NOERRCODE 26

ISR_NOERRCODE 27

ISR_NOERRCODE 28

ISR_NOERRCODE 29

ISR_NOERRCODE 30

ISR_NOERRCODE 31

IRQ 0, 32

IRQ 1, 33

IRQ 2, 34

IRQ 3, 35

IRQ 4, 36

IRQ 5, 37

IRQ 6, 38

IRQ 7, 39

IRQ 8, 40

IRQ 9, 41

IRQ 10, 42

IRQ 11, 43

IRQ 12, 44

IRQ 13, 45

IRQ 14, 46

IRQ 15, 47

И основная функция для инициализации IDT. В ней заполняем параметры структуры для загрузки в регистр idtr. Далее заполняем нулями наш массив idt_entries (будущую таблицу IDT). Делаем перенастройку аппаратных прерываний на номера 32-47. Это необходимо, т.к. изначально они имеют те же номера что и некоторые программные прерывания. Заполняем строки таблицы, в качестве обработчиков указываем указатели на наши функции. И в конце выполняем загрузку в регистр.

static void init_idt()

{

idt_ptr.limit = sizeof(idt_entry_t) * 256 -1;

idt_ptr.base = (u32int)&idt_entries;
memset(&idt_entries, 0, sizeof(idt_entry_t)*256);
// Remap the irq table.

outb(0x20, 0x11);

outb(0xA0, 0x11);

outb(0x21, 0x20);

outb(0xA1, 0x28);

outb(0x21, 0x04);

outb(0xA1, 0x02);

outb(0x21, 0x01);

outb(0xA1, 0x01);

outb(0x21, 0x0);

outb(0xA1, 0x0);
idt_set_gate( 0, (u32int)isr0 , 0x08, 0x8E);

idt_set_gate( 1, (u32int)isr1 , 0x08, 0x8E);

idt_set_gate( 2, (u32int)isr2 , 0x08, 0x8E);

idt_set_gate( 3, (u32int)isr3 , 0x08, 0x8E);

idt_set_gate( 4, (u32int)isr4 , 0x08, 0x8E);

idt_set_gate( 5, (u32int)isr5 , 0x08, 0x8E);

idt_set_gate( 6, (u32int)isr6 , 0x08, 0x8E);

idt_set_gate( 7, (u32int)isr7 , 0x08, 0x8E);

idt_set_gate( 8, (u32int)isr8 , 0x08, 0x8E);

idt_set_gate( 9, (u32int)isr9 , 0x08, 0x8E);

idt_set_gate(10, (u32int)isr10, 0x08, 0x8E);

idt_set_gate(11, (u32int)isr11, 0x08, 0x8E);

idt_set_gate(12, (u32int)isr12, 0x08, 0x8E);

idt_set_gate(13, (u32int)isr13, 0x08, 0x8E);

idt_set_gate(14, (u32int)isr14, 0x08, 0x8E);

idt_set_gate(15, (u32int)isr15, 0x08, 0x8E);

idt_set_gate(16, (u32int)isr16, 0x08, 0x8E);

idt_set_gate(17, (u32int)isr17, 0x08, 0x8E);

idt_set_gate(18, (u32int)isr18, 0x08, 0x8E);

idt_set_gate(19, (u32int)isr19, 0x08, 0x8E);

idt_set_gate(20, (u32int)isr20, 0x08, 0x8E);

idt_set_gate(21, (u32int)isr21, 0x08, 0x8E);

idt_set_gate(22, (u32int)isr22, 0x08, 0x8E);

idt_set_gate(23, (u32int)isr23, 0x08, 0x8E);

idt_set_gate(24, (u32int)isr24, 0x08, 0x8E);

idt_set_gate(25, (u32int)isr25, 0x08, 0x8E);

idt_set_gate(26, (u32int)isr26, 0x08, 0x8E);

idt_set_gate(27, (u32int)isr27, 0x08, 0x8E);

idt_set_gate(28, (u32int)isr28, 0x08, 0x8E);

idt_set_gate(29, (u32int)isr29, 0x08, 0x8E);

idt_set_gate(30, (u32int)isr30, 0x08, 0x8E);

idt_set_gate(31, (u32int)isr31, 0x08, 0x8E);

idt_set_gate(32, (u32int)irq0, 0x08, 0x8E);

idt_set_gate(33, (u32int)irq1, 0x08, 0x8E);

idt_set_gate(34, (u32int)irq2, 0x08, 0x8E);

idt_set_gate(35, (u32int)irq3, 0x08, 0x8E);

idt_set_gate(36, (u32int)irq4, 0x08, 0x8E);

idt_set_gate(37, (u32int)irq5, 0x08, 0x8E);

idt_set_gate(38, (u32int)irq6, 0x08, 0x8E);

idt_set_gate(39, (u32int)irq7, 0x08, 0x8E);

idt_set_gate(40, (u32int)irq8, 0x08, 0x8E);

idt_set_gate(41, (u32int)irq9, 0x08, 0x8E);

idt_set_gate(42, (u32int)irq10, 0x08, 0x8E);

idt_set_gate(43, (u32int)irq11, 0x08, 0x8E);

idt_set_gate(44, (u32int)irq12, 0x08, 0x8E);

idt_set_gate(45, (u32int)irq13, 0x08, 0x8E);

idt_set_gate(46, (u32int)irq14, 0x08, 0x8E);

idt_set_gate(47, (u32int)irq15, 0x08, 0x8E);
idt_flush((u32int)&idt_ptr);

}

5.1.3Обработка прерывания


При возникновении прерывания, выполнение переходит к обработчику, записанному в IDT. Код начальных обработчиков создается компилятором на основе макросов:

; This macro creates a stub for an ISR which does NOT pass it's own

; error code (adds a dummy errcode byte).

%macro ISR_NOERRCODE 1

global isr%1

isr%1:

cli ; Disable interrupts firstly.

push byte 0 ; Push a dummy error code.

push byte %1 ; Push the interrupt number.

jmp isr_common_stub ; Go to our common handler code.

%endmacro
; This macro creates a stub for an ISR which passes it's own

; error code.

%macro ISR_ERRCODE 1

global isr%1

isr%1:

cli ; Disable interrupts.

push byte %1 ; Push the interrupt number

jmp isr_common_stub

%endmacro
; This macro creates a stub for an IRQ - the first parameter is

; the IRQ number, the second is the ISR number it is remapped to.

%macro IRQ 2

global irq%1

irq%1:

cli

push byte 0

push byte %2

jmp irq_common_stub

%endmacro

Эти обработчики сохраняют свой номер в стек и вызывают следующую функцию. Программные и аппаратные обработчики работают по разному.

В начале рассмотрим обработку программных прерываний. Вызывается функция isr_common_stub. Она сохраняет в стек значения регистров и передает управление функции на Си isr_handler.

; This is our common ISR stub. It saves the processor state, sets

; up for kernel mode segments, calls the C-level fault handler,

; and finally restores the stack frame.

isr_common_stub:

pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
mov ax, ds ; Lower 16-bits of eax = ds.

push eax ; save the data segment descriptor
mov ax, 0x10 ; load the kernel data segment descriptor

mov ds, ax

mov es, ax

mov fs, ax

mov gs, ax
call isr_handler
pop ebx ; reload the original data segment descriptor

mov ds, bx

mov es, bx

mov fs, bx

mov gs, bx
popa ; Pops edi,esi,ebp...

add esp, 8 ; Cleans up the pushed error code and pushed ISR number

sti

iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP

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

// This gets called from our ASM interrupt handler stub.

void isr_handler(registers_t regs)

{

monitor_write("recieved interrupt: ");

monitor_write_dec(regs.int_no);

monitor_put('\n');
if (interrupt_handlers[regs.int_no] != 0)

{

isr_t handler = interrupt_handlers[regs.int_no];

handler(regs);

}

}

Параметры этой функции – структура:

typedef struct registers

{

u32int ds; // Data segment selector

u32int edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha.

u32int int_no, err_code; // Interrupt number and error code (if applicable)

u32int eip, cs, eflags, useresp, ss; // Pushed by the processor automatically.

} registers_t;

Её сформировали в стеке предыдущие функции.

Если был установлен наш обработчик прерывания, он вызывается.

Далее приведены диаграммы обработки прерываний.





5.1.4Установка своих обработчиков прерываний


Для установки своей функции, в качестве обработчика прерывания, написана функция:

void register_interrupt_handler(u8int n, isr_t handler)

{

interrupt_handlers[n] = handler;

}

Первым параметром она принимает номер прерывания, а вторым – указатель на нашу функцию.

Вот пример использования этой функции для установки обработчика на срабатывание таймера (аппаратное прерывание IRQ0).

register_interrupt_handler(IRQ0, &timer_callback);

Теперь каждый раз при возникновении этого прерывания будет вызываться функция timer_callback.
1   2   3   4   5   6


написать администратору сайта