问题
如何并行执行多个任务?(如何在多个任务之间切换执行?)
方案思路
在中断服务程序中改变 gCTaskAddr 的值
- 注:gCtaskAddr 指向当前执行任务中的 Task 结构体
课程目标:创建两个任务并行执行
1. 启动时钟中断
2. 启动 TaskA 并打开中断开关
3. 在时钟中断服务程序中使得 gCTaskAddr 指向 TaskB
4. TaskB 执行 (中断开关已打开)
5. 在时钟中断服务程序中使得 gCTaskAddr 指向 TaskA
6. 。。。
任务并行方案
再再论TSS。。。
目前的设计中,使用TSS唯一的意义仅是转入高特权级时获取栈的位置 (潜台词:仅仅在中断发生时,将 ss0 和 esp0 指定的内存作为初始内核栈)。
因此,所有任务共享一个TSS结构体;在切换任务执行时,重新指定 TSS 结构体中的 ss0 和 esp0;注:每个任务的 LDT 依然是私有的,不可共享。
多任务并行执行
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};
Task p = {0};
volatile Task* gCTaskAddr = NULL;
TSS gTss = {0};
extern void TimerHandlerEntry();
void ChangeTask()
{
gCTaskAddr = (gCTaskAddr == &p) ? &t : &p;
gTss.ss0 = GDT_DATA32_FLAT_SELECTOR;
gTss.esp0 = (uint)&gCTaskAddr->rv.gs + sizeof(RegValue);
SetDescValue(&gGdtInfo.entry[GDT_TASK_LDT_INDEX], (uint)&gCTaskAddr->ldt, sizeof(gCTaskAddr->ldt) - 1, DA_LDT + DA_DPL0);
LoadTask(gCTaskAddr);
}
void TimerHandler()
{
static uint i = 0;
i = (i + 1) % 5;
if(i == 0)
{
ChangeTask();
}
SendEOI(MASTER_EOI_PORT);
}
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 TaskB()
{
int i = 0;
SetPrintPos(0, 13);
PrintString("TaskB: ");
while(1)
{
SetPrintPos(8, 13);
PrintChar('0' + i);
i = (i + 1) % 10;
Delay(1);
}
}
void InitTask(Task* pt, void (* entry) ())
{
pt->rv.gs = LDT_VIDEO_SELECTOR;
pt->rv.cs = LDT_CODE32_SELECTOR;
pt->rv.ds = LDT_DATA32_SELECTOR;
pt->rv.es = LDT_DATA32_SELECTOR;
pt->rv.fs = LDT_DATA32_SELECTOR;
pt->rv.ss = LDT_DATA32_SELECTOR;
pt->rv.esp = (uint)pt->stack + sizeof(pt->stack);
pt->rv.eip = (uint)entry;
pt->rv.eflags = 0x3202;
gTss.ss0 = GDT_DATA32_FLAT_SELECTOR;
gTss.esp0 = (uint)&pt->rv.gs + sizeof(pt->rv);
gTss.iomb = sizeof(TSS);
SetDescValue(pt->ldt + LDT_VIDEO_INDEX, 0xB8000, 0X07FFF, DA_DRWA + DA_32 + DA_DPL3);
SetDescValue(pt->ldt + LDT_CODE32_INDEX, 0x00, 0XFFFFF, DA_C + DA_32 + DA_DPL3);
SetDescValue(pt->ldt + LDT_DATA32_INDEX, 0x00, 0XFFFFF, DA_DRW + DA_32 + DA_DPL3);
pt->ldtSelector = GDT_TASK_LDT_SELECTOR;
pt->tssSelector = GDT_TASK_TSS_SELECTOR;
SetDescValue(&gGdtInfo.entry[GDT_TASK_LDT_INDEX], (uint)&pt->ldt, sizeof(pt->ldt) - 1, DA_LDT + DA_DPL0);
SetDescValue(&gGdtInfo.entry[GDT_TASK_TSS_INDEX], (uint)&gTss, sizeof(gTss) - 1, DA_386TSS + DA_DPL0);
}
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');
InitTask(&p, TaskB);
InitTask(&t, TaskA);
SetIntHandler(gIdtInfo.entry + 0x20, (uint)TimerHandlerEntry);
PrintString("Stack Bottom: ");
PrintIntHex((uint)t.stack);
PrintString(" Stack Top: ");
PrintIntHex((uint)t.stack + sizeof(t.stack));
gCTaskAddr = &t;
InitInterrupt();
EnableTimer();
RunTask(gCTaskAddr);
}
kentry.asm
%include "common.asm"
global _start
global TimerHandlerEntry
extern gCTaskAddr
extern TimerHandler
extern KMain
extern ClearScreen
extern gGdtInfo
extern gIdtInfo
extern RunTask
extern InitInterrupt
extern EnableTimer
extern SendEOI
extern LoadTask
%macro BeginISR 0
sub esp, 4
pushad
push ds
push es
push fs
push gs
mov dx, ss
mov ds, dx
mov es, dx
mov esp, BaseOfLoader
%endmacro
%macro EndISR 0
mov esp, [gCTaskAddr]
pop gs
pop fs
pop es
pop ds
popad
add esp, 4
iret
%endmacro
[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
mov eax, dword [LoadTaskEntry]
mov dword [LoadTask], eax
leave
ret
;
;
TimerHandlerEntry:
BeginISR
call TimerHandler
EndISR
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 [LoadTaskEntry], LoadTask
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
; void LoadTask(Task* pt)
;
LoadTask:
push ebp
mov ebp, esp
mov eax, [esp + 8]
lldt word [eax + 200]
leave
ret
[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
本次任务需要定义两个任务 t 和 p,两个任务入口函数,TaskA 函数用于打印字母 A - Z,TaskB 函数用于打印数字 1 - 9。
由于我们现在使用 TSS 的唯一目的是:仅仅在中断发生时,将 ss0 和 esp0 指定的内存作为初始内核栈。因此,我们定义了一个全局的 TSS,所有任务共享这个全局 TSS。
InitTask 函数用于初始化一个任务,并设置这个任务执行的入口地址。
初始化任务 t 和 p 后,打开中断并使能时钟中断,然后开始 t 任务的执行;当前的esp0指向的是 t 的 RegValue 的底部,LDT 也是 t 任务结构内部定义的 ldt。
RunTask(gCTaskAddr) 执行后,首先是加载任务 t 的 LDT 和 TSS,然后恢复 t 任务的一系列寄存器,通过 iret 指令,转去执行 TaskA;每过1个时钟中断,都会保护 t 的任务上下文,去执行中断服务程序,然后恢复上下文;当每过5个时钟中断时,首先会保存 t 的上下文,让gCTaskAddr 指向任务 p,改变TSS,让 esp0 指向 p 的 RegValue 的底部 (以便下一次中断保存的是任务 p 的上下文),改变全局段描述符LDT;然后去执行 LoadTask。
LoadTask 里,让 esp 指向 gCTaskAddr (任务结构 p),加载任务 p 里的 ldt,然后再执行EndISR,恢复 p 任务的上下文,就开始执行 p 任务了。
如此往复。。。