3. 5Исследование готовых файлов 5
Скачать 438.97 Kb.
|
4.3Исходные коды ядра ОС4.3.1boot.s4.3.2main.c5.Проектирование исходного кода ядра ОС5.1Таблицы IDT и GDTФайлы, в которых реализован код этой главы
На следующей странице приведена общая диаграмма процесса инициализации таблиц. Там видно, что в начале вызывается функция 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); }
В результате получаем такую таблицу 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. |