Let's see how some relatively small core does it, i.e. Linux 0.01 !
- Make space for interrupt descriptor table
This is done twice (well, technically only once): firstly, the bootloader (path /boot/boot.s boot / /boot/boot.s ) initializes the IDTR, so the CPU is happy when it /boot/boot.s protected mode. The content of the IDTR is as follows:
idt_48: .word 0 | idt limit=0 .word 0,0 | idt base=0L
IDTR is loaded as follows:
lidt idt_48 | load idt with 0,0
Now the jump can be completed.
Please note that there is no IDT. This is just a dummy, so no error occurs in the kernel.
Then, the real IDT is initialized (path / /boot/head.s ). The space is allocated as follows:
_idt: .fill 256,8,0
- Tell the CPU where this space is (see GDT
lidt : lidt works just like lgdt )
lidt expects a linear address containing the contents of the IDTR. This content is as follows:
idt_descr: .word 256*8-1 # idt contains 256 entries .long _idt
IDTR is initialized as follows:
lidt idt_descr
- Tell PIC that you no longer want to use the default BIOS settings (see PIC Chip Programming).
As @RossRidge explained in the comments on your question, this means reassigning IRQ (IV) interrupt vectors.
Because PIC IV overlaps with Intel x86 exception addresses, we must reassign one of them. The exception addresses are tightly coupled, so we need to reassign the PIC vectors.
See also this comment right above the relevant Linus code:
| well, that went ok, I hope. Now we have to reprogram the interrupts :-( | we put them right after the intel-reserved hardware interrupts, at | int 0x20-0x2F. There they won't mess up anything. Sadly IBM really | messed this up with the original PC, and they haven't been able to | rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f, | which is used for the internal hardware interrupts as well. We just | have to reprogram the 8259's, and it isn't fun.
Now here is the real code. jmp between them is designed to synchronize the CPU and PIC, so the CPU will not send data that the PIC cannot yet receive. This is comparable to the wait states when writing to memory: when the processor is faster than the memory / memory arbiter, the next time it needs to wait a while before accessing the memory.
mov al,#0x11 | initialization sequence out #0x20,al | send it to 8259A-1 .word 0x00eb,0x00eb | jmp $+2, jmp $+2 out #0xA0,al | and to 8259A-2 .word 0x00eb,0x00eb mov al,#0x20 | start of hardware int (0x20) out #0x21,al .word 0x00eb,0x00eb mov al,#0x28 | start of hardware int 2 (0x28) out #0xA1,al .word 0x00eb,0x00eb mov al,#0x04 | 8259-1 is master out #0x21,al .word 0x00eb,0x00eb mov al,#0x02 | 8259-2 is slave out #0xA1,al .word 0x00eb,0x00eb mov al,#0x01 | 8086 mode for both out #0x21,al .word 0x00eb,0x00eb out #0xA1,al .word 0x00eb,0x00eb mov al,#0xFF | mask off all interrupts for now out #0x21,al .word 0x00eb,0x00eb out #0xA1,al
- Write a pair of ISR handlers (see "Interrupt Maintenance Procedures") for IRQs and Exceptions
For exceptions, you can find the handler code in /kernel/traps.c and /kernel/asm.s .
Some exceptions push the error code onto the stack before moving to the handler that you must pop up, or the iret command will fail. The page error also writes the corresponding virtual address in cr2 .
IRQ handlers are distributed throughout the system. -.- The timer and disk interrupt handlers are in /kernel/system_call.s , for example, the keyboard interrupt handler is in /kernel/keyboard.s .
- Put the addresses of the ISR handlers in the appropriate descriptors
Exception is thrown in /kernel/traps.c in the trap_init function:
void trap_init(void) { int i; set_trap_gate(0,÷_error); set_trap_gate(1,&debug); set_trap_gate(2,&nmi); set_system_gate(3,&int3); /* int3-5 can be called from all */ set_system_gate(4,&overflow); set_system_gate(5,&bounds); set_trap_gate(6,&invalid_op); set_trap_gate(7,&device_not_available); set_trap_gate(8,&double_fault); set_trap_gate(9,&coprocessor_segment_overrun); set_trap_gate(10,&invalid_TSS); set_trap_gate(11,&segment_not_present); set_trap_gate(12,&stack_segment); set_trap_gate(13,&general_protection); set_trap_gate(14,&page_fault); set_trap_gate(15,&reserved); set_trap_gate(16,&coprocessor_error); for (i=17;i<32;i++) set_trap_gate(i,&reserved); /* __asm__("movl $0x3ff000,%%eax\n\t" "movl %%eax,%%db0\n\t" "movl $0x000d0303,%%eax\n\t" "movl %%eax,%%db7" :::"ax");*/ }
Initializing an IRQ handler entry again spreads over several files. sched_init from /kernel/sched.c initializes the address of the timer interrupt handler, for example.
- Enable All Supported IRQ Mask Interrupts (PIC)
This is done in /init/main.c in the main function with the sti macro. It is defined in /asm/system.h as follows:
#define sti() __asm__ ("sti"::)