打印一个字符,中断似乎和函数类似?

image-20220607100424625

中断的概念和意义

  • 概念:正在执行任务时,出现某个请求暂停当前任务,转而处理这个请求(类似函数调用)处理结束后继续任务的执行
  • 意义:中断是提高系统整体性能的必要方式

中断与外设

  • 中断是一种处理器与外设进行通信的机制
  • 用于“通知”处理器外部有“重要事件”发生
  • 一般情况下,中断需要被处理器响应

操作系统的本质是中断驱动的死循环!
从摁下开机键一直到任务完成,一直死循环等待中断,OS kernel就用于处理这些中断。
比如摁下‘F’键,操作系统有关于键盘按键的处理方式,操作系统会用相应处理方式处理键盘中断

中断的分类:

  • 外部中断:包括不可屏蔽中断(内存读写错误,总线校验错误,电源掉电),可屏蔽中断(外部设备键盘网卡硬盘等等来数据了)。
  • 内部中断:包括软中断(程序自身执行的时候发出的中断请求,比如汇编int 10h,看起来像是函数调用)和异常(程序执行的时候有指令错误,比如除法操作中除数是0)

中断处理

中断服务程序(Interrupt Service Routine):对于处理器而言, 处理中断的方式就是执行一段事先写好的代码, 这段代码叫做中断服务程序(由操作系统内核提供)

Linux处理中断的方式

为了缩短处理器对中断的响应时间, 可以把中断分为:

  • 中断上半部 (ISR)
    中断应答或硬件复位等重要紧迫的工作
    实时性要求高,不可被打断
  • 中断下半部
    相对耗时的数据处理工作, 后续调度执行 (通过软中断来执行中断服务程序或者通过触发新任务的方式)

image-20220607105224691

中断与对应的服务程序间如何建立关联?
在代码层面如何进行转移?

实模式下的中断处理

  • 使用中断向量表映射不同中断与中断服务程序
  • 中断向量表 (Interrupt Vector Table)(指针数组)
    起始于物理地址0, 长度为 1 KB
    每个单元4 字节, 连续 256 个单元
    每个单元存放一个中断服务程序的入口地址

处理器接收到中断信号时, 能够询问到中断向量(中断类型号,其实就是一个整数下标去访问一维数组);进而,通过中断向量查找 IVT(一维指针数组), 获取 ISR 入口地址(中断服务程序就可以开始执行了); 之后跳转执行(类似于函数调用)

image-20220607113624249

实模式下的中断向量表 (IVT) 和中断服务程序 (ISR) 需要操作系统内核来建立吗?

计算机上电,处理器直接在实模式执行,这个时候硬件需要做一些特殊工作:
将主板ROM中的code(BIOS)拷贝到内存当中,将CS:IP寄存器的值设置为BIOS入口地址
BIOS扫描各个存储介质,会建立中断向量表,将主引导区中的主引导程序载入内存0x7c00并且会交出控制权(jmp 0x7c00)

保护模式下的中断处理

使用中断描述符表(IDT)映射不同中断与中断服务程序

  • 中断描述符表(Interrupt Descriptor Table)
  • 中断描述符表可以包含中断门, 陷阱门, 任务门(之前说的调用门和这几个没太大关系,只是结构比较相似)
    门描述符
    包含中断服务程序的入口(选择子 : 偏移)
    包含各种用于合法性检查的属性(如: 特权级)

中断描述符

image-20220607111205200

Note:调用门,中断门,陷阱门的字段布局完全相同;当 Type 表示中断门 (1110) 和陷阱门 (1111) 时, Param 字段未使用。

中断描述符表 (IDT)

  • 中断描述表是中断描述符的线性集合 (类似 GDT)
  • 每个元素的大小为 8 字节64bits(即: 中断描述符)
  • 使用前将起始地址及界限载入 IDTR 中 (专用指定: lidt)
1
2
3
4
5
6
7
8
9
10
11
12
[section .idt]
align 32
[bits 32]
LABEL_LDT:
;门 目标选择子 偏移 参数个数 属性
%rep 255
GATE SelectorCode32, ISR_Entry, 0, DA_386IGate
%endrep

IdtLen equ $ - LABEL_LDT
IdtPtr dw IdtLen - 1
dd 0

