Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

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

Что такое исключение ?

Исключение - это событие , возникающее тогда , когда выясняется , что в выполняемом коде заложена ошибка. Например , это может быть деление на ноль , попытка доступа к несуществующему сегменту .

Существуют таблица стандартных интеловских исключений.

Divide Error
Debug Exceptions
Intel reserved
Breakpoint
Overflow
Bounds Check
Invalid Opcode
Coprocessor Not Available
Double Fault
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-байтную длину.

31151412740
Offset 31-16(word)Present(1 bit)DPL(2 bits)01110(5 bits)000(3 bits)Not Used4
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.

31151412740
0x2010001110000000004
0x100x00000

Переведем это на NASM:

dw 0x0000
 dw 0x10
 dw 0x8E00
 dw 0x20
 

Теперь осталось сказать процессору , как воспользоваться всем этим.

Загрузка IDT с помощью LIDT

Мы загружаем IDT с помощью инструкции LIDT . LIDT в качестве параметра имеет указатель на структуру , которая описывает IDT, в которой содержится начальный адрес и число дескрипторов.

31150
 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.

31150
 end_of_idt - start_of_idt - 14
start_of_idt0

Переведем указатель на 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