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

中断——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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容