内核的中断处理(上)

问题

创建两个任务:Task A 和 Task B,并执行 Task A;那么,什么时候由谁进行任务切换执行?

问题分析

课程任务:打断任务的执行 (中断机制)

可使用时钟中断打断任务 (每个任务执行固定时间片)

中断发生后立即转而执行中断服务程序 (ISR)

在中断服务程序中完成任务上下文保存及任务切换

解决方案

解决方案的实现基础

  • 建立并加载中断描述符表 (IDT)
  • 编写时钟中断服务程序 (ISR)
  • 初始化8259A并开启时钟中断

内核的中断实现 (上)

 loader.asm


%include "blfunc.asm"
%include "common.asm"

org BaseOfLoader

interface:
    BaseOfStack     equ    BaseOfLoader	
	BaseOfTarget    equ    BaseOfKernel
	Target  db "KERNEL     "
    TarLen          equ   ($-Target)

[section .gdt]
; GDT definition
;                                     段基址,       段界限,       段属性
GDT_ENTRY		    : Descriptor		0, 			  0,		   0
CODE32_DESC		    : Descriptor		0, 	   Code32SegLen - 1,  DA_32 + DA_C + DA_DPL0
VIDEO_DESC		    : Descriptor	 0xB8000, 	  0x07FFF,        DA_32 + DA_DRWA + DA_DPL0
CODE32_FLAT_DESC	: Descriptor		0, 	      0xFFFFF,        DA_32 + DA_C + DA_DPL0
DATA32_FLAT_DESC	: Descriptor		0, 	      0xFFFFF,        DA_32 + DA_DRW + DA_DPL0
TASK_LDT_DESC       : Descriptor		0, 			  0,		   0
TASK_TSS_DESC       : Descriptor		0, 			  0,		   0
; GDT end  

GdtLen	  equ	   $ - GDT_ENTRY
GdtPtr:    
		dw	GdtLen - 1
		dd	0

