问题
完整实现等待机制还有哪些工作要做?
关键实现步骤
新增 Wait(const char* name) 系统调用接口
拓展 RunningToWaiting() 内核函数:参数化等待队列
拓展 WaitingToReady() 内核函数:参数化等待队列
实现 FindTaskByName():通过名字查找任务数据结构
关键问题
任务分散于各个队列中,如何快速通过名字查找目标任务?
边界情况处理
当目标任务不存在时,立即返回,无需阻塞
任务不能等待"IdleTask",Wait("IdleTask") 将立即返回
进程等待机制的实现
task.h
typedef struct
{
RegValue rv;
Descriptor ldt[3];
ushort ldtSelector;
ushort tssSelector;
void (*tmain)();
uint id;
ushort current;
ushort total;
char name[16];
Queue wait;
byte* stack;
} Task;
syscall.c
void Wait(const char* name)
{
SysCall(0, 1, name, 0);
}
ihandler.c
void SysCallHandler(uint type, uint cmd, uint param1, uint param2) // __cdcel__
{
switch(type)
{
case 0:
TaskCallHandler(cmd, param1, param2);
break;
case 1:
MutexCallHandler(cmd, param1, param2);
break;
default:
break;
}
}
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(
"movl $0, %eax \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);
Queue_Init(&pt->wait);
pt->tmain = entry;
pt->current = 0;
pt->total = MAX_TIME_SLICE - pri;
SetDescValue(AddrOff(pt->ldt, LDT_VIDEO_INDEX), 0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL3);
SetDescValue(AddrOff(pt->ldt, LDT_CODE32_INDEX), 0x00, KernelHeapBase - 1, DA_C + DA_32 + DA_DPL3);
SetDescValue(AddrOff(pt->ldt, LDT_DATA32_INDEX), 0x00, KernelHeapBase - 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 WaitingToReady(Queue* wq)
{
while(Queue_Length(wq) > 0)
{
TaskNode* tn = (TaskNode*)Queue_Front(wq);
PrintString("W2R: ");
PrintString(gCTaskAddr->name);
PrintString(" => ");
PrintString(tn->task.name);
PrintChar('\n');
Queue_Remove(wq);
Queue_Add(&gReadyTask, (QueueNode*)tn);
}
}
static void RunningToWaiting(Queue* wq)
{
if(Queue_Length(&gRunningTask) > 0)
{
TaskNode* tn = (TaskNode*)Queue_Front(&gRunningTask);
if(!IsEqual(tn, gIdleTask))
{
Queue_Remove(&gRunningTask);
Queue_Add(wq, (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*)(AppHeapBase - 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);
}
static void ScheduleNext()
{
ReadyToRunning();
CheckRunningTask();
Queue_Rotate(&gRunningTask);
gCTaskAddr = &((TaskNode*)Queue_Front(&gRunningTask))->task;
PrepareForRun(gCTaskAddr);
LoadTask(gCTaskAddr);
}
void Schedule()
{
RunningToReady();
ScheduleNext();
}
void MtxSchedule(uint action)
{
if(IsEqual(action, NOTIFY))
{
WaitingToReady(&gWaitingTask);
}
else if(IsEqual(action, WAIT))
{
RunningToWaiting(&gWaitingTask);
ScheduleNext();
}
}
void KillTask()
{
QueueNode* node = Queue_Remove(&gRunningTask);
Task* pt = &((TaskNode*)node)->task;
pt->id = 0;
WaitingToReady(&pt->wait);
Queue_Add(&gFreeTaskNode, node);
ScheduleNext();
}
static Task* FindTaskByName(const char* name)
{
Task* ret = NULL;
if(!StrCmp(name, "IdleTask", -1))
{
int i = 0;
for(i = 0; i < MAX_TASK_NUM; i++)
{
TaskNode* tn = AddrOff(gTaskBuff, i);
if(tn && tn->task.id && StrCmp(name, tn->task.name, -1))
{
ret = &tn->task;
break;
}
}
}
return ret;
}
static void WaitTask(const char* name)
{
if(name)
{
Task* pt = FindTaskByName(name);
if(pt)
{
PrintString("WT: ");
PrintString(gCTaskAddr->name);
PrintString(" wait ");
PrintString(pt->name);
PrintChar('\n');
RunningToWaiting(&pt->wait);
ScheduleNext();
}
}
}
void TaskCallHandler(uint cmd, uint param1, uint param2)
{
switch(cmd)
{
case 0:
KillTask();
break;
case 1:
WaitTask((char*)param1);
break;
default:
break;
}
}
app.c
void AppMain()
{
RegApp("CookRice", CookRice, 255);
RegApp("CookDish", CookDish, 255);
RegApp("HaveDinner", HaveDinner, 255);
}
void CookRice()
{
SetPrintPos(0, 12);
PrintString(__FUNCTION__);
PrintChar('\n');
for(i = 0; i < 50; i++)
{
SetPrintPos(12, 12);
PrintChar('A' + i % 26);
Delay(1);
}
}
void CookDish()
{
SetPrintPos(0, 14);
PrintString(__FUNCTION__);
PrintChar('\n');
for(i = 0; i < 30; i++)
{
SetPrintPos(12, 14);
PrintChar('0' + i % 10);
Delay(1);
}
}
void HaveDinner()
{
Wait("CookDish");
Wait("CookRice");
SetPrintPos(10, 16);
PrintString("Having dinner...");
}
Task 任务结构体中新增了 wait 队列,这个队列为任务等待队列。新增了函数 wait,这是一个系统调用接口,用来等待一个任务执行结束。当调用这个函数时,将当前任务放到要等待的任务的等待队列中,当要等待的任务运行结束时,再把这个执行结束的等待队列中的任务放回就绪队列,等待调度执行。
我们在一个任务运行结束时,把该任务的 id 号标记为0,代表任务已运行结束。通过任务名查找任务,只查找还在运行并且不是 IdleTask 的任务(id 不为0)。
在等待 CookRice 和 CookDish 执行结束时,HaveDinner 才开始执行。
经典问题一
TaskA 想要占用相同的锁,从而造成死锁。
经典问题二
当 TaskA 获取了互斥锁A,想要获取互斥锁B,而 TaskB 获取了互斥锁B,想要获取互斥锁A时,就会产生死锁。
经典问题三
TaskA 等待 TaskB 运行结束时,再向下执行;而 TaskB 等待 TaskA 运行结束时,再向下执行,从而造成了死锁。
死锁问题
现象描述
- 任务因所需的资源无法获得而进入无限阻塞的状态
发生的条件
- 互斥:任务需要对资源进行互斥访问 (临界资源)
- 持有并等待:任务获得了资源,同时又在等待其他资源
- 循环等待:任务之间在相互等待 (等待路径形成环路)
- 非抢占:任务已获得的资源不能被抢夺
小结
任务之间除了互斥关系还有等待关系
任务之间的等待可以看作对执行时间的互斥
任务之间可能会因为对多个临界资源的竞争而产生死锁
可根据死锁发生的条件精心设计多任务,避免死锁的发生