保护模式下中断处理寻址

  • 每个外中断产生一个中断向量 (由硬件发送的中断类型号)
  • 通过中断向量在 IDT 中查找对应的中断描述符
  • 通过中断描述符中的选择子偏移可找到ISR的入口地址

image-20220607111841639

注意:

  • IDT 除了提供ISR入口地址(偏移地址),还提供了特权级等属性
  • 中断发生后,转移执行ISR 代码前需要进行特权级检查
  • 实模式与保护模式的中断向量完全一致 (硬件不变)
  • IDT 中必须提供每种中断所对应的 ISR 入口地址

中断代理-8259A

不同外设如何向处理器发送中断信号?
当多个外设同时产生中断时,如何进行处理?

想象中的连接方式

image-20220607151722853

处理器有多少INTR引脚?能接入多少外设? 处理器有必要与外设直接相连吗?

超高速的处理器和超低速的外设相连合适吗?肯定是不合适的,因此可以引入一个中断代理

image-20220607152030063

8259A构造

8259A 是处理器的中断功能模块, 用于管理和裁决外部设备的中断请求

8259A 是专为处理器设计的中断管理芯片

  • 可通过编程对 8259A 进行功能配置
  • 屏蔽外设中断, 对中断进行优先级判决
  • 向处理器提供中断向量

对 8259A 的编程控制是操作系统内核的重要工作

image-20220607152341289

  • INT:选出优先级最高的中断请求后, 发信号通知 CPU
  • INTA:中断响应信号, 接收来自 CPU 的 INTA 接口的中断响应信号
  • PR:优先级仲裁器, 当多个中断同时发生时, 找出优先级最高的中断
  • IMR: 中断屏蔽寄存器, 用来屏蔽某个外设的中断。IMR的位与引脚一一对应,被设置为1的位对应的引脚被屏蔽了,被设置为0的位,其对应引脚的中断被放行。
  • ISR:中断服务寄存器, 当某个中断正在被处理时, 保存庄此寄存器中。ISR中的位与引脚一一对应,被设置为1的位对应的引脚表示中断正在被处理
  • IRR:中断请求寄存器, 用来接受经过 IMR 寄存器过滤后的中断信号并锁存,此寄存器中全是等待处理的中断。IRR中的位与引脚一一对应,被设置为1的位对应的引脚有中断请求,类似于一个请求队列

image-20220607153922885

中断触发方式
边沿触发(推荐):中断引脚电平变化的一瞬间认为中断申请到来(上升沿触发)
电平触发:中断引脚上的信号保持稳定电平一定时间后认为中断申请到来

8259A工作方式

数据连接方式

  • 非缓冲方式:将8259A 直接与数据总线相连
  • 缓冲方式:将8259A 通过总线驱动器和数据总线相连

中断优先的方式

  • 固定优先级方式:优先级由高到低的顺序是: IRO,IR1,IR2, …, IR7(存在饥饿现象)
  • 自动循环方式:某一中断请求被响应后, 该中断原优先级自动成为最低
  • 特殊循环方式:
    通过编程指定某中断源优先级成为最低
    其它中断源优先级自动改变

中断嵌套方式

  • 完全嵌套方式(默认方式):执行中断服务程序期间, 不响应本级中断和较低级中断
  • 特殊完全嵌套方式:执行中断服务程序期间, 可响应本级中断, 不响应较低级中断(在多片级联的情况下,当某从片的中断得到响应、进入中断服务期间,来自该从片的更高级的中断请求仍能为主8259A所识别(对主8259A来说,同一从8259A的8个中断都是一个级别),并向CPU提出请求。)

中断屏蔽方式

  • 普通屏蔽方式: IMR中的某一位或几位置为 1, 屏蔽掉相应级别的中断请求
  • 特殊屏蔽方式:未被屏蔽的中断源均可在某个中断服务程序中被响应 即低优先级中断可以打断正在服务的高优先级中断

中断结束方式

  • 自动结束方式(只适用节非多重中断情况,任意中断之间都能相互打断):8259A自动清除 ISR中已置位的优先级最高的位
  • 手动结束方式(在中断服务程序里面像8259A发EOI命令,这样不存在低优先级中断打断高优先级中断):在中断服务程序的最后,向8259A 发中断结束命令,将 ISR中相应的位清除,表明中断服务程序已完成

8259A控制编程

一般而言,x86系统中使用2个8259A级联作为中断代理

