问题
如何设计 KillTask() 函数?
任务生命周期
任务从开始执行到结束执行经过的时间
任务的状态
任务在生命周期中会经历不同状态 (创建,销毁)
任务的生命周期与状态转换
TaskNodeBuffer 数组里存有多个任务,此时处于创建的状态,还没有将任务初始化;当将任务初始化后,就处于就绪状态,随时可能被处理器调度;当有任务被处理器调度时,这个任务处于执行状态;当任务处于执行状态,又需要与外设交互,等待外设处理结果返回时,这个任务处于等待状态;当这个任务执行完,操作系统需要销毁这个任务并释放这个任务的系统资源,此时这个任务处于销毁状态。
状态切换概要设计
为每种状态准备内核队列 (就绪队列,执行队列,等待队列)
任务创建后立即加入就绪队列
调度器根据当前执行的任务数量决定调度策略
- 执行时间结束的任务进入就绪队列
- 等待外部事件的任务进入等待队列
- 等待队列中的任务必须先进入就绪队列才能继续执行
核心数据结构 => 内核队列
KillTask() 实现思路
当 KillTask() 被调用时,意味着当前执行的任务结束
因此:
1. 将当前任务从执行队列中移除,并移入空闲 TaskNode 队列
2. 调度就绪队列中的任务进入执行队列 (如果就绪队列存在任务)
3. 执行队列中的队首任务被调度执行 (调度下一个任务执行)
整体实现规划
1. 实现空闲 TaskNode 队列 (填充预定义数量的 TaskNode)
2. 初始化预定义任务 (调度进入就绪队列)
3. 启动第一个任务 (调度进入执行队列)
4. 实现 KillTask() 。。。
任务生命期状态实现
task.c
#include "utility.h"
#include "task.h"
#include "app.h"
#define MAX_TASK_NUM 4
#define MAX_RUNNING_TASK 2
#define MAX_READY_TASK (MAX_TASK_NUM - MAX_RUNNING_TASK)
extern AppInfo* GetAppToRun(uint index);
extern uint GetAppNum();
void (* const RunTask)(volatile Task* pt) = NULL;
void (* const LoadTask)(volatile Task* pt) = NULL;
volatile Task* gCTaskAddr = NULL;
static TaskNode gTaskBuff[MAX_TASK_NUM] = {0};
static TSS gTSS = {0};
static Queue gFreeTaskNode = {0};
static Queue gReadyTask = {0};
static Queue gRunningTask = {0};
static Queue gWaitingTask = {0};
static TaskNode gIdleTask = {0};
static uint gAppToRunIndex = 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 IdleTask()
{
int i = 0;
SetPrintPos(0, 10);
PrintString(__FUNCTION__);
while(1)
{
SetPrintPos(10, 10);
PrintChar('A' + i);
i = (i + 1) % 26;
Delay(1);
}
}
static void InitTask(Task* pt, const char* name, void(*entry)())
{
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;
StrCpy(pt->name, name, sizeof(pt->name) - 1);
pt->tmain = entry;
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);
}
static void CreateTask()
{
int num = GetAppNum();
while((gAppToRunIndex < num) && (Queue_Length(&gReadyTask) < MAX_READY_TASK))
{
TaskNode* tn = (TaskNode*)Queue_Remove(&gFreeTaskNode);
if(tn != NULL)
{
AppInfo* app = GetAppToRun(gAppToRunIndex);
InitTask(&tn->task, app->name, app->tmain);
Queue_Add(&gReadyTask, &tn->head);
}
else
{
break;
}
gAppToRunIndex++;
}
}
static void ReadyToRunning()
{
QueueNode* node = NULL;
if(Queue_Length(&gReadyTask) == 0)
{
CreateTask();
}
while((Queue_Length(&gReadyTask) > 0) && (Queue_Length(&gRunningTask) < MAX_RUNNING_TASK))
{
node = Queue_Remove(&gReadyTask);
Queue_Add(&gRunningTask, node);
}
}
static void CheckRunningTask()
{
if(Queue_Length(&gRunningTask) == 0)
{
Queue_Add(&gRunningTask, &gIdleTask.head);
}
else if(Queue_Length(&gRunningTask) > 1)
{
if(Queue_Front(&gRunningTask) == &gIdleTask.head)
{
Queue_Remove(&gRunningTask);
}
}
}
void TaskModInit()
{
int i = 0;
Queue_Init(&gFreeTaskNode);
Queue_Init(&gReadyTask);
Queue_Init(&gRunningTask);
Queue_Init(&gWaitingTask);
for(i = 0; i < MAX_TASK_NUM; i++)
{
Queue_Add(&gFreeTaskNode, (QueueNode*)AddrOff(gTaskBuff, i));
}
SetDescValue(AddrOff(gGdtInfo.entry, GDT_TASK_TSS_INDEX), (uint)&gTSS, sizeof(gTSS)-1, DA_386TSS + DA_DPL0);
InitTask(&(gIdleTask.task), "IdleTask", IdleTask);
ReadyToRunning();
CheckRunningTask();
}
void LaunchTask()
{
gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task;
PrepareForRun(gCTaskAddr);
RunTask(gCTaskAddr);
}
void Schedule()
{
ReadyToRunning();
CheckRunningTask();
Queue_Rotate(&gRunningTask);
gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task;
PrepareForRun(gCTaskAddr);
LoadTask(gCTaskAddr);
}
void KillTask()
{
QueueNode* node = Queue_Remove(&gRunningTask);
Queue_Add(&gFreeTaskNode, node);
Schedule();
}
app.h
#ifndef APP_H
#define APP_H
#include "type.h"
typedef struct
{
const char* name;
void (*tmain)();
} AppInfo;
void AppModInit();
AppInfo* GetAppToRun(uint index);
uint GetAppNum();
#endif
app.c
#include "app.h"
#include "utility.h"
#define MAX_APP_NUM 16
static AppInfo gAppToRun[MAX_APP_NUM] = {0};
static uint gAppNum = 0;
void TaskA();
void TaskB();
void TaskC();
void TaskD();
static void RegApp(const char* name, void (*tmain)())
{
if(gAppNum < MAX_APP_NUM)
{
AppInfo* app = AddrOff(gAppToRun, gAppNum);
app->name = name;
app->tmain = tmain;
gAppNum++;
}
}
void AppModInit()
{
RegApp("TaskA", TaskA);
RegApp("TaskB", TaskB);
RegApp("TaskC", TaskC);
RegApp("TaskD", TaskD);
}
AppInfo* GetAppToRun(uint index)
{
AppInfo* ret = NULL;
if(index < MAX_APP_NUM)
{
ret = (AppInfo*)AddrOff(gAppToRun, index);
}
return ret;
}
uint GetAppNum()
{
return gAppNum;
}
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);
}
}
我们在 task.c 中定义了空闲队列、就绪队列、等待队列和运行队列;并定义了最大任务数量为4,运行队列里最多有2个任务,就绪队列里最多有2个任务。
由于 task.c 应该存放内核的代码,但我们却把一些用户的任务也定义在这个源文件里,所以我们新定义了 app.c、app.h 来存放用户的代码。
我们把4个任务搬到了 app.c,在 app.h 中定义了 AppInfo 结构体,里面存放了任务的名字和任务的入口地址;RegApp 函数用于将一个任务名字和入口地址注册到 AppInfo 结构体数组 gAppToRun 中。GetAppToRun 函数通过数组下标拿到一个任务的名字和入口地址。
task.c 中,CreateTask 函数当有任务可以被创建并且就绪队列没满时,取走 gFreeTaskNode 空闲队列的一个空闲任务,拿到一个 app 的名字和入口地址,用来初始化这个任务,最后将这个任务加入就绪队列。
ReadyToRunning 函数:当就绪队列中没有任务时,我们调用 CreateTask 函数来创建任务,并且在就绪队列不为空,运行队列没满时,将就绪队列的队首任务加入到运行队列。
CheckRunningTask 函数:保证一定有一个任务会被执行,当运行队列为空时,就把 IdleTask 任务加入队列,在运行队列中有1个以上的任务时并且队首为 IdleTask 任务时,将 IdleTask 任务移除。
KillTask 函数:将运行完成的任务从运行队列移除,并将这个结点插入到空闲队列中,以便有资源创建新的任务。
实验结果
当前有4个任务,A任务只会运行一段时间,B、C、D 任务会一直运行下去。
当前程序设置的是最多同时运行2个任务,一开始A任务和B任务并行执行,当A任务运行结束时,C任务就开始和B任务并行执行了。
当运行队列中没有任务时,就会将 IdleTask 添加到运行队列中,保证一个会有一个任务会被执行。
一开始A任务在运行,等A任务运行结束,运行队列没有任务时,IdleTask 任务就被加入运行队列,运行 IdleTask 任务。