; GDT Selector
Code32Selector	    equ    (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector	    equ    (0x0002 << 3) + SA_TIG + SA_RPL0
Code32FlatSelector	equ    (0x0003 << 3) + SA_TIG + SA_RPL0
Data32FlatSelector	equ    (0x0004 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]


[section .idt]
align 32
[bits 32]
IDT_ENTRY:
; IDT definition
;                           Selector,           Offset,            DCount,           Attribut
%rep 256
             Gate        Code32FlatSelector,   DefaultHandler,        0,             DA_386IGate + DA_DPL0
%endrep
; IDT end

IdtLen      equ     $ - IDT_ENTRY
IdtPtr:
        dw  IdtLen - 1
        dd  0

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

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

	; initalize 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
	
	call LoadTarget
	cmp dx, 0
	jz Output	

	call StoreGlobal

	; 1. load GDT
    lgdt [GdtPtr]
    
    ; 2. close interrupt
    ;    load IDT
    ;    set IOPL = 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. jump to 32 bits code
    jmp dword Code32Selector : 0


Output:
	mov bp, ErrStr
	mov cx, ErrLen
	call Print
	
	jmp $

; 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

;
;
StoreGlobal:
	push eax	
	
	mov dword [RunTaskEntry], RunTask
	
	mov dword [InitInterruptEntry], InitInterrupt
	
	mov dword [EnableTimerEntry], EnableTimer 
	
	mov dword [SendEOIEntry], SendEOI
	
	mov eax, dword [GdtPtr + 2]
	mov dword [GdtEntry], eax
	
	mov dword [GdtSize], GdtLen / 8
	
	mov eax, dword [IdtPtr + 2]
	mov dword [IdtEntry], eax
	
	mov dword [IdtSize], IdtLen / 8
	
	pop eax
	
	ret

[section .sfunc]
[bits 32]

DefaultHandlerFunc:

    iret
    
DefaultHandler      equ     DefaultHandlerFunc - $$

; void InitInterrupt()
;
InitInterrupt:
	push ebp
	mov ebp, esp
	
    push ax
    push dx
    
    call Init8259A

    sti
    
    mov al, 0xFF
    mov dx, MASTER_IMR_PORT
    call WriteIMR
    
    mov al, 0xFF
    mov dx, SLAVE_IMR_PORT
    call WriteIMR
    
    pop dx
    pop ax
    
    leave
    
    ret
    
; void EnableTimer()
;
EnableTimer:
	push ebp
	mov ebp, esp
	
	push ax
    push dx
    
    mov dx, MASTER_IMR_PORT
    call ReadIMR
    and ax, 0xFE
    call WriteIMR
    
    pop ax
    pop dx
	
	leave
	
	ret

; SendEOI(uint port) 
;
SendEOI:
	push ebp
	mov ebp, esp
	
	mov edx, [esp + 8];
	mov al, 0x20
	out dx, al
	call Delay
	
	leave
	
	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



[section .gfunc]
[bits 32]
;
; parameter  ===> Task* pt
RunTask:
	push ebp
	mov ebp, esp
	
	mov esp, [ebp + 8]
	
	lldt word [esp + 200]
	ltr  word [esp + 202] 
	
	pop gs
	pop fs
	pop es
	pop ds
	
	popad
	
	add esp, 4
	
	iret
	

[section .s32]
[bits 32]
CODE32_SEGMENT:
	mov ax, VideoSelector
	mov gs, ax
	
	mov ax, Data32FlatSelector
	mov ds, ax
	mov es, ax
	mov fs, ax
	
	mov ax, Data32FlatSelector
	mov ss, ax
	mov esp, BaseOfLoader
	
	jmp dword Code32FlatSelector : BaseOfKernel

Code32SegLen	equ    $ - CODE32_SEGMENT


ErrStr db  "No Kernel" 
ErrLen equ ($-ErrStr)

Buffer:
    db  0x00

loader.asm里,我们初始化了中断描述符表,并把它加载进了内存,这256个中断向量默认的中断服务程序为 DefaultHandlerFunc;我们给内核提供了3个接口,这3个函数都需要遵循调用规则;InitInterrupt 函数用来初始化中断,EnableTimer 函数用来初始化时钟,SendEOI 函数用来发送EOI控制字,表示该中断已经结束;在 StoreGlobal 函数中我们将这3个内核函数的入口地址写到了共享交换区上。

kernel.h

#ifndef KERNEL_H
#define KERNEL_H

#include "type.h"
#include "const.h"

typedef struct
{
	ushort limit1;
	ushort base1;
	byte   base2;
	byte   attr1;
	byte   attr2_limit2;
	byte   base3;
} Descriptor;

typedef struct
{
	Descriptor* const entry;
	const int         size;
} GdtInfo;

typedef struct
{
	ushort offset1;
	ushort selector;
	byte dcount;
	byte attr;
	ushort offset2;
} Gate;

typedef struct
{
	Gate* const entry;
	const int   size;
} IdtInfo;

typedef struct 
{
    uint gs;
    uint fs;
    uint es;
    uint ds;
    uint edi;
    uint esi;
    uint ebp;
    uint kesp;
    uint ebx;
    uint edx;
    uint ecx;
    uint eax;
    uint raddr;
    uint eip;
    uint cs;
    uint eflags;
    uint esp;
    uint ss;
} RegValue;

typedef struct
{
    uint   previous;
    uint   esp0;
    uint   ss0;
    uint   unused[22];
    ushort reserved;
    ushort iomb;
} TSS;

typedef struct
{
    RegValue   rv;
    Descriptor ldt[3];
    TSS        tss;
    ushort     ldtSelector;
    ushort     tssSelector;
    uint       id;
    char       name[8]; 
    byte       stack[512];
} Task;

int SetDescValue(Descriptor* pDesc, uint base, uint limit, ushort attr);
int GetDescValue(Descriptor* pDesc, uint* pBase, uint* pLimit, ushort* pAttr);
int SetIntHandler(Gate* pGate, uint ifunc);
int GetIntHandler(Gate* pGate, uint* pIfunc);

#endif

kernel.c


#include "kernel.h"

int SetDescValue(Descriptor* pDesc, uint base, uint limit, ushort attr)
{
	int ret = 0;
	
	if(ret = (pDesc != NULL))
	{
		pDesc->base1         = base  & 0xFFFF;
		pDesc->limit1        = limit & 0xFFFF;
		pDesc->base2         = (base >> 16) & 0xFF;
		pDesc->attr1         = attr  & 0xFF;
		pDesc->attr2_limit2  = ((attr >> 8) & 0xF0) | ((limit >> 16) & 0x0F); 
		pDesc->base3         = (base >> 24) & 0xFF;
	}
	
	return ret;
}

int GetDescValue(Descriptor* pDesc, uint* pBase, uint* pLimit, ushort* pAttr)
{
	int ret = 0;
	
	if(ret = (pDesc) && (pBase) && (pLimit) && (pAttr))
	{
		*pBase  = (pDesc->base3 << 24) | (pDesc->base2 << 16) | pDesc->base1;
		*pLimit = ((pDesc->attr2_limit2 & 0x0F) << 16) | pDesc->limit1;
		*pAttr  = ((pDesc->attr2_limit2 & 0xF0) << 8) | pDesc->attr1;
	}
	
	return ret;
}

int SetIntHandler(Gate* pGate, uint ifunc)
{
	int ret = 0;
	
	if(ret = (pGate != NULL))
	{
		pGate->offset1 = (ifunc & 0xFFFF);
		pGate->selector = GDT_CODE32_FLAT_SELECTOR;
		pGate->dcount = 0;
		pGate->attr = DA_386IGate + DA_DPL0;
		pGate->offset2 = ((ifunc >> 16) & 0xFFFF);
	}
	
	return ret;
}

int GetIntHandler(Gate* pGate, uint* pIfunc)
{
	int ret = 0;
	
	if(ret = (pGate && pIfunc))
	{
		*pIfunc = (pGate->offset2 << 16) | pGate->offset1;	
	}
	
	return ret;
}

我们定义了 Gate 结构体,里面存储着门描述符的相关信息;定义了 IdtInfo 结构体,里面存储着中断描述符表和和它的大小。

int SetIntHandler(Gate* pGate, uint ifunc) 用来将 ifunc 函数入口地址作为一个中断描述符的中断服务程序。

int GetIntHandler(Gate* pGate, uint* pIfunc) 用来读取一个中断描述符的中断服务程序的入口地址,存储在 pIfunc中。

kentry.asm


%include "common.asm"

global _start

extern KMain
extern ClearScreen
extern gGdtInfo
extern gIdtInfo
extern RunTask
extern InitInterrupt
extern EnableTimer
extern SendEOI

[section .text]
[bits 32]
_start:
    mov ebp, 0
    
    call InitGlobal
    call ClearScreen
    
    call KMain
    
    
    jmp $
    
;
;
InitGlobal:
	push ebp
	mov ebp, esp
	
	mov eax, dword [GdtEntry]
	mov dword [gGdtInfo], eax
	
	mov eax, dword [GdtSize]
	mov dword [gGdtInfo + 4], eax
	
	mov eax, dword [IdtEntry]
	mov dword [gIdtInfo], eax
	
	mov eax, dword [IdtSize]
	mov dword [gIdtInfo + 4], eax
	
	mov eax, dword [RunTaskEntry]
	mov dword [RunTask], eax
	
	mov eax, dword [InitInterruptEntry]
	mov dword [InitInterrupt], eax
	
	mov eax, dword [EnableTimerEntry]
	mov dword [EnableTimer], eax
	
	mov eax, dword[SendEOIEntry]
	mov dword [SendEOI], eax
	
	leave
	
	ret

InitGlobal 用来初始化中断描述符表的信息和3个接口函数的入口地址给内核使用。

kmain.c

#include "kernel.h"
#include "screen.h"
#include "global.h"

void (* const InitInterrupt)() = NULL;
void (* const EnableTimer)() = NULL;
void (* const SendEOI)() = NULL;

Task t = {0};

void TimerHandler()
{
	static uint i = 0;
	
	i = (i + 1) % 10;
	
	if(i == 0)
	{
		static uint j = 0;
		
		SetPrintPos(0, 13);
		PrintString("Timer: ");
		
		SetPrintPos(8, 13);
		PrintIntDec(j++);
	}
	
	SendEOI(MASTER_EOI_PORT);
	
	asm volatile("leave\n" "iret\n");
}

Delay(int n)
{
	while(n > 0)
	{
		int i = 0, j = 0;
		
		for(i = 0; i < 1000; i++)
		{
			for(j = 0; j < 1000; j++)
			{
				asm volatile("nop\n");
			}
		}
		
		n--;
	}
}

void TaskA()
{
	int i = 0;
	
	SetPrintPos(0, 12);
	PrintString("TaskA: ");
	
	while(1)
	{
		SetPrintPos(8, 12);
		PrintChar('A' + i);
		i = (i + 1) % 26;
		Delay(1);
	}
}

void KMain()
{
	PrintString("D.T.OS\n");
	
	uint base = 0;
	uint limit = 0;
	uint attr = 0;
	int i = 0;
	
	PrintString("Gdt Entry: ");
	PrintIntHex((uint)gGdtInfo.entry);
	PrintChar('\n');
	
	PrintString("Gdt Size: ");
	PrintIntDec((uint)gGdtInfo.size);
	PrintChar('\n');

	PrintString("Idt Entry: ");
	PrintIntHex((uint)gIdtInfo.entry);
	PrintChar('\n');
	
	PrintString("Idt Size: ");
	PrintIntDec((uint)gIdtInfo.size);
	PrintChar('\n');
	
	t.rv.gs = LDT_VIDEO_SELECTOR;
	t.rv.cs = LDT_CODE32_SELECTOR;
	t.rv.ds = LDT_DATA32_SELECTOR;
	t.rv.es = LDT_DATA32_SELECTOR;
	t.rv.fs = LDT_DATA32_SELECTOR;
	t.rv.ss = LDT_DATA32_SELECTOR;
	
	t.rv.esp = (uint)(t.stack + sizeof(t.stack));
	t.rv.eip = (uint)TaskA;
	t.rv.eflags = 0x3202;

	t.tss.ss0 = GDT_DATA32_FLAT_SELECTOR;
	t.tss.esp0 = 0x9000;
	t.tss.iomb = sizeof(t.tss);
	
	SetDescValue(t.ldt + LDT_VIDEO_INDEX,  0xB8000, 0X07FFF, DA_DRWA + DA_32 + DA_DPL3);
	SetDescValue(t.ldt + LDT_CODE32_INDEX, 0x00,    0XFFFFF, DA_C + DA_32 + DA_DPL3);
	SetDescValue(t.ldt + LDT_DATA32_INDEX, 0x00,    0XFFFFF, DA_DRW + DA_32 + DA_DPL3);

	t.ldtSelector = GDT_TASK_LDT_SELECTOR;
	t.tssSelector = GDT_TASK_TSS_SELECTOR;
	
	SetDescValue(&gGdtInfo.entry[GDT_TASK_LDT_INDEX], (uint)&t.ldt, sizeof(t.ldt) - 1, DA_LDT + DA_DPL0);
	SetDescValue(&gGdtInfo.entry[GDT_TASK_TSS_INDEX], (uint)&t.tss, sizeof(t.tss) - 1, DA_386TSS + DA_DPL0);
	
	SetIntHandler(gIdtInfo.entry + 0x20, (uint)TimerHandler);
	
	PrintString("Stack Bottom: ");
    PrintIntHex((uint)t.stack);
    PrintString("    Stack Top: ");
    PrintIntHex((uint)t.stack + sizeof(t.stack));
    
    InitInterrupt();
    EnableTimer();
	
	RunTask(&t);
}

全局的3个函数指针被用来初始化3个内核函数的入口地址,TimerHandler 函数是时钟的中断服务程序。

117行,我们将时钟中断关联到了 TimerHandler 函数。

124 - 125行,我们初始化了中断并使能了时钟中断。

101行,我们将 eflags 的值设为 0x3202,把 IF 位置位1,打开了处理器的中断;如果这里不把 IF 位置1的话,Task A在执行的时候,IF 位将一直为0,处理器将接收不到中断。

104行我们将 esp0 的值设置为 0x9000,是因为Task A任务的特权级为3,在Task A任务执行期间,会因为时钟中断的到来,从而升高特权级去执行中断服务程序,中断服务程序的特权级为0,因此这里要将设置0特权级的 esp 值,从而能正常的从3特权级跳转到0特权级执行。

TimerHandler 时钟中断服务程序中,我们需要在中断服务程序结束时,手动清除EOI标志位,表明中断已经结束,可以接受下一个中断;如果我们不清除EOI,则中断服务程序只会执行一次;在30行,我们内嵌汇编,用 iret 指令进行返回,在中断服务程序中,我们需要指定用 iret 指令返回,否则就是用普通的 ret 指令返回。

程序成功的执行了! 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值