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

作者: VicX | 来源:发表于2016-04-30 15:30 被阅读511次

中断——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) 说明
limit 0~1 描述了IDT的大小(所占的字节数-1)
base 2~5 IDT开始的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中寻找处理程序,而这个中断号并不会以某种参数的形式传递给相应的中断处理程序。因此,我需要为每个中断实现特有的中断处理程序。如有以下中断程序程序和中断号:

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

因此,当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

相关文章

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

    中断——Interrupt what 中断指一个由CPU外部(或者内部)产生的信号,用以通知CPU发生了某种特定的...

  • Linux IO多路复用底层原理(刚接触,知识还不成体系,等懂得

    Linux 操作系统中断 什么是系统中断系统处理中断的过程: 首先由需要紧急处理的程序向处理器发送中断请求,处理器...

  • windows异常

    异常处理 异常和中断是由CPU触发的.操作系统怎么接收到异常的?IDT表, 操作系统在启动时,就会将中断处理的地址...

  • 每日记录之操作系统的中断

    操作系统的中断 计算机遇到一些更紧急的工作,中断当前的工作,去处理更紧急的工作 中断的类别 外部中断,内部中断,软...

  • (三)操作系统运行环境

    操作系统运行环境 CPU状态 中断/异常机制 操作系统运行机制 系统调用 处理器状态(模式) 中央处理器(CPU)...

  • 中断系统

    中断系统是计算机操作系统中响应和处理中断的系统,包括硬件子系统和软件子系统两部分中断系统响应由硬件子系统完成中断处...

  • 1-6.操作系统(Mid)

    一、导论 概念 操作系统极小化定义:内核才是操作系统中断:当出现需要时,CPU暂时停止当前进程的执行,转而执行处理...

  • 操作系统拾遗-中断与系统调用

    中断 概念 操作系统是中断驱动的,整个操作系统离不开中断机制,所谓的中断机制,宏观上讲就是cpu在执行某件事情时可...

  • 处理器调度

    想要资源吗?求我啊~ 调用处理器调度程序的前提是进入操作系统,处于系统态。而之前在中断中提到,中断是进程切换的前提...

  • 嵌入式中断处理的简单描述

    嵌入式中断处理的简单描述 ## 通用的中断处理过程: 中断源---》中断路径---》中断响应 中断由中断源发出,进...

网友评论

    本文标题:【自制操作系统】(五)处理中断

    本文链接:https://www.haomeiwen.com/subject/cwekrttx.html