一个有趣的问题
系统运行后,下面的任务将如何执行?
我们期待的执行结果是 TaskA,TaskB,TaskC 在并行执行
我们可以看到 TaskB 并没有被执行
思考
任务B执行了吗?如果执行了,那么为什么看不到输出?
问题分析
app.c
void AppMain()
{
RegApp("TaskA", TaskA, 255);
RegApp("TaskB", TaskB, 255);
RegApp("TaskC", TaskC, 255);
//RegApp("TaskD", TaskD, 255);
}
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)
#define MAX_TASK_BUFF_NUM (MAX_TASK_NUM + 1)
#define PID_BASE 0x10
static AppInfo* (*GetAppToRun)(uint index) = NULL;
static uint (*GetAppNum)() = NULL;
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 TSS gTSS = {0};
static Queue gFreeTaskNode = {0};
static Queue gReadyTask = {0};
static Queue gRunningTask = {0};
static Queue gWaitingTask = {0};
static TaskNode* gIdleTask = NULL;
static uint gAppToRunIndex = 0;
static uint gPid = PID_BASE;
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()
{
while(1);
}
static void InitTask(Task* pt, uint id, const char* name, void(*entry)(), ushort pri)
{
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 + AppStackSize;
pt->rv.eip = (uint)TaskEntry;
pt->rv.eflags = 0x3202;
pt->id = id;
StrCpy(pt->name, name, sizeof(pt->name) - 1);
pt->tmain = entry;
pt->current = 0;
pt->total = 256 - pri;
SetDescValue(AddrOff(pt->ldt, LDT_VIDEO_INDEX), 0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL3);
SetDescValue(AddrOff(pt->ldt, LDT_CODE32_INDEX), 0x00, PageDirBase - 1, DA_C + DA_32 + DA_DPL3);
SetDescValue(AddrOff(pt->ldt, LDT_DATA32_INDEX), 0x00, PageDirBase - 1, DA_DRW + DA_32 + DA_DPL3);
pt->ldtSelector = GDT_TASK_LDT_SELECTOR;
pt->tssSelector = GDT_TASK_TSS_SELECTOR;
}
static void PrepareForRun(volatile Task* pt)
{
pt->current++;
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, gPid++, app->name, app->tmain, app->priority);
Queue_Add(&gReadyTask, &tn->head);
}
else
{
break;
}
gAppToRunIndex++;
}
}
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()
{
if(Queue_Length(&gRunningTask) > 0)
{
TaskNode* tn = (TaskNode*)Queue_Front(&gRunningTask);
if(!IsEqual(tn, gIdleTask))
{
if(tn->task.current == tn->task.total)
{
Queue_Remove(&gRunningTask);
Queue_Add(&gReadyTask, (QueueNode*)tn);
}
}
}
}
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;
byte* pStack = (byte*)(PageDirBase - MAX_TASK_BUFF_NUM * AppStackSize);
for(i = 0; i < MAX_TASK_BUFF_NUM; i++)
{
TaskNode* tn = (void*)AddrOff(gTaskBuff, i);
tn->task.stack = (void*)AddrOff(pStack, i * AppStackSize);
}
gIdleTask = (void*)AddrOff(gTaskBuff, MAX_TASK_NUM);
GetAppToRun = (void*)(*((uint*)GetAppToRunEntry));
GetAppNum = (void*)(*((uint*)GetAppNumEntry));
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), 0, "IdleTask", IdleTask, 255);
ReadyToRunning();
CheckRunningTask();
}
void LaunchTask()
{
gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task;
PrepareForRun(gCTaskAddr);
RunTask(gCTaskAddr);
}
void Schedule()
{
RunningToReady();
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();
}
TaskA,TaskB,TaskC 的 total 值都为1,任务开始被执行时,TaskA 和 TaskB 被放入执行队列,TaskA 的1次执行时间到了后,就把TaskA 移入到就绪队列,TaskC 加入到执行队列, 然后执行 Rotate 操作,将 TaskC 变换到执行队列的队首,去执行 TaskC,TaskC 的一次执行时间到了后,把 TaskC 移入到就绪队列, TaskA加入到执行队列,然后又执行 Rotate 操作,把 TaskA 变换到执行队列的队首,去执行 TaskA;TaskA 任务完成时,又把 TaskC 加入执行队列,执行 Rotate 操作,将 TaskC 变换到队首,去执行 TaskC,这样 TaskB 就永远无法得到执行了。
问题解决
任务B 得不到执行的关键是 要执行任务的 total 值不能都为1,所以我们需要将一个任务的最小 total 值设置为至少大于1。
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)
#define MAX_TASK_BUFF_NUM (MAX_TASK_NUM + 1)
#define PID_BASE 0x10
#define MAX_TIME_SLICE 260
static AppInfo* (*GetAppToRun)(uint index) = NULL;
static uint (*GetAppNum)() = NULL;
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 TSS gTSS = {0};
static Queue gFreeTaskNode = {0};
static Queue gReadyTask = {0};
static Queue gRunningTask = {0};
static Queue gWaitingTask = {0};
static TaskNode* gIdleTask = NULL;
static uint gAppToRunIndex = 0;
static uint gPid = PID_BASE;
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()
{
while(1);
}
static void InitTask(Task* pt, uint id, const char* name, void(*entry)(), ushort pri)
{
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 + AppStackSize;
pt->rv.eip = (uint)TaskEntry;
pt->rv.eflags = 0x3202;
pt->id = id;
StrCpy(pt->name, name, sizeof(pt->name) - 1);
pt->tmain = entry;
pt->current = 0;
pt->total = MAX_TIME_SLICE - pri;
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, PageDirBase - 1, DA_C + DA_32 + DA_DPL3);
SetDescValue(AddrOff(pt->ldt, LDT_DATA32_INDEX), 0x00, PageDirBase - 1, DA_DRW + DA_32 + DA_DPL3);
pt->ldtSelector = GDT_TASK_LDT_SELECTOR;
pt->tssSelector = GDT_TASK_TSS_SELECTOR;
}
static void PrepareForRun(volatile Task* pt)
{
pt->current++;
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, gPid++, app->name, app->tmain, app->priority);
Queue_Add(&gReadyTask, &tn->head);
}
else
{
break;
}
gAppToRunIndex++;
}
}
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()
{
if(Queue_Length(&gRunningTask) > 0)
{
TaskNode* tn = (TaskNode*)Queue_Front(&gRunningTask);
if(!IsEqual(tn, gIdleTask))
{
if(tn->task.current == tn->task.total)
{
Queue_Remove(&gRunningTask);
Queue_Add(&gReadyTask, (QueueNode*)tn);
}
}
}
}
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;
byte* pStack = (byte*)(PageDirBase - MAX_TASK_BUFF_NUM * AppStackSize);
for(i = 0; i < MAX_TASK_BUFF_NUM; i++)
{
TaskNode* tn = (void*)AddrOff(gTaskBuff, i);
tn->task.stack = (void*)AddrOff(pStack, i * AppStackSize);
}
gIdleTask = (void*)AddrOff(gTaskBuff, MAX_TASK_NUM);
GetAppToRun = (void*)(*((uint*)GetAppToRunEntry));
GetAppNum = (void*)(*((uint*)GetAppNumEntry));
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), 0, "IdleTask", IdleTask, 255);
ReadyToRunning();
CheckRunningTask();
}
void LaunchTask()
{
gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task;
PrepareForRun(gCTaskAddr);
RunTask(gCTaskAddr);
}
void Schedule()
{
RunningToReady();
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();
}
我们定义了宏 MAX_TIME_SLICE 最大时间分片为260,所以一个任务最少执行的次数为 (260 - 255 = 5)次,就不会出现一个在执行队列里的任务永远都无法被执行的情况了。
深入讨论
任务调度的定义
当多个任务处于就绪状态,而处理器只有一个时,操作系统就必须决定哪一个任务先执行
操作系统中做出这种决定的部分称为调度器,调度器使用的算法为调度算法
关于调度算法的分类
非抢占式调度算法
- 任务持续执行直到阻塞 (等待事件),或自愿让出执行权
抢占式调度算法
- 任务每次执行的时间固定,时间一到被迫让出执行权
什么时候进行任务调度?
当任务退出时 (运行结束)
当新任务创建时
当时钟中断发生时
当任务 I/O 中断发生时
任务的分类
计算密集型任务
- 任务大部分时间在使用处理器运算 (如:科学运算)
I/O 密集型任务
- 任务大部分时间用于等待 I/O 操作 (如:文件操作)
关于优先级调度
优先级用于反应任务的重要程度
优先级越高,任务越容易获得执行的机会
- 越多的执行时间 (计算密集型任务)
- 越快的响应速度 (I/O 密集型任务)
调度算法的终极目标
公平公正,每个任务都能被执行
最大化处理器利用率,保持处理器始终忙碌
最小化任务从执行到完成的时间
最快响应时间,快速响应用户交互
课后拓展
FCFS 先到先服务调度算法 (First Come First Served)
SJF 最短执行时间调度算法 (Shortest Job First)
RR 轮询调度算法 (Round Robin)
MQ 多级队列调度算法 (Multilevel Queue)