【自制操作系统】(五)处理中断

中断——Interrupt

what

中断指一个由CPU外部(或者内部)产生的信号,用以通知CPU发生了某种特定的事件,如键盘输入、IO任务完成等。

why

以最简单的文件读写操作为例,与忙等(Busy waiting)相比,基于中断(Interrupt-based)的系统能更有效地利用有限的计算资源。没有底层硬件的支持,也就没法实现基于中断的操作系统。因此,现代CPU都采用基于中断的体系架构。

which

  1. 中断既可以产生于CPU外部,也可以产生于CPU内部。

CPU内部也可以产生中断,用以通知操作系统发生了某些特殊的事件,如页错误(Page Fault)等。

  1. 中断既可以由硬件发出,也可以由软件发出。

通过指令int num即可产生中断号为num的中断。软件发出的中断最常见的例子就是实现系统调用。

IDT简介

IDT DESCRIPTOR

和GDT一样,我们需要一个结构来告诉CPU,IDT的位置及其大小:

字段大小(bytes)说明
limit0~1描述了IDT的大小(所占的字节数-1)
base2~5IDT开始的32位线性地址

和GDT不一样的是

  1. IDT中的第一个(index为0)的表项会被使用
  2. 256个表项描述了对应的256种不同的中断
  3. Limit字段可以(!=256)*8 - 1,即IDT表项的数量可以不为256.但是超过的的部分将被系统忽略,而如果对应的中断在触发时没有对应的IDT表项则将抛出GPT(General Protection Fault,通用保护错误)。

IDT 表项

IDT的表项相对GDT简单,从下述数据结构就可以很清晰的知道其组成:

struct IDTDescr{
   uint16_t offset_1; // offset 的低16位
   uint16_t selector; // 对应GDT(或LDT)中的一个代码段的选择子
   uint8_t zero;      // 未使用,设为0
   uint8_t type_attr; // 类型和属性
   uint16_t offset_2; // offset 的高16位
};

其中字段type_attr描述了中断的种类以及权限等信息,在此不做展开。我们将设置我么的所有中断为32bit的ring0特权级下的中断门,对应的type_attr值为0x8e.

编写中断处理程序ISR

发生中断时,CPU会根据中断号从IDT中加载对应的中断处理程序ISR(Interrupt Service Routine)。而中断发生前的状态的保存于中断处理完之后状态的恢复,需要由硬件和操作系统相互配合来完成。

为什么要为每个中断实现不同的中断处理程序?

为什么不能实现一个统一的中断处理程序,所有的IDT表项都指向它,由它根据具体的中断号而确定相应的操作?

这是因为x86架构在设计时,CPU会根据中断号从IDT中寻找处理程序,而这个中断号并不会以某种参数的形式传递给相应的中断处理程序。因此,我需要为每个中断实现特有的中断处理程序。如有以下中断程序程序和中断号:

中断号中断处理程序
0isr0()
1isr1()
255isr255()

因此,当isr0被调用时,我们的系统就知道发生了中断0;当isr255被调用时,发生了中断255。
但是如果完完全全为每一个中断实现一个不同的中断处理器,在设计上显得有些蹩脚,同时会有大量重复的代码。因此我们可以将所有中断处理程序中相同的代码抽取出来,作为一个独立的部分。所有的中断处理程序在完成特定的处理之后(即将中断号、错误号压栈以后)调用这个共同的代码逻辑而完成对中断的相同的操作(保存中断发生前的状态)。

有些中断没有错误号?

为了使所有的中断都能使用一致的数据结构,我们可以给没有错误号的中断,增加一个哑错误号(Dummy Error Code)

需要保存哪些数据?

CPU在发生中断时,硬件会自动地为我们保存(压栈)eip,ec,eflags,esp,ss寄存器的内容。而软件(操作系统)需要

  1. 保存中断号,以及错误号

这样我们的中断处理程序才知道具体发生了什么中断

  1. 通用寄存器

中断处理程序也是程序,也会使用到通用寄存器,为了能在中断处理完成之后,我们必须能恢复这些通用寄存器的值。

  1. 中断前数据段选择子

硬件只为我们保存了代码段选择子(CS寄存器),我们需要自己保存中断前数据段的选择子。

综上,我们需要保存的数据可以由如下数据结构表示:

typedef struct {
    uint32_t ds; //Saved data segment selector
    uint32_t edi, esi, ebp, useless_esp, ebx, edx, ecx, eax; // Pushed by pusha.
    uint32_t int_no, err_code;    // Interrupt number and error code 
    uint32_t eip, cs, eflags, esp, ss; // Pushed by the processor automatically.
} registers_t;

实现中断处理程序

笔者采用两段式中断处理程序,即汇编+C。

汇编代码部分:

.macro ISR_WITH_ERROR_CODE num
    .global isr\num
    .type isr\num, @function
    isr\num:
        cli #clear interrupt

        pushl $\num
        jmp isr_comman_stub
.endm
.macro ISR_NO_ERROR_CODE num
    .global isr\num
    .type isr\num, @function
    isr\num:
        cli #clear interrupt

        pushl $0
        pushl $\num
        jmp isr_comman_stub
.endm
ISR_NO_ERROR_CODE 0
ISR_NO_ERROR_CODE 1
ISR_NO_ERROR_CODE 2
...
isr_comman_stub:
    pusha  #Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax

    movw %ds, %eax
    pushl %eax  #Save ds

    mov $0x10, %ax     #Load kernel data segment descriptor
    mov %ax, %ds
    mov %ax, %es
    mov %ax, %fs
    mov %ax, %gs

    pushl %esp #Pointer to struct registers_t as argument

    call isr_dispatcher  

    popl %esp

    popl %eax   # Restore data segment
    mov %ax, %ds
    mov %ax, %es
    mov %ax, %fs
    mov %ax, %gs

    popa

    addl $8, %esp   #Cleans up the pushed error code and pushed ISR number
    iret    #pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP. **EFLAGS contais wheter we should set interrupt

C代码示例:

//Dispath an interrupt to a i_handler
void isr_dispatcher(registers_t *regs);

//An interrupt handler. It is a pointer to a function which takes a pointer 
//to a structure containing register values.
typedef void (*i_handler)(registers_t *);

//Allows us to register an interrupt handler.
void register_i_handler(int num, i_handler h);

运行效果

测试C代码:

void kernel_main(void)
{
    printf("Hello, kernel World!\n");
    printf("Sending int 3...\n");
    asm volatile ("int $0x3");
    printf("Sending int 4...\n");
    asm volatile ("int $0x4");
}

运行截图

《【自制操作系统】(五)处理中断》

参考文献

  1. http://wiki.osdev.org/Interrupts
  2. http://wiki.osdev.org/Interrupt_Service_Routines
  3. https://en.wikipedia.org/wiki/Programmable_Interrupt_Controller?oldformat=true
  4. http://wiki.osdev.org/PIC
  5. http://wiki.osdev.org/APIC
  6. http://wiki.osdev.org/PS2_Keyboard
  7. http://www.jamesmolloy.co.uk/tutorial_html/4.-The%20GDT%20and%20IDT.html
  8. http://wiki.osdev.org/IDT
    原文作者:VicX
    原文地址: https://www.jianshu.com/p/81063671792e
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