Что такое Interrupt Service Routine (ISR)?
Когда срабатывает внешнее прерывание - нормальное течение команд
прерывается и вызывается подпрограмма обслуживания прерываний (ISR).
Прерывание может быть вызвано как железом , так и софтом .
Например , каждый раз при нажатии на клавишу
срабатывает прерывание IRQ1 (Interrupt Request
1)
Софт может вызвать прерывания с помощью ключевого слова int
.
Нужно различать , что в защищенном режиме текущие прерывания хранятся в
в interrupt descriptor table (IDT) , а в реальном режиме - в interrupt vector table (IVT)
.
Problem
Некоторые пытаются писать inline-код в следующем стиле :
/* How NOT to write an interrupt handler */
void interrupt_handler(void)
{
__asm__("pushad"); /* Save registers. */
/* do something */
__asm__("popad"); /* Restore registers. */
__asm__("iret"); /* This will triple-fault! */
}
Это не будет работать . Правильно вот так :
push %ebp
mov %esp,%ebp
sub $<size of local variables>,%esp
pushad
# C code comes here
popad
iret
# 'leave' if you use local variables, 'pop %ebp' otherwise.
leave
ret
Solutions
Two-Stage Assembler Wrapping
Напишем враппер на ассемблере , который вызывает си-шную функцию :.
/* filename : isr_wrapper.asm */
.globl _isr_wrapper
.align 4
_isr_wrapper:
pushad
call _interrupt_handler
popad
iret
/* filename : interrupt_handler.c */
void interrupt_handler(void)
{
/* do something */
}
Загрузчик на GAS - первые шаги
Начнем со следующего кода - step1.s - создаем простой бинарник размером 512 байт ,
в его конец не забудем прописать 2 магических байта:
.code16
.text
.global _start
start:
hang:
jmp hang
.org 510
boot_flag:
.word 0xAA55
Компиляция :
as step1.s -o step1.o
ld step1.o -o step1.bin --oformat binary
Немного усложним задачу - step2.s - распечатаем сообщение на экране .
Для этого используем 10-е прерывание.
Команда lodsb последовательно прогрузит байтики текстового сообщения.
В примере используем числовые метки.
Если при обращении метка стоит выше , к ней добавляется префикс
b,
если ниже - префикс
f:
.code16
.text
# .global _start
# start:
mov $0x07C0, %ax
mov %ax, %ds
mov $msg, %si
1: lodsb
or %al,%al # zero=end of str
jz 2f # get out
mov $0x0E, %ah
int $0x10
jmp 1b
2:
jmp 2b
msg: .asciz "Welcome to www.iakovlev.org\n"
.org 510
.word 0xAA55
Копиляция :
as step2.s -o step2.o
ld --oformat binary --Ttext 0x0 -o step2.bin step2.o
Далее - step3.s - попробуем вызвать подпрограмму внутри загрузчика :
.code16
.text
mov $0x07C0, %ax
mov %ax, %ds
mov $msg, %si
call Print
1:
jmp 1b
# Prints a NULL-terminated string
# INPUT: SI=String
# OUTPUT: None
# CLOBBERS: SI, AX, BX, FLAGS
Print:
mov $0x0E, %ah
xor %bx, %bx
1:
lodsb
or %al, %al
jz 2f
# INT 0x10 VIDEO TELETYPE
# AH=0xE AL=Character [BH=VideoPage BL=Attribute]
int $0x10
jmp 1b
2:
ret
msg: .asciz "Welcome to www.iakovlev.org\n"
.org 510
.word 0xAA55
Компиляция :
as step3.s -o step3.o
ld --oformat binary --Ttext 0x0 -o step3.bin step3.o
Следующим вариантом будет использование макроса .
2 исходника - step4.s
.code16
.text
.global _start
_start:
.include "our_macros.s"
1:
jmp 1b
и our_macros.s :
# Syntax is .macro MacroName Param1, Param2, Param3, ...
# Alternative: .macro MacroName Param1 Param2 Param3 ...
# You can also set defaults like .macro DoSomething Int1=3 Int2=6
.macro BIOSPrint StringPointer
mov $\StringPointer, %si
1: lodsb
or %al,%al
jz 2f
mov $0x0E, %ah
int $0x10
jmp 1b
2:
.endm
mov $0x07C0, %ax
mov %ax, %ds
BIOSPrint msg
2:
jmp 2b
msg: .asciz "Welcome to www.iakovlev.org\n"
.org 510
.word 0xAA55
компиляция -
as step4.s -o step4.o
ld --oformat binary --Ttext 0x0 -o step4.bin step4.o
В следующем примере переключаемся в защищенный режим .
Глобальная таблица дескрипторов - gdt - имеет размер 1 MB и расположена
в памяти начиная с адреса 0x0 . После возвращения из защищенного режима
в real mode ограничение на размер сираницы в 64 КВ уже недействительно.
.code16
.text
ljmp $0, $Start
Start:
xor %ax, %ax
mov %ax, %ds
mov %ax, %ss
mov $0x8200, %sp # Differs from the original since we hardly need 7KBs of stack
cli # Interrupts off, save current DS
push %ds
lgdt (gdtinfo) # The brackets are just decoration, it works without them
# but you may find it more legible this way
mov %cr0, %eax # Enable protected mode
or $1, %al
mov %eax, %cr0
mov $0x8, %ax # Load selector 1, 32bit flat data
mov %ax, %ds
mov $0x0f01, %ax # Use the segment to write to the screen
mov %ax, %ds:(0xB8000)
mov %cr0, %eax # Shut off protected mode
xor $1, %al
mov %eax, %cr0
pop %ds # Get the realmode segment back and enable interrupts
sti
mov $msg, %si
10: lodsb
or %al,%al # zero=end of str
jz 1f # get out
mov $0x0E, %ah
int $0x10
jmp 10b
1:
jmp 1b
# Description for the CPU to find the GDT
gdtinfo: .word gdtend - gdt - 1
.long gdt
gdt: .long 0 # Descriptor 0 is the NULL descriptor
.long 0
gdtkerneldata: # 32bit Flat Data, 0-4GB Writable
.word 0xFFFF
.word 0
.byte 0
.byte 0x92
.byte 0xCF
.byte 0
gdtend:
.org 510
.word 0xAA55
Несколько примеров вспомогательных функций:
.code32
# ------------------
dochar:
call cprint # print one character
sprint:
lodsb # string char to AL
or %al, %al
jnz dochar # else, we're done
addb $1, (ypos) # down one row
movb $0, (xpos) # back to left
ret
cprint:
mov $0x0F, %ah # attrib = white on black
mov %eax, %ecx # save char/attribute
movzxb (ypos), %eax
mov $160, %edx # 2 bytes (char/attrib)
mul %edx # for 80 columns
movzxb (xpos), %ebx
shl $1, %ebx # times 2 to skip attrib
mov $0xb8000, %edi # start of video memory
add %eax, %edi # add y offset
add %ebx, %edi # add x offset
mov %ecx, %eax # restore char/attribute
movw %ax, %ds:(%edi)
addb $1, (xpos) # advance to right
ret
# ------------------------------------
printreg32:
mov $outstr32, %edi
mov (reg32), %eax
mov $hexstr, %esi
mov $8, %ecx # eight nibbles
hexloop:
rol $4, %eax # leftmost will
mov %eax, %ebx # become rightmost
and $0x0f, %ebx
mov (%esi, %ebx), %bl # index into hexstr
mov %bl, (%edi)
inc %edi
dec %ecx
jnz hexloop
mov $outstr32, %esi
call sprint
ret
# ------------------------------------
xpos: .byte 0
ypos: .byte 0
hexstr: .ascii "0123456789ABCDEF"
outstr32: .asciz "00000000" # register value
reg32: .long 0 # pass values to printreg32