系统调用的实现

问题

系统中的所有任务都是一直不停执行吗?任务入口函数结束后,返回到哪里?

再论当前任务执行解决方案

我们是通过修改 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)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值