问题
8259A在中断特殊完全嵌套方式下,同一个引脚的新中断是否可以打断旧中断的处理?
问题剖析
假设:时钟请求周期为5ms,对应的中断服务程序执行时间为10ms;那么中断服务程序是否会被新的时钟请求打断?
我们初始化8259A时,将主片的中断方式设置为特殊完全嵌套方式,同一个引脚的新中断可以打断旧中断的处理。理应来说,处理器在处理时钟的中断服务程序时,会响应8259A新发送来的时钟请求,打断当前的执行,从而去执行新的时钟中断服务程序,但事实上却不是这样的。
关于中断优先级
中断优先级由8259A管理 (高优先级中断请求优先送往处理器)
处理器决定是否响应中断请求 (处理器没有中断优先级的概念)
在默认情况下
- 中断服务程序执行时,屏蔽外部中断请求 (IF = 0)
- 中断服务程序返回后,重新响应外部中断 (IF = 1)
我们接着上节课的程序,通过断点调试查看IF寄存器的值。
在时钟中断服务程序调用之前,IF为大写,说明处理器的中断总开关是打开的,可以接收外部中断。
在进入时钟中断服务程序时,if为小写,处理器的中断总开关是关闭的,说明了处理器在处理中断服务程序时,会自动把中断总开关关闭,不会响应外部中断。
中断嵌套
如果希望高优先级中断请求打断当前中断服务程序,可以在中断服务程序中打开IF,即:将IF设置为1 (sti)。
当代操作系统的设计
应用程序 (DPL3) 执行系统调用后会陷入内核态 (DPL0)
自定义软中断用于系统调用 (int 0x80)
通过软中断陷入内核态以最高特权级 (DPL0) 执行系统调用
中断服务程序运行于内核态 (DPL0)
与调用门类似
如果中断发生时涉及特权级转移,那么必须为不同特权级准备不同的栈
中断特权级转移过程
1. 处理器通过中断向量找到对应的中断描述符
2. 特权级检查:
- 软中断:(目标代码段DPL <= CPL) && (CPL <= 中断描述符DPL)
- 外部中断:CPL >= 目标代码段DPL
3. 加载目标代码段选择子到cs,加载偏移地址到ip
中断发生时的压栈
中断服务程序返回
iret使得处理器从内核态返回用户态
返回时进行特权级检查
- CPL <= 目标代码段DPL (高特权级 -> 低特权级)
- 对相关寄存器强制清零 (指向高特权级数据的段寄存器)
中断结束时的栈恢复
eflags标志寄存器
IF:系统标志位,决定是否响应外部中断
- IF ==1,响应外部中断
- IF == 0, 屏蔽外部中断
IOPL:系统标志位,决定是否进行IO操作
- CPL <= IOPL 才允许访问IO端口
- 当前仅当 CPL == 0 时才能改变 IOPL 的值
设置IOPL的方法
x86汇编语言没有提供指令改变 eflags 寄存器的值,因此,只能利用 pushf 和 popf 指令间接改变。
当前程序的 IOPL = 0,我们要将 IOPL 改为3。
pushf
pop eax
or eax, 0x3000
push eax
popf
我们加入上述代码后,改变了IOPL的值,IOPL = 3。
目标实验:使用软中断实现系统调用
- 定义32位核心代码段 (中断函数,系统函数)
- 定义32位任务代码段和数据段 (用户程序)
- 通过软中断 (int 0x80) 转移到内核态调用系统函数 (低 -> 高)
- 在任务代码段使用软中断 (int 0x80) 实现功能函数
目标实验
注意事项
将IOPL设置为3使得用户态和内核态均可访问IO端口
特权级转移时会发生栈的变化 (定义TSS结构,定义不同栈段)
在用户态通过sti指令使得处理器响应外部中断 (必须用户态)
0x80中断设计 (系统调用设计)
ax == 0:外部设备中断初始化 (InitDevInt)
ax == 1:字符串打印 (Printf)
ax == 2:启动时钟中断 (EnableTimer)
软中断实现系统调用
%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_32 + DA_C + DA_DPL3
VIDEO_DESC : Descriptor 0xb8000, 0x7fff, DA_32 + DA_DRWA + DA_DPL3
DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_32 + DA_DRW + DA_DPL3
STACK32U_DESC : Descriptor 0, TopOfStack32U, DA_32 + DA_DRW +DA_DPL3
STACK32K_DESC : Descriptor 0, TopOfStack32K, DA_32 + DA_DRW + DA_DPL0
TSS_DESC : Descriptor 0, TssLen - 1, DA_32 + DA_386TSS + DA_DPL0
KERNEL32_DESC : Descriptor 0, KernelSegLen - 1, DA_32 + DA_C + 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, Attribut
%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
; IDT end
IdtLen equ $ - IDT_ENTRY
IdtPtr:
dw IdtLen - 1
dd 0
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 - $$
HELLOWORLD db "Hello, World!", 0
HELLOWORLD_Offset equ HELLOWORLD - $$
INT_0X80 db "int 0x80", 0
INT_0X80_Offset equ INT_0X80 - $$
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
; change IOPL
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 labelBACK_ENTRY_SEGMENT
; 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, 13
mov dl, 33
call Printf
call InitDevInt
call EnableTimer
jmp $
InitDevInt:
push ax
sti
mov ax, 0
int 0x80
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 : ((15 * 80 + 32) * 2)]
cmp al, '9'
je throtate
inc al
jmp thshow
throtate:
mov al, '0'
thshow:
mov [gs : ((15 * 80 + 32) * 2)], ax
mov dx, MASTER_OCW2_PORT
call WriteEOI
pop ax
pop dx
iret
TimerHandler equ TimerHandlerFunc - $$
; ds:ebp --> string address
; bx --> attribute
; dx --> dh : row, dl : col
PrintString:
push ebp
push cx
push eax
push dx
push edi
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 edi
pop dx
pop eax
pop cx
pop ebp
ret
;
;
EnableTimerFunc:
push ax
push dx
mov ah, 0x0C
mov al, '0'
mov [gs : ((15 * 80 + 32) * 2)], ax
mov dx, MASTER_IMR_PORT
call ReadIMR
and ax, 0xFE
call WriteIMR
pop ax
pop dx
ret
;
;
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 port
; return:
; al --> IMR register value
ReadIMR:
in al, dx
call Delay
ret
;
; dx --> 8259A port
WriteEOI:
push ax
mov al, 0x20
out dx, al
call Delay
pop ax
ret
InitDevIntFunc:
push ax
push dx
call Init8259A
mov al, 0xFF
mov dx, MASTER_IMR_PORT
call WriteIMR
mov al, 0xFF
mov dx, SLAVE_IMR_PORT
call WriteIMR
pop dx
pop ax
ret
KernelSegLen 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
因为当前程序需要用户态和内核态之间的相互切换,所以我们需要定义DPL为0的内核态对应的栈段,DPL为3的用户态对应的栈段,并且需要定义TSS。
我们需要将中断描述符的DPL设置为3,这里中断门和调用门的作用类似,就像蹦床一样,将我们从用户态转移到内核态,这里的中断描述符的DPL应当 >= CPL,否则就无法从低特权级转移到高特权级去执行。
我们在任务代码段实现了3个系统调用,这3个系统调用都是通过0x80号中断来从用户态陷入内核态来执行系统函数。
内核代码段的0x80号中断是通过ax的值来执行相应的系统函数,我们可以拓展ax对应更多的系统函数,来为用户提供更多的系统调用。
注意:必须要在用户态通过sti指令使得处理器响应外部中断 。外部中断的特权级判断和中断描述符的DPL没有关系。