exec.cc
//*************************************************************************
// MODULE : Exec - Process creation and management module *
// AUTHOR : Ron Chernich *
// PURPOSE: This object contains an array of process descriptors and a *
// number of queues containing array indicies, plus the means *
// to create, destroy and move them around. *
// HISTORY: *
// 21-APR-93 First version *
// 14-MAY-93 Process animation "calls" inserted *
// 02-JUN-93 "Kill" not de-allocating memory *
// 03-FEB-94 Allow un-blocking process to pre-empt current on priority *
// 18-APR-94 Kill and Fork modified to accomodate shared memory *
// 01-SEP-94 File object support added to Fork and Kill *
// 17-SEP-94 LoSked allowed unallocated PID to be scheduled! *
// 21-MAR-94 Quantum made a constructor parameter *
//*************************************************************************
#include "exec.hh" // related header
#include "kernel.hh" // can't put this in header due recursion
#include "fsiface.hh" // ..or this (see header about cheshier cat)
#define PROC0_TEXT 8 // Minimum requirement for process zero code
#define PROC0_STACK 60 // Minimum stack allocation for process zero
#define PROC0_NAME "CONX" // Name for console process
///////////////////////////////////////////////////////////////////////////
// Class Member Functions for the Process Control Stuff
//-------------------------------------------------------------------------
// Initialize Process descriptors to empty, then create (but don't start)
// the primary process - this should be customizable and the Prime Proc
// should be a trusted p-code program - but in this release, for simplicity,
// the process is fixed and coded in C++.
//
Exec::Exec (Knl *pK, UINT16 nMs) : PqReady(TRUE)
{
pTx = pK;
uProcCnt = 0;
nQuantum = nMs;
uCurProc = NO_PROC;
memset(arrPCB, 0, sizeof(PCB) * MAX_PROC);
for (INT16 i = 0; i < MAX_PROC; i++) {
memset(&arrPCB[i], 0, sizeof(PCB));
arrPCB[i].uPid = NO_PROC;
for (INT16 j = 0; j < MAX_DEV; j++)
arrPCB[i].arrDev[j] = NO_PROC;
}
CreateCon();
}
//////////////////
// Somewhat gracefull death for the Execution unit - really the operator
// should delete any running programs, so the only operation required is
// to kill off the console process, but..
//
Exec::~Exec (void)
{
for (INT16 idx = 0; idx < MAX_PROC; idx++)
if (arrPCB[idx].uPid != NO_PROC)
Kill(idx);
}
/////////////////////
// Call here when we want to execute some application code. Run the
// scheduler if there is no current process, otherwise simply call
// the p-code interpreter UNLESS the PID is ZERO (which is special, being
// the first process) in which case we call the specified operator proc.
// After the process has run we do accumulated and quantum time accounting.
//
// If the process has done something causing it to yield, it is put into
// the Blocked Q. If it has run out of Quantum allocation, it gets put
// back into the Ready Q (this process temporarily reduces its priority
// ensures that processes at equal priority run round-robin).
//
// RETURNS: TRUE .. something got run (used by single-step logic)
// FALSE .. no go
//
BOOL Exec::Dispatch (void)
{
MSG msg;
if (uCurProc == NO_PROC)
Sked();
else {
UINT32 lMark = Clock.GetTicks();
if (arrPCB[uCurProc].uPid == 0)
RunCon(arrPCB[uCurProc]);
else
RunProc(arrPCB[uCurProc], arrMCB[uCurProc]);
UINT16 uIntVal = MAX(1, (UINT16)(Clock.GetTicks() - lMark));
arrPCB[uCurProc].luTicks += (UINT32)uIntVal;
arrPCB[uCurProc].nQuantum -= uIntVal;
switch (arrPCB[uCurProc].uStatus) {
case PS_Halted:
case PS_Illegal:
msg = message(ID_Kernel, ((arrPCB[uCurProc].uStatus = PS_Illegal) ?
ANI_HALT_BAD : ANI_HALT_OK), uCurProc);
pTx->SendMsg(ID_ANIM, &msg);
Kill(uCurProc);
uCurProc = NO_PROC;
break;
case PS_Ready:
/* if (arrPCB[uCurProc].nQuantum <= 0) {
PqReady.PqAdd(arrPCB[uCurProc]);
if (arrPCB[uCurProc].nQuantum > PCD_PREEMPT) {
msg = message(ID_Kernel, ANI_RUN_RDY, uCurProc);
pTx->SendMsg(ID_ANIM, &msg);
}
uCurProc = NO_PROC;
}*/
break;
default:
PqBlocked.PqAdd(arrPCB[uCurProc]);
msg = message(ID_Kernel, ANI_RUN_BLK, uCurProc);
pTx->SendMsg(ID_ANIM, &msg);
uCurProc = NO_PROC;
}
return TRUE;
}
return FALSE;
}
////////////////////
// Basic Prime Proc initialization must stop short of allocating memory
// because we won't have a MMU device registered yet!
//
void Exec::CreateCon (void)
{
if (arrPCB[0].uPid == NO_PROC) {
arrPCB[0].uStatus = PS_Zombie;
arrPCB[0].uPid = arrPCB[0].uPidp = 0;
arrPCB[0].nPriority = MAX_PRIORITY;
arrPCB[0].pReply = NULL;
arrPCB[0].arrDev[0] = CLS_VDU;
arrPCB[0].uBP = 1;
if (arrPCB[0].pszName = new char[strlen(PROC0_NAME)+1])
strcpy(arrPCB[0].pszName, PROC0_NAME);
arrPCB[0].uStatus = PS_Created;
PqIn.PqAdd(arrPCB[0]);
++uProcCnt;
}
}
//////////////////
// Called when the supervisor "RUN" button has been clicked for the first
// time - we must complete the Prime Proc initializations that could not
// be done before (we should now have a MMU and FS), then call the Low
// Level Scheduler which should be able to allocate a TTY and line driver
// to the console and move it into the Ready Queue.
//
void Exec::StartCon (void)
{
MSG mess(ID_Kernel, KM_Open, PROC0_TEXT);
pTx->SendMsg(ID_MMU, &mess);
arrMCB[0].hText = mess.wParam;
mess = message(ID_Kernel, KM_Open, PROC0_STACK);
pTx->SendMsg(ID_MMU, &mess);
arrMCB[0].hStack = mess.wParam;
if(arrMCB[0].hText && arrMCB[0].hStack) {
MMU_MSG mms;
INT16 ret_frame[3] = {0, 0, -1 };
UINT16 csp_exec[2] = { 0x08, 0x06 };
mms.pData = (UINT16*)&csp_exec[0], mms.uOffset = 0, mms.uLen = 2;
mess = message(ID_CON, KM_WriteBlk, arrMCB[0].hText, &mms);
pTx->SendMsg(ID_MMU, &mess);
mms.pData = (UINT16*)&ret_frame[0], mms.uOffset = 0, mms.uLen = 3;
mess = message(ID_CON, KM_WriteBlk, arrMCB[0].hStack, &mms);
pTx->SendMsg(ID_MMU, &mess);
arrPCB[0].uSP = 3;
if (LoSked(0))
InitCon();
}
}
////////////////////
// Low level scheduler starts the passed process provided it is able to
// allocate the resources contained in the PID's device list. Called in
// response to a console or batch GO command or to start the Prime Proc.
//
// Note that VDU devices are not allocated direct - we merely see if one
// is free and instantiate a Line Protocol Driver (which will get the TTY).
//
// *** INCOMPLETE: failed startup does not release allocated devices! ***
//
BOOL Exec::LoSked (UINT16 uGo)
{
INT16 idx = 0;
BOOL bCanStart;
bCanStart = (NO_PROC == arrPCB[uGo].uPid) ? FALSE : TRUE;
while (bCanStart && (idx < MAX_DEV) &&
(arrPCB[uGo].arrDev[idx] != NO_PROC)) {
MSG msg;
UINT16 uNeed = arrPCB[uGo].arrDev[idx];
switch (uNeed) {
case CLS_VDU:
msg = message(uGo, KM_Open, CLS_VDU);
pTx->SendMsg(ID_Kernel, &msg);
if (msg.wParam == 0)
bCanStart = FALSE;
else {
msg.wMsgType = KM_Close;
pTx->SendMsg(ID_Kernel, &msg);
arrPCB[uGo].pDev =
new LnDrv(arrPCB[uGo].uPid, ID_LNDRV+arrPCB[uGo].uPid, pTx);
bCanStart = (BOOL)(arrPCB[uGo].pDev != NULL);
}
break;
case CLS_MTU:
break;
case CLS_PRN:
break;
case CLS_FSF:
break;
}
++idx;
}
if (bCanStart) {
arrPCB[uGo].uStatus = PS_Ready;
PqReady.PqAdd(arrPCB[PqIn.PqGet(uGo)]);
MSG msg(ID_Kernel, ANI_IN_RDY, uGo);
pTx->SendMsg(ID_ANIM, &msg);
}
return bCanStart;
}
//////////////////////
// Process scheduler - first, see if there are any blocked processes that
// have become unblocked and if so, feed them back into the right Queue.
// Then, if there is anything at the head of the priority Ready Queue,
// make its context current, preparing it for dispatch to the CPU.
//
// If an unblocking process was blocked on a semaphore wait, we temporarily
// elevate its priority to ensure it will re-schedule ahead of the proc
// that has just signaled and hence been pre-empted (assuming they are at
// the same priority) - so, after insretion to the ready Q, return its
// priority to original.
//
void Exec::Sked (void)
{
INT16 i;
UINT16 arrProcs[MAX_PROC];
PqBlocked.PqImage(arrProcs);
for (i = 0; i < MAX_PROC; i++) {
if (arrProcs[i] == NO_PROC)
break;
if (!(arrPCB[arrProcs[i]].uStatus & PS_Blocked)) {
INT16 idx = PqBlocked.PqGet(arrProcs[i]);
arrPCB[idx].uStatus |= PS_Ready;
if (arrPCB[idx].uSemSet)
++arrPCB[idx].nPriority;
PqReady.PqAdd(arrPCB[idx]);
if (arrPCB[idx].uSemSet)
--arrPCB[idx].nPriority;
MSG msg(ID_Kernel, ANI_BLK_RDY, arrPCB[idx].uPid);
pTx->SendMsg(ID_ANIM, &msg);
}
}
PqSusBlock.PqImage(arrProcs);
for (i = 0; i < MAX_PROC; i++) {
if (arrProcs[i] == NO_PROC)
break;
if (!(arrPCB[arrProcs[i]].uStatus & PS_Blocked)) {
INT16 idx = PqSusBlock.PqGet(arrProcs[i]);
arrPCB[idx].uStatus |= PS_Ready;
PqSusReady.PqAdd(arrPCB[idx]);
MSG msg(ID_Kernel, ANI_SBLK_SRDY, arrPCB[idx].uPid);
pTx->SendMsg(ID_ANIM, &msg);
}
}
PqReady.PqImage(arrProcs);
uCurProc = PqReady.PqGet();
if (uCurProc != NO_PROC) {
arrPCB[uCurProc].nQuantum = nQuantum;
MSG msg(ID_Kernel, ANI_RDY_RUN, uCurProc);
pTx->SendMsg(ID_ANIM, &msg);
}
}
//////////////////////
// The current process has posted a message which the Kernel has decided
// cannot be serviced immediatly. Remove Ready Status and assert Blocked.
// The Dispatcher will do the Queue movements later.
//
void Exec::Block (void)
{
if (uCurProc != NO_PROC) {
arrPCB[uCurProc].uStatus &= ~PS_Ready;
arrPCB[uCurProc].uStatus |= PS_Blocked;
arrPCB[uCurProc].pReply = NULL;
}
}
////////////////
// Suspend this process - always an operator initiated action. Find the
// process and suspend the bugger (can't suspend the running process, only
// ready or blocked ones).
// RETURNS: TRUE .. process suspemded
// FALSE .. unknown process or already suspended!
//
BOOL Exec::Suspend (UINT16 uTarg)
{
if (uTarg == PqReady.PqGet(uTarg)) {
arrPCB[uTarg].uStatus |= PS_Suspended;
PqSusReady.PqAdd(arrPCB[uTarg]);
MSG msg(ID_Kernel, ANI_RDY_SRDY, uTarg);
pTx->SendMsg(ID_ANIM, &msg);
return TRUE;
}
if (uTarg == PqBlocked.PqGet(uTarg)) {
arrPCB[uTarg].uStatus |= PS_Suspended;
PqSusBlock.PqAdd(arrPCB[uTarg]);
MSG msg(ID_Kernel, ANI_BLK_SBLK, uTarg);
pTx->SendMsg(ID_ANIM, &msg);
return TRUE;
}
return FALSE;
}
////////////////
// Resume a Suspend process - always an operator initiated action.
// Find the process and move her back to the appropriate queue.
// RETURNS: TRUE .. process moved
// FALSE .. unknown process or not suspended!
//
BOOL Exec::Resume (UINT16 uTarg)
{
if (uTarg == PqSusBlock.PqGet(uTarg)) {
arrPCB[uTarg].uStatus &= ~PS_Suspended;
PqBlocked.PqAdd(arrPCB[uTarg]);
MSG msg(ID_Kernel, ANI_SBLK_BLK, uTarg);
pTx->SendMsg(ID_ANIM, &msg);
return TRUE;
}
if (uTarg == PqSusReady.PqGet(uTarg)) {
arrPCB[uTarg].uStatus &= ~PS_Suspended;
PqReady.PqAdd(arrPCB[uTarg]);
MSG msg(ID_Kernel, ANI_SRDY_RDY, uTarg);
pTx->SendMsg(ID_ANIM, &msg);
return TRUE;
}
return FALSE;
}
/////////////////
// Add the passed message to the reply message queue of process
// (which will cause it to un-block next time the scheduler runs). In
// this release of RCOS, the reply queue is limited to one message for
// simplicity. If the process was suspended, move it to the Suspended
// Ready queue.
//
// If the unblocking process is free to run and has a higher priority
// than the current process (if any), of the reason for the unblocking
// is (ie an IPC message), the current process gets pre-empted
// by unilaterally clobbering its remaining quantum.
//
void Exec::PostReply (UINT16 uProc, PMSG pM)
{
if (arrPCB[uProc].uPid != NO_PROC) {
arrPCB[uProc].pReply = new MSG(*pM);
arrPCB[uProc].uStatus &= ~PS_Blocked;
if (arrPCB[uProc].uStatus & PS_Suspended) {
if (uProc == PqSusBlock.PqGet(uProc)) {
PqSusReady.PqAdd(arrPCB[uProc]);
MSG msg(ID_Kernel, ANI_SBLK_SRDY, uProc);
pTx->SendMsg(ID_ANIM, &msg);
}
}
else
if ((uCurProc != NO_PROC) &&
((arrPCB[uProc].nPriority > arrPCB[uCurProc].nPriority) ||
(pM->wMsgType == KM_Signal))) {
arrPCB[uCurProc].nQuantum = PCD_PREEMPT;
MSG msg(ID_Kernel, ANI_PREEMPT, uProc);
pTx->SendMsg(ID_ANIM, &msg);
}
}
}
//////////////////////
// Remove all trace of this process and its memory allocation - including
// any shared memory blocks.
// NOTE :: This is VERY incomplete - frinstance, we unilaterally dispose
// of memory - what if the process is sharing text with another, eh?
// RETURNS: TRUE .. process deleted Ok
// FALSE .. invalid process ID
//
BOOL Exec::Kill (UINT16 uTarg)
{
if (arrPCB[uTarg].uPid != NO_PROC) {
MSG msg(ID_Kernel, KM_Close, (UINT16)arrMCB[uTarg].hText);
pTx->SendMsg(ID_MMU, &msg);
arrMCB[uTarg].hText = (HANDLE)ID_NULL;
msg = message(ID_Kernel, KM_Close, arrMCB[uTarg].hStack);
pTx->SendMsg(ID_MMU, &msg);
arrMCB[uTarg].hStack = (HANDLE)ID_NULL;
if (arrPCB[uTarg].pszName) {
DELETE_ARRAY arrPCB[uTarg].pszName;
arrPCB[uTarg].pszName = NULL;
}
if (arrPCB[uTarg].pDev) {
delete arrPCB[uTarg].pDev;
arrPCB[uTarg].pDev = NULL;
}
UINT16 *pn;
while (pn = (UINT16*)arrPCB[uTarg].Share.DblGetHead())
SmClose(uTarg, *pn);
if (arrPCB[uTarg].pFile)
delete arrPCB[uTarg].pFile;
arrPCB[uTarg].pFile = NULL;
arrPCB[uTarg].uPid = NO_PROC;
--uProcCnt;
return TRUE;
}
return FALSE;
}
//////////////////////
// Create a new process - Only way to do this is for the current process
// to fork (are processes like bacteria?), but Process ZERO pulls a trick.
// It is not a p-code process, but it has stack and text segments allocated
// to it which we will duplicate for its child. Before invoking , it
// has set the p-code to and loaded the name of the replacement
// program onto its stack - so when the new process (Child of Conx) gets
// the CPU, it's first action is to load itself. P-code processes that
// fork will duplicate their stack and text, retaining any handles to shared
// memory, semiphores and open streams. Note that shared memory blocks must
// have their usage count increased.
//
// RETURNS: PID Of child created, or
// NO_PROC if process could not be created
//
UINT16 Exec::Fork (void)
{
if (uProcCnt < MAX_PROC) {
for (INT16 i = 1; i < MAX_PROC; i++)
if (arrPCB[i].uPid == NO_PROC)
break;
arrPCB[i].uStatus = PS_Zombie;
MSG msg(ID_Kernel, MMU_Duplicate, arrMCB[uCurProc].hText);
pTx->SendMsg(ID_MMU, &msg);
if (arrMCB[i].hText = (HANDLE)msg.wParam) {
msg = message(ID_Kernel, MMU_Duplicate, arrMCB[uCurProc].hStack);
pTx->SendMsg(ID_MMU, &msg);
if (0 == (arrMCB[i].hStack = (HANDLE)msg.wParam)) {
msg = message(ID_Kernel, KM_Close, arrMCB[i].hText);
pTx->SendMsg(ID_MMU, &msg);
}
else {
++uProcCnt;
arrPCB[i].uPid = i;
arrPCB[i].uPidp = uCurProc;
arrPCB[i].nPriority = DEF_PRIORITY;
arrPCB[i].pReply = NULL;
arrPCB[i].pFile = NULL;
arrPCB[i].uIP = arrPCB[uCurProc].uIP;
arrPCB[i].uSP = arrPCB[uCurProc].uSP;
arrPCB[i].uBP = arrPCB[uCurProc].uBP;
arrPCB[i].uStatus = PS_Created;
PqIn.PqAdd(arrPCB[i]);
UINT16 *pn = (UINT16*)arrPCB[uCurProc].Share.DblGetHead();
while (pn) {
ShareMem.QmIncCnt(*pn);
arrPCB[i].Share.DblAppend((void*)pn, sizeof(UINT16));
pn = (UINT16*)arrPCB[uCurProc].Share.DblGetNext();
}
msg = message(ID_Kernel, ANI_FORKS, i);
pTx->SendMsg(ID_ANIM, &msg);
return i;
}
}
}
return NO_PROC;
}
////////////
// Take a "snapshot" of the current contents for the Animator..
//
void Exec::GetQcom (UINT16 uWhich, PMSG_ANIQ pUarr)
{
if (uWhich == 0) {
PqIn.PqImage(pUarr->arr[0]);
PqReady.PqImage(pUarr->arr[1]);
PqBlocked.PqImage(pUarr->arr[2]);
PqSusReady.PqImage(pUarr->arr[3]);
PqSusBlock.PqImage(pUarr->arr[4]);
}
else {
switch ((uWhich >> 8) & 0xff) {
case INPUT_Q: PqIn.PqImage(pUarr->arr[0]); break;
case READY_Q: PqReady.PqImage(pUarr->arr[0]); break;
case BLOKED_Q: PqBlocked.PqImage(pUarr->arr[0]); break;
case SUSRDY_Q: PqSusReady.PqImage(pUarr->arr[0]); break;
case SUSBLK_Q: PqSusBlock.PqImage(pUarr->arr[0]); break;
}
switch (uWhich & 0xff) {
case INPUT_Q: PqIn.PqImage(pUarr->arr[1]); break;
case READY_Q: PqReady.PqImage(pUarr->arr[1]); break;
case BLOKED_Q: PqBlocked.PqImage(pUarr->arr[1]); break;
case SUSRDY_Q: PqSusReady.PqImage(pUarr->arr[1]); break;
case SUSBLK_Q: PqSusBlock.PqImage(pUarr->arr[1]); break;
}
}
}
/////////////
// Fill in details of current process for animator..
//
void Exec::GetPcom (PMSG_ANIP pM)
{
if (uCurProc == NO_PROC)
pM->uNr[0] = NO_PROC;
else {
pM->uNr[0] = arrPCB[uCurProc].uPid;
pM->uNr[1] = arrPCB[uCurProc].uPidp;
pM->uNr[2] = arrPCB[uCurProc].uBP;
pM->uNr[3] = arrPCB[uCurProc].uIP;
pM->uNr[4] = arrPCB[uCurProc].uSP;
pM->uNr[5] = arrPCB[uCurProc].uTos;
pM->uNr[6] = arrPCB[uCurProc].nQuantum;
pM->nPri = arrPCB[uCurProc].nPriority;
pM->pszName = arrPCB[uCurProc].pszName;
DeCode(arrPCB[uCurProc].lCode, pM->szPCD);
}
}
///////////////////////////////////////////////////////////////////////////
// Class Member Functions for the Priority Queue
//-------------------------------------------------------------------------
// Create a priority queue for processes. No limit placed on size, since
// no more than MAX_PROC can ever be created - so even if they all arrive
// in this queue at the same time (unlikeley), the total size is limited.
// Ordered instatntiations will hold entries in descending priority.
//
Pque::Pque (BOOL bOrd) : DblList(DESCENDING)
{
bOrdered = bOrd;
}
Pque::~Pque (void)
{
PQMBR pD = (PQMBR)DblGetHead();
while (pD) {
DblDelete();
pD = (PQMBR)DblGetNext();
}
}
//////////
// Check for any elements in queue
// RETURNS: TRUE .. yes there is
// FALSE .. no there 'aint
//
BOOL Pque::PqEmpty (void)
{
return ((DblGetHead() == NULL) ? TRUE : FALSE);
}
///////////
// Add a PID to this queue. If the queue is ordered, insert at priority
// one less than actual, then adjust back to correct. This ensures that
// processes at equal priority are served Round-Robin.
//
void Pque::PqAdd (PCB& proc)
{
QMBR temp;
temp.uPid = proc.uPid;
temp.nKey = proc.nPriority - 1;
if (!bOrdered)
DblAppend((void*)&temp, sizeof(QMBR));
else {
PQMBR pLoc = (PQMBR)DblInsert((void*)&temp, sizeof(QMBR));
pLoc->nKey++;
}
}
/////////////////
// Remove member from head queue - use with ordered queue to "get" member
// with highest "key" (normally priority, but could be quantum, whatever..)
// RETURNS: PID # removed, or
// NO_PROC if queue empty
//
UINT16 Pque::PqGet (void)
{
PQMBR pD = (PQMBR)DblGetHead();
if (pD) {
UINT16 uPid = pD->uPid;
DblDelete();
return uPid;
}
return NO_PROC;
}
/////////////////
// Remove member with passed process ID from queue
// RETURNS: PID # removed, or
// NO_PROC if queue empty
//
UINT16 Pque::PqGet (UINT16 uTarg)
{
PQMBR pD = (PQMBR)DblGetHead();
while (pD)
if (pD->uPid != uTarg)
pD = (PQMBR)DblGetNext();
else {
UINT16 uPid = pD->uPid;
DblDelete();
return uPid;
}
return NO_PROC;
}
//////////////////
// See if passed PID is in this list
// RETURNS: TRUE .. yes it is
// FALSE .. no it ain't
//
BOOL Pque::PqFind (UINT16 uTarg)
{
PQMBR pD = (PQMBR)DblGetHead();
while (pD) {
if (pD->uPid == uTarg)
return TRUE;
pD = (PQMBR)DblGetNext();
}
return FALSE;
}
/////////////
// The passed pointer references an array [MAX_PROC] of UINT. This routine
// copies the queues PID numbers into the array, setting empty elements to
// 0xffff (why didn't I just use signed ints in the first place?).
//
void Pque::PqImage (UINT16 *p)
{
PQMBR pD = (PQMBR)DblGetHead();
for (INT16 i = 0; i < MAX_PROC; i++) {
*(p + i) = ((pD) ? pD->uPid : NO_PROC);
if (pD)
pD = (PQMBR)DblGetNext();
}
}
/////////////////////////////////// eof ////////////////////////////////////