前言

先前我们实现了多个任务并行执行。然而没有考虑任务结束后应该怎么办。因此存在两个问题:

  1. iret跳转到进程入口函数执行并非函数调用(没有将返回地址压入栈的操作),函数无法返回,也就无法销毁此任务
  2. 当前任务主动结束后应该如何处理此任务,如何具体地销毁当前任务并且执行下一个任务?

销毁任务KillTask

增加可销毁任务的返回点

当前方案直接通过iret(中断处理降低特权级)跳转到进程入口函数执行,并非函数调用,因此无法正确返回! ! !

因此构造一个函数即可,让这个任务结束后能到达一个我们可以销毁已结束任务的地方

改进思路:

  • *Task结构体中新增void(tmain)()成员(保存任务入口函数地址)
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct
{
RegValue rv;
Descriptor ldt[3];
ushort ldtSelector;
ushort tssSelector;
void (*tmain)();
uint id;
ushort current;
ushort total;
char name[8];
byte stack[512];
} Task;
  • 新增void TaskEntry()并且被设置为全局任务入口函数(RegValue.eip = (uint)TaskEntry;)
1
2
3
4
5
6
7
8
9
static void TaskEntry()
{
if( gCTaskAddr )
{
gCTaskAddr->tmain();
}

// to destory current task here 处于用户态
}

再添加一个TaskEntry全局入口函数,eip指向TaskEntry,而Tmain指向具体任务(函数)。

这样可以真正的调用新函数并且正常返回

中断提升特权级

只有在内核态才能销毁任务,然而这里任务入口函数返回之后仍然处于用户态。

既然要陷入内核态就可以借助中断来完成
特权级升高可以通过内部中断(软中断)来完成,自行设计参数比如ax=0; int 0x80然后设计相应的处理函数即可

  1. 实现void SysCallHandler(ushort ax)中断服务程序
  2. 设置IDT中0x80号中断描述符的ISR入口地址
  3. 当ax== 0时,调用KillTask()销毁当前任务(主动结束)
1
2
3
4
5
//80号中断服务程序
void SysCallHandler(ushort ax)
if( ax == 0 )
KillTask();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
static void TaskEntry()
{
if( gCTaskAddr )
{
gCTaskAddr->tmain();
}

// to destory current task here 处于用户态
asm volatile( //调用80号中断陷入内核态
"movl $0, %eax \n" // type
"int $0x80 \n"
);
}

80号中断处理函数完成释放任务资源

killTask需要释放资源,所谓资源就是内存资源和CPU资源

任务生命周期:任务从开始执行到结束执行经过的时间

任务的状态:任务在生命周期会经历不同的状态

image-20220723142523159

任务切换如何实现呢?可以通过不同的队列实现

  • 为每种状态准备内核队列 (就绪队列, 执行队列, 等待队列)
  • 任务创建后立即进入就绪队列
  • 调度器根据当前执行的任务数量决定调度策略
    执行时间结束的进入就绪队列
    等待外部事件的任务的进入等待队列,等待队列中的任务必须先进入就绪队列才能执行

image-20220723142939780

本质而言就是TaskNode在不同队列间的迁移

killtask实现思路:

  • 将当前任务从执行队列移除, 并移入空闲 TaskNode 队列
  • 调度就绪队列中的任务进入执行队列 (如果就绪队列存在任务)
  • 执行队列中的队首任务被调度执行 (调度下一个任务执行)

整体实现规划

  • 实现空闲TaskNode队列 (填充预定义数量的TaskNode)
  • 初始化预定义任务 (调度进入队列)
  • 启动第1个任务 (调度进入执行队列)
  • 实现 KillTask()

task.c中新增全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
void (* const RunTask)(volatile Task* pt) = NULL;
void (* const LoadTask)(volatile Task* pt) = NULL;

volatile Task* gCTaskAddr = NULL; /* DO NOT USE IT DIRECTLY */

static TaskNode gTaskBuff[MAX_TASK_BUFF_NUM] = {0};
static Queue gAppToRun = {0};
static Queue gFreeTaskNode = {0}; //空闲TaskNode,用于创建任务
static Queue gReadyTask = {0}; //就绪队列
static Queue gRunningTask = {0}; //执行队列
static TSS gTSS = {0};
static TaskNode* gIdleTask = NULL;
static uint gPid = PID_BASE;

另外需要增加一个idletask,因为每次时钟中断调用schedule函数的时候都有取出任务,如果运行队列为空了会报错,因此当队列中无其他任务时添加一个idletask到运行队列中,运行队列中有任务时则取出idletask避免占用时间片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//检查执行队列是否有任务,没有任务则添加idletask
static void CheckRunningTask()
{
if( Queue_Length(&gRunningTask) == 0 )
{
Queue_Add(&gRunningTask, (QueueNode*)&gIdleTask);
}
else if( Queue_Length(&gRunningTask) > 1 )
{
if( IsEqual(Queue_Front(&gRunningTask), (QueueNode*)&gIdleTask) )
{
Queue_Remove(&gRunningTask);
}
}
}

构建具体任务信息

增加一个app.c用于存储任务的信息。对应于用户级的代码,task.c则是内核级来调用app.c里面的任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
typedef struct 
{
const char* name;
void (*tmain)();
byte priority;
} AppInfo;

static AppInfo gAppToRun[MAX_APP_NUM] = {0};
static uint gAppNum = 0;

