前言
先前我们实现了多个任务并行执行。然而没有考虑任务结束后应该怎么办。因此存在两个问题:
- iret跳转到进程入口函数执行并非函数调用(没有将返回地址压入栈的操作),函数无法返回,也就无法销毁此任务
- 当前任务主动结束后应该如何处理此任务,如何具体地销毁当前任务并且执行下一个任务?
销毁任务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(); } }
|
再添加一个TaskEntry全局入口函数,eip指向TaskEntry,而Tmain指向具体任务(函数)。
这样可以真正的调用新函数并且正常返回
中断提升特权级
只有在内核态才能销毁任务,然而这里任务入口函数返回之后仍然处于用户态。
既然要陷入内核态就可以借助中断来完成
特权级升高可以通过内部中断(软中断)来完成,自行设计参数比如ax=0; int 0x80
然后设计相应的处理函数即可
- 实现void SysCallHandler(ushort ax)中断服务程序
- 设置IDT中0x80号中断描述符的ISR入口地址
- 当ax== 0时,调用KillTask()销毁当前任务(主动结束)
1 2 3 4 5
| 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(); } asm volatile( "movl $0, %eax \n" "int $0x80 \n" ); }
|
80号中断处理函数完成释放任务资源
killTask需要释放资源,所谓资源就是内存资源和CPU资源
任务生命周期:任务从开始执行到结束执行经过的时间
任务的状态:任务在生命周期会经历不同的状态
任务切换如何实现呢?可以通过不同的队列实现
- 为每种状态准备内核队列 (就绪队列, 执行队列, 等待队列)
- 任务创建后立即进入就绪队列
- 调度器根据当前执行的任务数量决定调度策略
执行时间结束的进入就绪队列
等待外部事件的任务的进入等待队列,等待队列中的任务必须先进入就绪队列才能执行
本质而言就是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;
static TaskNode gTaskBuff[MAX_TASK_BUFF_NUM] = {0}; static Queue gAppToRun = {0}; static Queue gFreeTaskNode = {0}; 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
| 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; 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(); CheckRunningTask(); Queue_Rotate(&gRunningTask); gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task; PrepareForRun(gCTaskAddr); 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导致后续任务一直等待在肯定不合理,因此需要给任务提供优先级,优先级的高低决定任务执行时间的长短。达到优先级的时间则此任务会强制调度回就绪队列。
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() { RunningToReady(); ReadyToRunning(); CheckRunningTask(); Queue_Rotate(&gRunningTask); gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task; PrepareForRun(gCTaskAddr); LoadTask(gCTaskAddr); }
|