Interrupts
Часть 1 - Прерывания, ISR, IRQ,PIC
Что такое прерывание ?
Интел учит нас , что прерывания останавливают ход программы при возникновении
внешних событий или ошибок.
Как происходит прерывание ? Оно может быть сгенерировано железом.
Его можно также вызвать программно.
Например , при нажатии клавиши прерывание поступает в процессор.
Процессор останавливает выполняемый код и вызывает функцию ,
которая будет читать порт 0x60(keyboard's output port).
Эта же функция потом должна вернуть контроль.
Программный код может вызвать прерывание путем вызова инструкции
'int <номер прерывания>'
Разрешение и запрещение прерываний выполняется с помощтю команд 'cli'(disable) и 'sti'(enable).
Где используются прерывания ?
Дисководы , звуковые карты , сетевые карты также используют прерывания.
PIT(Programable Interrupt Timer)
генерит прерывания со специальным интервалом , что полезно при реализации многозадачности.
Пользовательские программы могут использовать прерывния.
Например , в досе с помощью прерываний выводятся сообщения на экран.
В биосе есть специальные прерывания для пользовательских программ ,
с помощью которых можно переключаться между графическими режимами.
Что такое ISR?
ISR(Interrupt Service Routine) - это код , выполняемый в результате прерывания.
Каждому прерыванию должна соответствовать своя ISR.
Написание ISR
Во первых , нужно сохранить в стеке содержимое регистров SS, EIP, ESP, CS .
Затем в указатель стека положить адрес нужной ISR.
После чего будет выполнен ее код вплоть до инструкции 'iret',
которая восстановит вышеуказанные регистры .
Небольшой кусок на NASM :
isr:
pusha
push gs
push fs
push es
push ds
; do what you want to here :)
pop ds
pop es
pop fs
pop gs
popa
iret
IRQ
IRQ(Interrupt Request) - прерывание , генерируемое железом.
16 IRQ для PC пронумерованы от 0 до 15.
PIC(Programable Interrupt Controller)
ловит эти прерывания с помощью 2-х блоков по 8 IRQ каждое.
IRQ пересекаются с исключениями , поэтому их номера нужно переназначать.
PIC - шлюз между IRQ и CPU
Когда случается хардварное прерывание , много чего может произойти.
Для лучшей совместимости устройств и различных платформ ,
при генерации прерывания устройство посылает минимум необходимой информации на
PIC(Programable Interrupt Controller).
PIC вычисляет номер прерывания и сигнализирует процессору об этом.
После чего процессор останавливает поток выполняемых инструкций и вызывает
соответствующую процедуру прерывания.
Часть 2 - Exceptions
Что такое исключение ?
Исключение - это событие , возникающее тогда , когда выясняется ,
что в выполняемом коде заложена ошибка.
Например , это может быть деление на ноль , попытка доступа к несуществующему сегменту .
Существуют таблица стандартных интеловских исключений.
0 | Divide Error |
1 | Debug Exceptions |
2 | Intel reserved |
3 | Breakpoint |
4 | Overflow |
5 | Bounds Check |
6 | Invalid Opcode |
7 | Coprocessor Not Available |
8 | Double Fault |
9 | Coprocessor Segment Overrun |
10 | Invalid TSS |
11 | Segment Not Present |
12 | Stack Exception |
13 | General Protection Exception(Triple Fault) |
14 | Page Fault |
15 | Intel reserved |
16 | Coprocessor Error |
Что должна делать ISR , обрабатывающая исключение ?
Рассмотрим конкретный пример обратки исключения - деления на ноль.
Этот пример со смешанным кодом на си и ассемблере.
isr0:
pusha
push gs
push fs
push ds
push es
extern _interrupt_0
call _interrupt_0
pop es
pop ds
pop fs
pop gs
popa
iret
C function - interrupt_0:
void interrupt_0()
{
printf("Exception: Divide Error");
// if we just have ring 0 tasks, we just need to halt the computer
// if we also had ring 3 tasks, and a ring 3 task was the cause of the
// exception, then we should delete the ring 3 task and continue on
asm("cli");
asm("hlt");
};
Фактически на каждое исключение нужно иметь отдельную функцию .
Exception Interrupt 14 - Page Fault
Если вы будете использовать paging , вам понадобится это исключение.
Если вы попытаетесь получить доступ к странице , которая промаркирована
как not-present, процессор сгенерирует это исключение.
isr14:
pusha
push gs
push fs
push ds
push es
mov eax, cr2 ; CR2 contians the address that the program tried to access
push eax ; now our C function can get it
extern _interrupt_14
call _interrupt_14
pop eax ; have to pop what we pushed or the stack gets messed up
pop es
pop ds
pop fs
pop gs
popa
iret
void interrupt_14(u_long cr2)
{
// do whatever, the cr2 variable contains the address that the program
// tried to access that generated the page fault
};
Часть 3 - IDT
Что такое IDT?
IDT(Interrupt Descriptor Table)
это массив дескрипторов (каждый длиной 8 байт) , который необходим для того ,
чтобы привязать прерывания и исключения к нужным ISR.
IDT может хранить максимум 256 дескрипторов.
Не обязательно инициализировать все 256 дескрипторов.
Достаточно только те , которые вы собираетесь использовать.
Инструкция LIDT инициализирует этот массив.
IDT Descriptor Format
Дескриптор IDT имеет 8-байтную длину.
31 | 15 | 14 | 12 | 7 | 4 | 0 |
Offset 31-16(word) | Present(1 bit) | DPL(2 bits) | 01110(5 bits) | 000(3 bits) | Not Used | 4 |
Selector31-16(word) | Offset 15-0(word) | 0 |
Несколько слов о бите Present , поле DPL , селекторе и смещении :
Present: 0 = not present, 1 = present.
Если Present =0 , будет сгенерировано исключение.
DPL: Descriptor Privilege Level, 00 = ring0, 01 = ring1, 10 = ring2, 11 = ring3
Selector: Кодовый селектор , который будет использовать ISR
Offset: Адрес самой ISR.
Теперь посмотрим на реальный дескриптор.
Установим DPL = ring0, present = 1, selector = 0x10, offset= 0x200000.
31 | 15 | 14 | 12 | 7 | 4 | 0 |
0x20 | 1 | 00 | 01110 | 000 | 00000 | 4 |
0x10 | 0x0000 | 0 |
Переведем это на NASM:
dw 0x0000
dw 0x10
dw 0x8E00
dw 0x20
Теперь осталось сказать процессору , как воспользоваться всем этим.
Загрузка IDT с помощью LIDT
Мы загружаем IDT с помощью инструкции LIDT .
LIDT в качестве параметра имеет указатель на структуру , которая описывает IDT,
в которой содержится начальный адрес и число дескрипторов.
31 | 15 | 0 |
| Limit 15-0(word) | 4 |
Base 31-0(double word) | 0 |
Base: адрес самой IDT
Limit: длина IDT в байтах
Теперь рассмотрим пример с IDT , в которой 3 дескриптора :
start_of_idt:
;first entry
dw 0x0000
dw 0x10
dw 0x8E00
dw 0x20
;second entry
dw 0x0000
dw 0x10
dw 0x8E00
dw 0x30
;third entry
dw 0x0000
dw 0x10
dw 0x8E00
dw 0x10
end_of_idt:
2 метки - start_of_idt и end_of_idt , дадут нам возможность вычислить IDT pointer.
31 | 15 | 0 |
| end_of_idt - start_of_idt - 1 | 4 |
start_of_idt | 0 |
Переведем указатель на NASM:
idt_pointer:
dw end_of_idt - start_of_idt - 1
dd start_of_idt
И наконец сама загрузка :
lidt [idt_pointer]
Все в кучу
Теперь соберем все вместе и построим IDT , которая будет отлавливать 16 прерываний
и передавать их в ISR , которая будет расположена по адресу 0x200000(окромя прерываний 2 и 15 - которые
зарезервированы Intel).
NASM-код:
[section .text]
;load the IDT, this requires that the data section with
;the IDT exists
lidt [idt_pointer]
[section .data]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; the IDT with it's descriptors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
start_of_idt:
;interrupt 0
dw 0x0000
dw 0x10
dw 0x8E00
dw 0x20
;interrupt 1
dw 0x0000
dw 0x10
dw 0x8E00
dw 0x20
;interrupt 2, intel reserved, we set the 'present' bit to 0 on this one
dw 0x0000
dw 0x10
dw 0xE00
dw 0x20
;interrupts 3-14 now, since we are making the descriptors
;identical, we are going to loop to get them all(12 total)
%rep 0xC
dw 0x0000
dw 0x10
dw 0x8E00
dw 0x20
%endrep
;interrupt 15, intel reserved, we set the 'present' bit to 0 on this one
dw 0x0000
dw 0x10
dw 0xE00
dw 0x20
;interrupt 16
dw 0x0000
dw 0x10
dw 0x8E00
dw 0x20
end_of_idt:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; now for the IDT pointer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
idt_pointer:
dw end_of_idt - start_of_idt - 1
dd start_of_idt
|
Serg | Возможно ли вызвать ISR вручную для конкретного прерывания, например, не вызывая int 0x80, выполнить печать в консоль? 2019-04-16 00:24:48 | Яковлев Сергей | Конечно можно, например hello world:
.data
s:
.ascii "hello world
"
len = . - s
.text
.global _start
_start:
movl $4, %eax * write system call number *
movl $1, %ebx * stdout *
movl $s, %ecx * the data to print *
movl $len, %edx * length of the buffer *
int $0x80
movl $1, %eax * exit system call number *
movl $0, %ebx * exit status *
int $0x80
2019-04-16 14:34:57 | |
|