image-20220607161052213

初始化命令字 (Initialization Command Word)

用于确定是否需要级联, 设置起始中断向量, 等

  • ICW1: 初始化 8259A连接方式和中断触发方式 (如:设置主从级联)
  • ICW2: 设置起始中断向量(给外部设备编号,IRQ0对应的中断向量)
  • ICW3: 指定主从 8259A 的级联引脚(如:从片连接到主片 IRQ2)
  • ICW4: 设置 8259A 的工作模式 (中断嵌套方式)

ICW1: 初始化 8259A 连接方式和中断触发方式

第2,5,6,7位固定为0,第4位标记为1这是ICW1的固定标记

image-20220607212043010

ICW1需要写入主片的0x20端口和从片的0xA0端口

ICW2: 设置起始中断向量(IR0 对应的中断向量)

0-19(0x0-0x13)非屏蔽中断和异常,计:20个,Intel保留,不分配IRQ

必须设置高5位的值为中断向量,低三位固定为0

image-20220607212321017

ICW2需要写入主片的0x21端口和从片的0xA1端口

ICW3: 指定主从 8259A 的级联引脚

image-20220607212359167

ICW3需要写入主片的0x21端口和从片的0xA1端口

ICW4: 初始化 8259A 数据连接方式和中断触发方式

image-20220607212526631

ICW4需要写入主片的0x21端口和从片的0xA1端口

实战初始化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
Init8259A:
; 初始化主片
; 1) 先写 ICW1
mov al, 0x11 ; IC4 = 1, ICW4-write required
; mov al, 00010001 ; 边沿触发+级联
out MASTER_ICW1_PORT, al
call delay ; 延迟一段时间让写入的东西生效

; 2) 接着写 ICW2
mov al, 0x20 ; interrupt vector = 0x20
; mov al, 00100000 ; 起始中断向量为0x20
out MASTER_ICW2_PORT, al
call delay

; 3) 接着写 ICW3
mov al, 0x04 ; ICW3[2] = 1, for slave connection
; mov al, 00000100 ; 主从 8259A 的级联引脚是(从第0位开始算)下标为2,
out MASTER_ICW3_PORT, al
call delay

; 4) 接着写 ICW4
mov al, 0x01 ; ICW4[0] = 1, for Intel Architecture
; mov al, 0x00000001 ; 手动结束中断+非缓冲模式+全嵌套模式
out MASTER_ICW4_PORT, al
call delay

; 初始化从片
; 1) 先写 ICW1
mov al, 0x11 ; IC4 = 1, ICW4-write required
out SLAVE_ICW1_PORT, al ; 边沿触发+级联
call delay

; 2) 接着写 ICW2
mov al, 0x28 ; interrupt vector = 0x28
; 外部设备编号主片从0x20~0x27这8个引脚,从片的外部设备编号需要从0x28开始
out SLAVE_ICW2_PORT, al
call delay

; 3) 接着写 ICW3
mov al, 0x02 ; ICW3[1] = 1, connect to master IR2
out SLAVE_ICW3_PORT, al ; 需要连接到主片的下标为2的引脚
call delay

; 4) 接着写 ICW4
mov al, 0x01 ; for Intel Architecture
out SLAVE_ICW4_PORT, al ; 手动结束中断+非缓冲模式+全嵌套模式
call delay

ret

操作命令字

用于设置中断优先级方式,中断结束模式,

  • OCW1: 屏蔽连接在 8259A 上的中断源
  • OCW2: 设置中断结束方式和优先级模式
  • OCW3: 设置特殊屏蔽方式

OCW1 命令字最终写入 IMR 寄存器

  • IMR 寄存器为初级中断屏蔽寄存器 (分开关)
  • 如果标志寄存器中的 IF 位为 0, 则屏麵有外部中断(总开关)

注: OCW1 需要写入主片的 0x21 端口和从片的 0xA1 端口

OCW2: 设置中断结束方式和优先级模式

image-20220607215551467

OCW3: 设置特殊屏蔽方式及查询方式

image-20220607220012005

注: OCW3 需要写入主片的 0x20 端口和从片的 OxA0 端口

实战代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
;--------------------------
; write_EOI:
;--------------------------
write_master_EOI:
mov al, 00100000B ; OCW2 select, EOI
out MASTER_OCW2_PORT, al ; 主片
ret

