问题
系统中的所有任务都是一直不停执行吗?任务入口函数结束后,返回到哪里?
再论当前任务执行解决方案
我们是通过修改 eip 寄存器的值来实现任务跳转的。
结论
当前方案直接通过 iret 跳转到进程入口函数执行,并非函数调用,因此无法正确返回!!!
改进思路
Task 结构体中新增 void (*tmain)() 成员 (保存任务入口函数地址)
新增 void TaskEntry() 全局任务入口函数 (RegValue.eip = (uint)TaskEntry;)
改进任务执行方式
typedef struct
{
RegValue rv;
Descriptor ldt[3];
ushort ldtSelector;
ushort tssSelector;
void (*tmain)();
uint id;
char name[8];
byte stack[512];
} Task;
static void InitTask(Task* pt, void(*entry)())
{
PrintIntHex(pt);
PrintString(" ");
PrintIntHex(entry);
PrintChar('\n');
pt->rv.cs = LDT_CODE32_SELECTOR;
pt->rv.gs = LDT_VIDEO_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)TaskEntry;
pt->rv.eflags = 0x3202;
pt->tmain = entry;
gTSS.ss0 = GDT_DATA32_FLAT_SELECTOR;
gTSS.esp0 = (uint)&pt->rv + sizeof(pt->rv);
gTSS.iomb = sizeof(TSS);
SetDescValue(AddrOff(pt->ldt, LDT_VIDEO_INDEX), 0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL3);
SetDescValue(AddrOff(pt->ldt, LDT_CODE32_INDEX), 0x00, 0xFFFFF, DA_C + DA_32 + DA_DPL3);
SetDescValue(AddrOff(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;
}
void TaskEntry()
{
if(gCTaskAddr != NULL)
{
gCTaskAddr->tmain();
}
// to destory current task here
while(1); // TODO: schedule next task to run
}
我们修改或新增的代码如上。
首先我们在 Task 结构体中,新增了一个函数指针(tmain),用来指向任务的入口地址,把 eip 指向全局任务函数入口地址 (TaskEntry),这样在一个任务开始执行时,首先跳转到 TaskEntry 处,调用 tmain 函数;在任务结束时,就返回到 TaskEntry 处,之后就可以回收这个任务的系统资源了。
思考
什么时候销毁一个任务?如何销毁一个任务?
系统调用
任务与内核之间的交互接口
涉及特权级的转换 (DPL0 => DPL3)
执行内核中的代码
系统调用的实现
通过 0x80 号中断描述符实现系统调用 (软中断)
使用 ax 寄存器指定功能号 (如:ax == 0 => KillTask())
实现步骤
1. 实现 void SysCallHandler(ushort ax) 中断服务程序
2. 设置 IDT 中 0x80 号中断描述符的 ISR 入口地址
3. 当 ax == 0时,调用 KillTask() 销毁当前任务
实现系统调用
interrupt.c
#include "utility.h"
#include "interrupt.h"
#include "ihandler.h"
void (* const InitInterrupt)() = NULL;
void (* const EnableTimer)() = NULL;
void (* const SendEOI)(uint port) = NULL;
void IntModInit()
{
SetIntHandler(AddrOff(gIdtInfo.entry, 0x20), (uint)TimerHandlerEntry);
SetIntHandler(AddrOff(gIdtInfo.entry, 0x80), (uint)SysCallHandlerEntry);
InitInterrupt();
}
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_DPL3;
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;
}
kentry.asm
%include "common.asm"
global _start
global TimerHandlerEntry
global SysCallHandlerEntry
extern TimerHandler
extern SysCallHandler
extern gCTaskAddr
extern gGdtInfo
extern gIdtInfo
extern InitInterrupt
extern EnableTimer
extern SendEOI
extern RunTask
extern LoadTask
extern KMain
extern ClearScreen
%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 [gGdtInfo], eax
mov eax, dword [GdtSize]
mov [gGdtInfo + 4], eax
mov eax, dword [IdtEntry]
mov [gIdtInfo], eax
mov eax, dword [IdtSize]
mov [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
;
;
SysCallHandlerEntry:
BeginISR
push ax
call SysCallHandler
pop ax
EndISR
ihandler.h
#ifndef IHANDLER_H
#define IHANDLER_H
#define DeclHandler(name) void name##Entry(); \
void name()
DeclHandler(TimerHandler);
DeclHandler(SysCallHandler);
#endif
ihandler.c
#include "interrupt.h"
void TimerHandler()
{
static uint i = 0;
i = (i + 1) % 5;
if( i == 0 )
{
Schedule();
}
SendEOI(MASTER_EOI_PORT);
}
void SysCallHandler(ushort ax)
{
if(ax == 0)
{
KillTask();
}
else
{
}
}
task.c
#include "utility.h"
#include "task.h"
#define MAX_RUNNING_TASK 12
void (* const RunTask)(volatile Task* pt) = NULL;
void (* const LoadTask)(volatile Task* pt) = NULL;
volatile Task* gCTaskAddr = NULL;
static TaskNode gTaskBuff[MAX_RUNNING_TASK] = {0};
static TSS gTSS = {0};
static Queue gRunningTask = {0};
void TaskEntry()
{
if(gCTaskAddr != NULL)
{
gCTaskAddr->tmain();
}
// to destory current task here
asm volatile(
"movw $0, %ax \n"
"int $0x80 \n"
);
while(1); // TODO: schedule next task to run
}
void TaskA()
{
int i = 0;
SetPrintPos(0, 12);
PrintString(__FUNCTION__);
while(i < 5)
{
SetPrintPos(8, 12);
PrintChar('A' + i);
i = (i + 1) % 26;
Delay(1);
}
SetPrintPos(8, 12);
}
void TaskB()
{
int i = 0;
SetPrintPos(0, 13);
PrintString(__FUNCTION__);
while(1)
{
SetPrintPos(8, 13);
PrintChar('0' + i);
i = (i + 1) % 10;
Delay(1);
}
}
void TaskC()
{
int i = 0;
SetPrintPos(0, 14);
PrintString(__FUNCTION__);
while(1)
{
SetPrintPos(8, 14);
PrintChar('a' + i);
i = (i + 1) % 26;
Delay(1);
}
}
void TaskD()
{
int i = 0;
SetPrintPos(0, 15);
PrintString(__FUNCTION__);
while(1)
{
SetPrintPos(8, 15);
PrintChar('!' + i);
i = (i + 1) % 10;
Delay(1);
}
}
static void InitTask(Task* pt, void(*entry)())
{
PrintIntHex(pt);
PrintString(" ");
PrintIntHex(entry);
PrintChar('\n');
pt->rv.cs = LDT_CODE32_SELECTOR;
pt->rv.gs = LDT_VIDEO_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)TaskEntry;
pt->rv.eflags = 0x3202;
pt->tmain = entry;
gTSS.ss0 = GDT_DATA32_FLAT_SELECTOR;
gTSS.esp0 = (uint)&pt->rv + sizeof(pt->rv);
gTSS.iomb = sizeof(TSS);
SetDescValue(AddrOff(pt->ldt, LDT_VIDEO_INDEX), 0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL3);
SetDescValue(AddrOff(pt->ldt, LDT_CODE32_INDEX), 0x00, 0xFFFFF, DA_C + DA_32 + DA_DPL3);
SetDescValue(AddrOff(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;
}
static void PrepareForRun(volatile Task* pt)
{
gTSS.ss0 = GDT_DATA32_FLAT_SELECTOR;
gTSS.esp0 = (uint)&pt->rv + sizeof(pt->rv);
gTSS.iomb = sizeof(TSS);
SetDescValue(AddrOff(gGdtInfo.entry, GDT_TASK_LDT_INDEX), (uint)&pt->ldt, sizeof(pt->ldt)-1, DA_LDT + DA_DPL0);
}
void TaskModInit()
{
SetDescValue(AddrOff(gGdtInfo.entry, GDT_TASK_TSS_INDEX), (uint)&gTSS, sizeof(gTSS)-1, DA_386TSS + DA_DPL0);
InitTask(&(((TaskNode*)AddrOff(gTaskBuff, 0))->task), TaskA);
InitTask(&(((TaskNode*)AddrOff(gTaskBuff, 1))->task), TaskB);
InitTask(&(((TaskNode*)AddrOff(gTaskBuff, 2))->task), TaskC);
InitTask(&(((TaskNode*)AddrOff(gTaskBuff, 3))->task), TaskD);
Queue_Init(&gRunningTask);
Queue_Add(&gRunningTask, &((TaskNode*)AddrOff(gTaskBuff, 0))->head);
Queue_Add(&gRunningTask, &((TaskNode*)AddrOff(gTaskBuff, 1))->head);
Queue_Add(&gRunningTask, &((TaskNode*)AddrOff(gTaskBuff, 2))->head);
Queue_Add(&gRunningTask, &((TaskNode*)AddrOff(gTaskBuff, 3))->head);
}
void LaunchTask()
{
gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task;
PrepareForRun(gCTaskAddr);
RunTask(gCTaskAddr);
}
void Schedule()
{
Queue_Rotate(&gRunningTask);
gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task;
PrepareForRun(gCTaskAddr);
LoadTask(gCTaskAddr);
}
void KillTask()
{
PrintString(__FUNCTION__); // destroy current task
}
kmain.c
#include "task.h"
#include "interrupt.h"
#include "screen.h"
void KMain()
{
int n = PrintString("D.T.OS\n");
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');
TaskModInit();
IntModInit();
LaunchTask();
}
因为在 TaskEntry 函数中,当一个任务结束返回到该函数时,当前运行在用户态,特权级为3,而释放任务结束后的系统资源需要内核来完成,所以我们需要从3特权级转移到0特权级,陷入内核态,让内核完成销毁任务的系统资源;所以我们就需要实现一个系统调用(软中断)。
首先我们需要设置 0x80 号中断对应的中断入口函数 (SysCallHandlerEntry),注意:我们需要把 0x80 号中断描述符对应的特权级设置为3,否则无法从用户态跳转到内核态。
SysCallHandlerEntry 函数里我们调用了 SysCallHandler 函数,这个函数需要1个参数,并且是用C语言编写的,所以我们需要遵守调用约定,由调用者 SysCallHandlerEntry 手动 push ax 并且 pop ax。
在SysCallHandler 函数中,通过 ax 的值实现一系列的系统调用,当 ax 的值为0时,调用 KillTask 函数,销毁该任务的系统资源。
在 TaskEntry 函数中,当有任务结束时,我们通过内联汇编将 ax 的值设置为0,并调用 0x80号中断,让内核来销毁该任务的系统资源。
最后,又返回到 while(1)。