void AppModInit();
AppInfo* GetAppToRun(uint index);
uint GetAppNum();

void TaskA();
void TaskB();
void TaskC();
void TaskD();

static void RegApp(const char* name, void(*tmain)(), byte pri)
{
if( gAppNum < MAX_APP_NUM )
{
AppInfo* app = AddrOff(gAppToRun, gAppNum);

app->name = name;
app->tmain = tmain;
app->priority = pri;

gAppNum++;
}
}

void AppModInit()
{
RegApp("Task A", TaskA, 255);
RegApp("Task B", TaskB, 230);
RegApp("Task C", TaskC, 230);
RegApp("Task D", TaskD, 255);
}

AppInfo* GetAppToRun(uint index)
{
AppInfo* ret = NULL;

if( index < MAX_APP_NUM )
{
ret = AddrOff(gAppToRun, index);
}

return ret;
}

uint GetAppNum()
{
return gAppNum;
}

用户的应用程序是死的,所谓的进程就是执行起来的任务

任务在队列间转移

task.c中可以通过app.c中的函数创建具体的任务/进程,放入了就绪队列当中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//创建任务进入就绪队列
static void CreateTask()
{
while( (0 < Queue_Length(&gAppToRun)) && (Queue_Length(&gReadyTask) < MAX_READY_TASK) )
{
TaskNode* tn = (TaskNode*)Queue_Remove(&gFreeTaskNode);

if( tn )
{
AppNode* an = (AppNode*)Queue_Remove(&gAppToRun);

InitTask(&tn->task, gPid++, an->app.name, an->app.tmain, an->app.priority);

Queue_Add(&gReadyTask, (QueueNode*)tn);

Free((void*)an->app.name);
Free(an);
}
else
{
break;
}
}
}

就绪与运行队列转化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static void ReadyToRunning()
{
QueueNode* node = NULL;

if( Queue_Length(&gReadyTask) < MAX_READY_TASK )
{
CreateTask();
}

while( (Queue_Length(&gReadyTask) > 0) && (Queue_Length(&gRunningTask) < MAX_RUNNING_TASK) )
{
node = Queue_Remove(&gReadyTask);

((TaskNode*)node)->task.current = 0;//当前任务的current赋值为0表示刚开始

Queue_Add(&gRunningTask, node);
}
}

static void RunningToReady()
{
TaskNode* tn = (TaskNode*)Queue_Front(&gRunningTask);

if( !IsEqual(tn, (QueueNode*)&gIdleTask) )
{ //时间到了从运行队列中移出去
if( tn->task.current == tn->task.total )
{
Queue_Remove(&gRunningTask);
Queue_Add(&gReadyTask, (QueueNode*)tn);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void TimerHandler()
{
static uint i = 0;

i = (i + 1) % 5;

if( i == 0 )
{
Schedule();
}

SendEOI(MASTER_EOI_PORT);
}

void Schedule()
{
RunningToReady();//如果有时间片已经用完的移入到就绪队列
ScheduleNext(); //执行队列间的的任务来回执行实现并行
}

static void ScheduleNext()
{
//若执行队列任务数未达到最大任务数,则从就绪队列中移入任务到执行队列
ReadyToRunning();
//判断是否有任务用于移动idle任务
CheckRunningTask();
//执行队列队首元素移动到队尾
Queue_Rotate(&gRunningTask);
//取出队首元素修改任务指针指向新任务
gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task;
////初始化好此任务的内核栈TSS信息和设置ldt信息
PrepareForRun(gCTaskAddr);
//加载队ldt信息到内存中
LoadTask(gCTaskAddr);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void KillTask()
{
QueueNode* node = Queue_Remove(&gRunningTask);
Task* task = &((TaskNode*)node)->task;
Event evt = {TaskEvent, (uint)task, 0, 0};
//被本任务阻塞的其他任务都给唤醒
EventSchedule(NOTIFY, &evt);

task->id = 0;

Queue_Add(&gFreeTaskNode, node);

Schedule();
}

增加优先级

如果当前运行队列的任务都是个while(1)死循环,这些任务一直占者CPU导致后续任务一直等待在肯定不合理,因此需要给任务提供优先级,优先级的高低决定任务执行时间的长短。达到优先级的时间则此任务会强制调度回就绪队列。

image-20220723171239876

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct
{
RegValue rv;
Descriptor ldt[3];
ushort ldtSelector;
ushort tssSelector;
void (*tmain)();
uint id;
ushort current; //每次目前已经执行了多少时间
ushort total; //允许此任务在执行队列中的存在时间
char name[8];
byte stack[512];
} Task;

时间处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void TimerHandler()
{
static uint i = 0;

i = (i + 1) % 5;
if( i == 0 )
{
Schedule();
}
SendEOI(MASTER_EOI_PORT);
}
void Schedule()
{
//判断运行队列的首元素是否到了total时间,若是则移入就绪队列
RunningToReady();
//若执行队列任务数未达到最大任务数,则从就绪队列中移入任务到执行队列
ReadyToRunning();
//判断是否有任务用于移动idle任务
CheckRunningTask();
//执行队列队首元素移动到队尾
Queue_Rotate(&gRunningTask);
//取出队首元素修改任务指针指向新任务
gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task;
////初始化好此任务的内核栈TSS信息和设置ldt信息
PrepareForRun(gCTaskAddr);
//加载队ldt信息到内存中
LoadTask(gCTaskAddr);
}