write_slave_EOI:
mov al, 00100000B
out SLAVE_OCW2_PORT, al ; 从片
ret

;----------------------------
; read_isr:
;----------------------------
read_master_isr:
mov al, 00001011B ; OCW3 select, read ISR
out MASTER_OCW3_PORT, al
jmp $+2
in al, MASTER_OCW3_PORT
ret

read_slave_isr:
mov al, 00001011B ; 关闭特殊屏蔽
out SLAVE_OCW3_PORT, al
jmp $+2
in al, SLAVE_OCW3_PORT
ret

;-----------------------------
; read_irr:
;-----------------------------
read_master_irr:
mov al, 00001010B ; OCW3 select, read IRR
out MASTER_OCW3_PORT, al
jmp $+2
in al, MASTER_OCW3_PORT
ret

read_slave_irr:
mov al, 00001010B
out SLAVE_OCW3_PORT, al
jmp $+2
in al, SLAVE_OCW3_PORT
ret

;-----------------------------
; read_imr:
;-----------------------------
read_master_imr:
in al, MASTER_IMR_PORT
ret

read_slave_imr:
in al, SLAVE_IMR_PORT
ret

;------------------------------
; send_smm_command
;------------------------------
send_smm_command:
mov al, 01101000B ; SMM=ESMM=1, OCW3 select
out MASTER_OCW3_PORT, al
ret

中断编程实践

  • 预备工作:8259A 初始化, 读写 IMR 寄存器, 发送 EOI 控制字, 等
  • 实践一:自定义软中断的实现(内部中断处理)
  • 实践二:时钟中断的响应及处理 (外部中断处理)

8359A初始化

将控制字发送到主从片的那些端口提前预定义好

image-20220610211446155

读写中断屏蔽寄存器IMR的值

需要借助OCW1操作命令字(设置IMR的值对应的位设置为1即可),写入对应端口0x21(主片端口)或者0xA1(从片端口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
; al --> IMR register value
; dx --> 8259A port 0x21或0xA1
WriteIMR:
out dx, al
call Delay
ret

; dx --> 8259A
; return:
; ax --> IMR register value
ReadIMR:
in ax, dx
call Delay
ret

主函数数中设置全1没有任何屏蔽的中断

1
2
3
4
5
6
7
mov ax, 0xFF
mov dx, MASTER_IMR_PORT
call WriteIMR

mov ax, 0xFF
mov dx, SLAVE_IMR_PORT
call WriteIMR

汇编指令rep

  • 汇编语言中支持预处理语句 (如: %include)
  • 与 C 语言中的情况类似, 汇编预处理语句常用于文本替换
  • 示例: 语句重复 (%rep)
1
2
3
4
5
6
7
8
9
10
11
12
13
Delay:
%rep 5
nop
%endrep
ret
; 等价于
Delay:
nop
nop
nop
nop
nop
ret

image-20220610221054474

x86 处理器一共支持 256 个中断类型, 因此中断描述符表中需要有 256 个描述符与之对应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[section .idt]
align 32
[bits 32]
IDT_ENTRY:
; IDT definition
; Selector, Offset, DCount, Attribute
%rep 32
Gate Code32Selector, DefaultHandler, 0, DA_386IGate
%endrep

Int0x20 : Gate Code32Selector, TimerHandler, 0, DA_386IGate

%rep 95
Gate Code32Selector, DefaultHandler, 0, DA_386IGate
%endrep

Int0x80 : Gate Code32Selector, Int0x80Handler, 0, DA_386IGate

%rep 127
Gate Code32Selector, DefaultHandler, 0, DA_386IGate
%endrep

IdtLen equ $ - IDT_ENTRY

IdtPtr:
dw IdtLen - 1
dd 0

; end of [section .idt]

Init8259A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Init8259A:
push ax

; master
; ICW1
mov al, 00010001B
out MASTER_ICW1_PORT, al
call Delay

; ICW2
mov al, 0x20
out MASTER_ICW2_PORT, al
call Delay

; ICW3
mov al, 00000100B
out MASTER_ICW3_PORT, al
call Delay

; ICW4
mov al, 00010001B ;特殊完全中断,响应同级中断请求
out MASTER_ICW4_PORT, al
call Delay

; slave
; ICW1
mov al, 00010001B
out SLAVE_ICW1_PORT, al
call Delay

; ICW2
mov al, 0x28
out SLAVE_ICW2_PORT, al
call Delay

; ICW3
mov al, 00000010B
out SLAVE_ICW3_PORT, al
call Delay

; ICW4
mov al, 00000001B
out SLAVE_ICW4_PORT, al
call Delay

pop ax

ret

自定义保护模式软中断

实现还是很简单的,只需要调用已有函数PrintString,注意返回使用iret

1
2
3
4
5
6
7
8
9
10
DefaultHandleFunc:
iret

DefaultHandle equ DefaultHandleFunc - $$

Int0x80HandleFunc:
call PrintString
iret

Int0x80Handle equ Int0x80HandleFunc - $$

主函数中

1
2
3
4
5
6
mov ebp, INT_80H_OFFSET			; 目标字符串
mov bx, 0x0C ; 打印属性
mov dh, 13 ; 位置,行
mov dl, 32 ; 位置,列

int 0x80

image-20220610222736056

处理外部时钟中断

外部时钟中断无需过于频繁,一般取20mS(50Hz)即可。

image-20220610222854909

由于 8259A 初始化为手动结束中断的方式,因此,外部中断服务程序中需要手动发送结束控制字。

这里借助OCW2操作命令,OCW2: 手动清除 ISR中优先级最高的位, 各引脚优先级固定。

先前设置了IR0中断向量为0x20,而时钟中断会向IRQ0设置发送信号,因此这里向IRQ0发送信号,需要在IDT中注册20号中断描述符

1
2
3
4
5
6
7
8
9
10
11
; dx --> 8259A port
WriteEOI:
push ax

mov al, 0x20
out dx, al
call Delay

pop ax

ret

打开时钟中断(IRQ0),外部可屏蔽中断的发生受到两个因素的影响,只有当IF位为1,并且IMR相应位为0时才会发生。那么,如果我们想打开时钟中断的话,一方面不仅要设计一个中断处理程序,另一方面还要设置IMR,并且设置IF位。

主函数中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    sti                             ; 打开外部中断总开关

call EnableTimer

jmp $
; ...
; ...
; ...
EnableTimer: ; 打开屏蔽位触发时钟中断
push ax
push dx

mov ah, 0x0C
mov al, '0'
mov [gs:((80 * 14 + 36) * 2)], ax

mov dx, MASTER_IMR_PORT

call ReadIMR

and ax, 0xFE ; 第0位设置为0

call WriteIMR ; 写回IMR,不屏蔽0

pop dx
pop ax

ret

中断处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
TimerHandlerFunc:
push ax
push dx

mov ax, [gs:((80 * 14 + 36) * 2)] ;取出14行36列字符

cmp al, '9'
je throtate ;如果是'9',修改为'0'
inc al ; +1之后再放回去
jmp thshow

throtate:
mov al, '0'

thshow:
mov [gs:((80 * 14 + 36) * 2)], ax

mov dx, MASTER_OCW2_PORT
call WriteEOI ;OCW2手动清除ISR优先级最高位,以便继续响应下一个中断

pop dx
pop ax

iret

image-20220610225636165

0~9一直变化闪烁,时钟中断还是成功了。

注意ICW4设置mov al, 00010001B特殊完全中断,意味着会去响应同级中断请求。

假设: 时钟中断请求周期为 5ms , 对应的中断服务程序执行时间为 10ms; 那么, 中断服务程序是否会被新的时钟中断请求打断?

  • 中断优先级由 8259A 管理 (高优先级中断请求优先送往处理器),新的时钟中断请求当然也会送给处理器,但处理器不一定处理
  • 处理器决定是否响应中断请求,依据于总开关是否打开 (处理器没有中断优先级的概念)
  • 在默认情况下,中断服务程序执行时,即便来了新的外部中断,处理器会屏蔽外部中断请求 (IF == 0),直到中断服务程序返回结束后,重新响应外部中断 (总开关打开即IF == 1)

因此,如果希望高优先级中断请求打断当前中断服务程序, 可以在中断服务程序中打开 IF , 即: 将 IF 设置为 1 ( sti )。

中断处理与特权级处理

中断特权级转移过程

  1. 处理器通过中断向量找到对应的中断描述符
  2. 特权级检查:
    软中断:(目标代码段 DPL <= CPL)&&(CPL <= 中断描述符 DPL 即当前特权级>=中断描述符特权级),中断门类似于“蹦床”
    外部中断: CPL >=目标代码段 DPL
  3. 加载目标代码段选择子到 cs,加载偏移地址到ip

中断中栈变化

压栈

image-20220617161524360

  • 首先用户态执行操作,影响的仅仅是用户态下相应的栈Stack_DPL3
  • 然后int 0x80会调用中断,先去查找是否注册了TSS全局段描述符,如果注册了从TSS中取出esp和ss0的值(内核栈信息),sp会转移到高特权级的栈Stack_DPL0,同时Stack_DPL0(注意是内核栈)压入ss,esp,eflag,cs,ip等寄存器用于执行完中断服务程序后iret返回
  • 然后cs和ip寄存器修改为中断服务程序对应的段和段内偏移地址,就可以开始执行中断服务程序直到遇到iret返回

中断服务程序返回iret

  • iret 使得处理器从内核态返回用户态
  • 返回时进行特权级检查
    • CPL <= 目标代码段 DPL (高特权级–>低特权级)
    • 对相关段寄存器强制清零 (指向高特权级数据的段寄存器)

栈恢复

image-20220617162131009

恢复之后,ss和esp指向原先的栈,eflags标志寄存器恢复,eflags寄存器是个标志寄存器,标志寄存器每一位都是一个状态标志位。

所谓的中断的上下文保存下来中的上下文就是将这些寄存器ss,esp,eflags,cs,ip压入栈中保存下来

eflags标志寄存器

image-20220617162556086

  • IF: 系统标志位, 决定是否响应外部中断
    IF == 1,响应外部中断
    IF == 0,屏蔽外部中断
  • IOPL: 系统标志位, 决定是否允许进行I/O操作
    CPL <= IOPL 才能允许访问I/O 端口
    当且仅当 CPL == 0 时才能改变 IOPL的值

x86汇编语句并没有提供直接修改eflags寄存器的值的直接语句。只能借助pushf,popf指令间接改变。

1
2
3
4
5
6
7
pushf				;eflags值压入栈中
pop eax ;eflags弹出保存到eax

or eax, 0x3000 ;eax 中的第12, 13位置1

push eax ; eax 的值压入栈中
popf ;将 eflags 中的 IOPL 设置为 3

使用软中断实现系统调用

  • 定义 32 位核心代码段(包括一些初始化操作中断函数, 系统函数)
  • 定义 32 位用户代码段和数据段(用户程序)
  • 通过软中断 (int 0x80) 转移到内核态调用系统函数(低—>高)
  • 在任务代码段使用软中断 (int 0x80) 实现功能函数

image-20220617163716059

注意事项:

  • 将 IOPL 设置为 3 使得用户态和内核态均可访问10 端口
  • 特权级转移时会发生栈的变换(定义 TSS 结构, 定义不同栈段)
  • 在用户态通过 sti 指令打开总开关使得处理器响应外部中断 (必须用户态)

0x80中断自行设计(系统调用设计)一个中断门调用多个系统函数

  • ax == 0: 外部设备中断初始化 (InitDevInt)
  • ax == 1: 字符串打印 (Printf)
  • ax == 2: 启动时钟中断 (EnableTimer)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
%include "inc.asm"

org 0x9000

jmp ENTRY_SEGMENT

[section .gdt]
; GDT definition
; 段基址, 段界限, 段属性
GDT_ENTRY : Descriptor 0, 0, 0
CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_C + DA_32 + DA_DPL3
VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL3
DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_DRW + DA_32 + DA_DPL3
STACK32U_DESC : Descriptor 0, TopOfStack32U, DA_DRW + DA_32 + DA_DPL3
STACK32K_DESC : Descriptor 0, TopOfStack32K, DA_DRW + DA_32 + DA_DPL0
TSS_DESC : Descriptor 0, TSSLen - 1, DA_386TSS + DA_DPL0
KERNEL32_DESC : Descriptor 0, Kernel32SegLen - 1, DA_C + DA_32 + DA_DPL0
; GDT end

GdtLen equ $ - GDT_ENTRY

GdtPtr:
dw GdtLen - 1
dd 0

; GDT Selector

Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL3
VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL3
Data32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL3
Stack32USelector equ (0x0004 << 3) + SA_TIG + SA_RPL3
Stack32KSelector equ (0x0005 << 3) + SA_TIG + SA_RPL0
TSSSelector equ (0x0006 << 3) + SA_TIG + SA_RPL0
Kernel32Selector equ (0x0007 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]

[section .idt]
align 32
[bits 32]
IDT_ENTRY:
; IDT definition
; Selector, Offset, DCount, Attribute
%rep 32
Gate Kernel32Selector, DefaultHandler, 0, DA_386IGate + DA_DPL3
%endrep

Int0x20 : Gate Kernel32Selector, TimerHandler, 0, DA_386IGate + DA_DPL3

%rep 95
Gate Kernel32Selector, DefaultHandler, 0, DA_386IGate + DA_DPL3
%endrep

Int0x80 : Gate Kernel32Selector, Int0x80Handler, 0, DA_386IGate + DA_DPL3

%rep 127
Gate Kernel32Selector, DefaultHandler, 0, DA_386IGate + DA_DPL3
%endrep

IdtLen equ $ - IDT_ENTRY

IdtPtr:
dw IdtLen - 1
dd 0

; end of [section .idt]

TopOfStack16 equ 0x7c00

[section .tss]
[bits 32]
TSS_SEGMENT:
dd 0
dd TopOfStack32K ; 0
dd Stack32KSelector ;
dd 0 ; 1
dd 0 ;
dd 0 ; 2
dd 0 ;
times 4 * 18 dd 0
dw 0
dw $ - TSS_SEGMENT + 2
db 0xFF

TSSLen equ $ - TSS_SEGMENT

[section .dat]
[bits 32]
DATA32_SEGMENT:
DTOS db "D.T.OS!", 0
DTOS_OFFSET equ DTOS - $$
INT_80H db "int 0x80", 0
INT_80H_OFFSET equ INT_80H - $$

Data32SegLen equ $ - DATA32_SEGMENT

[section .s16]
[bits 16]
ENTRY_SEGMENT:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, TopOfStack16

; initialize GDT for 32 bits code segment
mov esi, CODE32_SEGMENT
mov edi, CODE32_DESC

call InitDescItem

mov esi, DATA32_SEGMENT
mov edi, DATA32_DESC

call InitDescItem

mov esi, STACK32U_SEGMENT
mov edi, STACK32U_DESC

call InitDescItem

mov esi, STACK32K_SEGMENT
mov edi, STACK32K_DESC

call InitDescItem

mov esi, TSS_SEGMENT
mov edi, TSS_DESC

call InitDescItem

mov esi, KERNEL32_SEGMENT
mov edi, KERNEL32_DESC

call InitDescItem

; initialize GDT pointer struct
mov eax, 0
mov ax, ds
shl eax, 4
add eax, GDT_ENTRY
mov dword [GdtPtr + 2], eax

; initialize IDT pointer struct
mov eax, 0
mov ax, ds
shl eax, 4
add eax, IDT_ENTRY
mov dword [IdtPtr + 2], eax

; 1. load GDT
lgdt [GdtPtr]

; 2. close interrupt
; load IDT
; set IOPL to 3
cli

lidt [IdtPtr]

pushf
pop eax

or eax, 0x3000

push eax
popf

; 3. open A20
in al, 0x92
or al, 00000010b
out 0x92, al

; 4. enter protect mode
mov eax, cr0
or eax, 0x01
mov cr0, eax

; 5. load TSS
mov ax, TSSSelector
ltr ax

; 6. jump to 32 bits code
; jmp dword Code32Selector : 0
push Stack32USelector
push TopOfStack32U
push Code32Selector
push 0
retf


; esi --> code segment label
; edi --> descriptor label
InitDescItem:
push eax

mov eax, 0
mov ax, cs
shl eax, 4
add eax, esi
mov word [edi + 2], ax
shr eax, 16
mov byte [edi + 4], al
mov byte [edi + 7], ah

pop eax

ret


[section .s32]
[bits 32]
CODE32_SEGMENT:
mov ax, VideoSelector
mov gs, ax

mov ax, Stack32USelector
mov ss, ax

mov eax, TopOfStack32U
mov esp, eax

mov ax, Data32Selector
mov ds, ax


mov ebp, DTOS_OFFSET
mov dh, 12
mov dl, 33

call Printf

call InitDevInt

call EnableTimer

jmp $

;
;
InitDevInt:
push ax

mov ax, 0

int 0x80

sti

pop ax
ret

; ds:ebp --> string address
; dx --> dh : row, dl : col
Printf:
push ax
push bx

mov ax, 1
mov bx, 0x0C

int 0x80

pop bx
pop ax
ret

;
;
EnableTimer:
push ax

mov ax, 2

int 0x80

pop ax
ret

Code32SegLen equ $ - CODE32_SEGMENT

[section .knl]
[bits 32]
KERNEL32_SEGMENT:
;
;
DefaultHandlerFunc:
iret

DefaultHandler equ DefaultHandlerFunc - $$

;
;
Int0x80HandlerFunc:
ax0:
cmp ax, 0
jnz ax1
call InitDevIntFunc
iret
ax1:
cmp ax, 1
jnz ax2
call PrintString
iret
ax2:
cmp ax, 2
jnz ax3
call EnableTimerFunc
iret
ax3:
iret

Int0x80Handler equ Int0x80HandlerFunc - $$

;
;
TimerHandlerFunc:
push ax
push dx

mov ax, [gs:((80 * 14 + 36) * 2)]

cmp al, '9'
je throtate
inc al
jmp thshow

throtate:
mov al, '0'

thshow:
mov [gs:((80 * 14 + 36) * 2)], ax

mov dx, MASTER_OCW2_PORT
call WriteEOI

pop dx
pop ax

iret

TimerHandler equ TimerHandlerFunc - $$

;
;
Delay:
%rep 5
nop
%endrep
ret

;
;
Init8259A:
push ax

; master
; ICW1
mov al, 00010001B
out MASTER_ICW1_PORT, al

call Delay

; ICW2
mov al, 0x20
out MASTER_ICW2_PORT, al

call Delay

; ICW3
mov al, 00000100B
out MASTER_ICW3_PORT, al

call Delay

; ICW4
mov al, 00010001B
out MASTER_ICW4_PORT, al

call Delay

; slave
; ICW1
mov al, 00010001B
out SLAVE_ICW1_PORT, al

call Delay

; ICW2
mov al, 0x28
out SLAVE_ICW2_PORT, al

call Delay

; ICW3
mov al, 00000010B
out SLAVE_ICW3_PORT, al

call Delay

; ICW4
mov al, 00000001B
out SLAVE_ICW4_PORT, al

call Delay

pop ax

ret

; al --> IMR register value
; dx --> 8259A port
WriteIMR:
out dx, al
call Delay
ret

; dx --> 8259A
; return:
; ax --> IMR register value
ReadIMR:
in ax, dx
call Delay
ret

;
; dx --> 8259A port
WriteEOI:
push ax

mov al, 0x20
out dx, al

call Delay

pop ax

ret

;
;
EnableTimerFunc:
push ax
push dx

mov ah, 0x0C
mov al, '0'
mov [gs:((80 * 14 + 36) * 2)], ax

mov dx, MASTER_IMR_PORT

call ReadIMR

and ax, 0xFE

call WriteIMR

pop dx
pop ax

ret

;
;
InitDevIntFunc:
push ax
push dx

call Init8259A

mov ax, 0xFF
mov dx, MASTER_IMR_PORT

call WriteIMR

mov ax, 0xFF
mov dx, SLAVE_IMR_PORT

call WriteIMR

pop dx
pop ax
ret

; ds:ebp --> string address
; bx --> attribute
; dx --> dh : row, dl : col
PrintString:
push ebp
push eax
push edi
push cx
push dx

print:
mov cl, [ds:ebp]
cmp cl, 0
je end
mov eax, 80
mul dh
add al, dl
shl eax, 1
mov edi, eax
mov ah, bl
mov al, cl
mov [gs:edi], ax
inc ebp
inc dl
jmp print

end:
pop dx
pop cx
pop edi
pop eax
pop ebp

ret

Kernel32SegLen equ $ - KERNEL32_SEGMENT

[section .gsu]
[bits 32]
STACK32U_SEGMENT:
times 1024 * 4 db 0

Stack32USegLen equ $ - STACK32U_SEGMENT
TopOfStack32U equ Stack32USegLen - 1

[section .gsk]
[bits 32]
STACK32K_SEGMENT:
times 1024 * 4 db 0

Stack32KSegLen equ $ - STACK32K_SEGMENT
TopOfStack32K equ Stack32KSegLen - 1