/* File: Switch.c Contains: Process switching routines. Written by: Erich Ringewald Copyright: © 1986-1992 by Apple Computer, Inc., all rights reserved. Change History (most recent first): <22> 10/28/92 DTY Use new Get/Set macros to access ExpandMem. <21> 10/27/92 DTY Change BlockMoves to BlockMoveData. <20> 9/11/92 DRF Take out Òwhile(FSBUSY);Ó in ÒTheFutureÓ to allow for faster switching. <19> 8/26/92 DTY Roll in latest changes for A/UX. <18> 3/30/92 DTY #1025416,: Tell Skia to switch itÕs globals on a context switch. <17> 3/23/92 JSM OSEvents.h is obsolete, use Events.h. <16> 2/18/92 DTY #1021226: Switch emMessageManagerGlobals again. <15> 10/4/91 JSM Change PsychoticFarmerOrLater conditionals to TheFuture. <14> 9/22/91 DTY Change PsychoticFarmerAndLater to PsychoticFarmerOrLater. <13> 9/13/91 DTY Conditionalise previous change so it doesnÕt get built for CubeE. (ItÕll get built for PsychoticFarmerAndLater.) <12> 6/4/91 DFH Switch emMessageManagerGlobals. <11> 1/28/91 DFH JSM,#81425:Include MFPrivate.h, since that's where DisableSwitching and EnableSwitching are now. <10> 1/18/91 DFH (ewa) Fix kill_vbl check of VBL record address. Was checking the wrong element. <9> 1/15/91 DFH (VL) Conditionalize out the AUX switch prototypes. <8> 1/14/91 DFH (JDR) Conditionalize out AUX support. <6> 12/19/90 gbm (dba) Fix off-by-one bug in an A/UX change. Also kill VBLs whose queue entries are in an applicationÕs heap as well as those whose task pointers are. <5> 12/5/90 DFH Integrated AUX support. <4> 11/6/90 DFH Renamed emAppleEventsGlobal to emAppleEvents. <3> 11/1/90 DFH Unconditionalized edition mgr lomem switch. <0> 9/2/86 ELR New Today. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Glue.h" #include "Lomem.h" #include "Data.h" #include "SysMisc.h" #include "Patches.h" #include "Aux.h" #ifdef MORE_SEGMENTS #pragma segment kernel_segment #endif MORE_SEGMENTS void SwitchSkiaGlobals(ProcessSerialNumber theNewProcess) = {0x303C, 0xFFFE, 0xA832}; /* Some function prototypes that should be in (yet another) header file */ void save_or_restore_dispatch(PCB *, int); void kill_dispatch(PCB *); void SwitchAllRegs(unsigned long *, unsigned long); void SwitchCPURegs(unsigned long *, unsigned long); #ifdef HAS_AUX_PROCESSMGR void aux_ctxsw (unsigned long *, unsigned long, long); void aux_fpxsw (unsigned long *, unsigned long, long); #endif HAS_AUX_PROCESSMGR void save_lomem(PEntryPtr); void restore_lomem(PEntryPtr, Boolean); void save_lmemtab(Ptr); void restore_lmemtab(Ptr); /* Function prototypes internal to this file */ void save_state(PEntryPtr); void save_dce(PEntryPtr); void save_vbl(PEntryPtr); void AtomicVBLSave(Handle, unsigned long, unsigned long); void save_hooks(PEntryPtr); unsigned short restore_state(PEntryPtr, Boolean); void restore_dce(PEntryPtr); void restore_vbl(PEntryPtr); void restore_hooks(PEntryPtr); void kill_dce(THz); void GiveGoodByeKiss(DCtlPtr); void kill_vbl(PEntryPtr); void kill_hooks(void); /* Scheduling constant to cause applications to yield the CPU even if there is * is no other reason to switch. For foreground processes, this is checked * only when there are no pending events. For background processes, it is * checked before events are. Of course, the process can continue with the * processor (get a new quantum right away), if there are no other processes * ready to be scheduled. */ #define QUANTUM (5) /* switch_task. Switch to the specified process. The outgoing process, * pCurrentProcess, can be nil. It happens when switching out of the dead * app in c_exittoshell(). */ void switch_task(register PEntryPtr pNewProc) { register PEntryPtr pOldProc; unsigned short ps; #ifdef HAS_AUX_PROCESSMGR long auxPID; #endif HAS_AUX_PROCESSMGR unsigned long olda5; Ptr ourQDGlobals; unsigned long *oldStackSave; assert((pNewProc != nil) && (IsProcessEntry(pNewProc)) && (pNewProc->p_state != PRSLEEPING)); /* Save Process Mgr a5 and proc globals */ olda5 = ProcessMgrA5Setup(); ourQDGlobals = *((Ptr *) olda5); /* Do nothing if this would all be a NOP */ if ((pOldProc = pCurrentProcess) == pNewProc) { A5Restore(olda5); return; } /* Let the error handling routines know we can't handle switching now, * but only do it if we'll see the code on the other side of SwitchxxxRegs(). */ if (pNewProc->p_slices > 0) DisableSwitching(); #if TheFuture // We don't do anything special for old apps anymore #else /* Wait until file system and AppleShare async calls are done. * NOTE: This is done for the sole reason that MacWrite (4.5??) has an * HFS completion routine that doesn't set up A5. If we can workaround * (or forget) them, we should. This would let switches go faster, and * allow apps to sleep pending I/O completion (i.e. WNE with big sleep, * and an I/O completion routine that calls WakeupProcess). */ #ifdef HAS_AUX_PROCESSMGR /* No need to wait for synchronous FM since A/UX does it asynchronously */ if (! AUXIsPresent) #endif HAS_AUX_PROCESSMGR while (FSBUSY) ; #endif TheFuture // <18> Call Skia to let it know that a context switch is occuring if (skiaExists) SwitchSkiaGlobals(pNewProc->p_serialNumber); /* Update count of switched-in time for outgoing process. * NOTE: On TICKS rollover we don't bother to updateÉ who'll miss 'em? * Assume we never have to worry about p_activeTime rollover. */ if ((lastswitch < TICKS) && (pOldProc != nil)) pOldProc->p_activeTime += (TICKS - lastswitch); lastswitch = TICKS; /* Update count of slices for incoming process */ pNewProc->p_slices++; /* Set time of next quantum cut (even works if TICKS is about to roll over) */ nextswitch = TICKS + QUANTUM; /* Invalidate current process information during switch */ pCurrentProcess = nil; /* Save outgoing state, set up incoming state */ save_state(pOldProc); ps = restore_state(pNewProc, false); /* Everything is in place. It's time for the final act of switching the CPU * context from the old to the new. The SwitchAllRegs and SwitchCPURegs routines * are entered by the old process, but are returned from by the new one. The most * interesting part is that this switches the stack pointer: all local variables * used after this use the values that the incomer saved when it was the outgoer. */ pCurrentProcess = pNewProc; oldStackSave = (pOldProc != nil) ? &pOldProc->p_sp : nil; #ifdef HAS_AUX_PROCESSMGR if (AUXIsPresent) { auxPID = ((pOldProc == nil) || (pNewProc->aux_realpid != pOldProc->aux_realpid)) ? pNewProc->aux_realpid : 0; if (MachineHasFPU) aux_fpxsw(oldStackSave, pNewProc->p_sp, auxPID); else aux_ctxsw(oldStackSave, pNewProc->p_sp, auxPID); } else { #endif HAS_AUX_PROCESSMGR if (MachineHasFPU) SwitchAllRegs(oldStackSave, pNewProc->p_sp); else SwitchCPURegs(oldStackSave, pNewProc->p_sp); #ifdef HAS_AUX_PROCESSMGR } #endif HAS_AUX_PROCESSMGR /* Restore interrupt level to what old guy saved before passing the torch */ spl(ps); /* let the error handling routines know we can handle switching again */ EnableSwitching(); /* Restore his A5 world and his quickdraw globals */ A5Restore(olda5); *((Ptr *) olda5) = ourQDGlobals; /* Now kill him! */ if (pCurrentProcess->p_condemned) { DSERRCODE = noErr; ExitToShell(); } } #pragma segment kernel_segment /* save_state. Save all process specific stuff in the process state records. * We make an effort to remove all executable things (drivers, vbls, etc) * before switching the patches or lomem. This guarantees that those * executables never run without the context the need. * NOTE: Should we switch timer tasks? * NOTE: VBLs, drivers, and DAs in the system heap are not switched out by * this code, so they better not rely on low memory and patches being * fair game. Not only that, but they shouldn't rely on TWGetPID to tell * them, because they'll see an incorrect value during the switch. */ void save_state(PEntryPtr pPEntry) { PCB **pcbHdl; /* Weed out the degenerates */ if ( (pPEntry == nil) || (pcbHdl = pPEntry->p_pcb) == nil) return; /* Disarm executables */ save_dce(pPEntry); save_vbl(pPEntry); save_hooks(pPEntry); /* Remove the guy's patches and save off low memory */ save_or_restore_dispatch(*pcbHdl, SAVE_DISPATCH); save_lomem(pPEntry); } /* restore_state. Restores all process specific stuff from the state records. * NOTE: Interrupts must be turned off while restoring volatile portions of * low memory (e.g. STKLOWPT), because they may be affected by the interrupts * and VBLs that execute even as we speak. */ unsigned short restore_state(register PEntryPtr pPEntry, Boolean shouldRestoreVolatile) { u_short ps; if (pPEntry->p_pcb == nil) return(0); /* Restore data context (lomem) */ restore_lomem(pPEntry, shouldRestoreVolatile); /* Now restore executable context */ save_or_restore_dispatch(*pPEntry->p_pcb, RESTORE_DISPATCH); restore_dce(pPEntry); restore_vbl(pPEntry); restore_hooks(pPEntry); /* Restore volatile state with interrupts off. These were deferred by * restore_lomem() until we got to this section with interrupts off. * Please excuse the lack of layering. * NOTE: To leave interrupts off even less we could lift this into * switch_task() itself. */ if (shouldRestoreVolatile == false) { ps = disable(); STKLOWPT = (*pPEntry->p_pcb)->stklowpt; } return(ps); } /* kill_state. Chuck the switched context of the specified process. The idea here is * a) neutralize the context, and b) make sure that the info is lost so that another * save_state/restore_state would not re-install any of the custom context. This is * important when calling StandardLaunch in HandleShellDeath. We want the restore_state * at the end of NewProcess to not re-install patches, dces, etc. The dces, vbls, and * hooks are naturally handled, since the save_state will see genericness. The dispatch * table, tho, must be explicitly removed now. */ void kill_state(PEntryPtr pPEntry) { PCB **pcbHdl; /* Do nothing if app had no state info */ if ((pcbHdl = pPEntry->p_pcb) == nil) return; /* Neutralize! */ kill_dispatch(*pcbHdl); kill_dce((*pcbHdl)->applzone); kill_vbl(pPEntry); kill_hooks(); } /* Save_lomem copies the current low memory state for the process described * by `pp'. * * Low memory locations can be divided into 4 classes: * (1) Undefined Memory: Doesn't matter what we do with these * (2) Application Specific Memory: Must be switched. * (3) Static System Memory: Must be switched. * Only examples so far: BOOTVOL (SWITCHDATA?). * (4) Dynamic System Memory: Must not be switched. * * Low memory should be switched out only after all the executable objects that * use the low memory have been switched out. These executables include VBLs, * drivers, and "hooks". * * NOTE: Assumes A5 = PROCESSMGRGLOBALS */ void save_lomem(PEntryPtr pp) { PCB *pc; assert(pp->p_pcb != nil && pp->p_lmemtool != nil); pc = *pp->p_pcb; /* Save ExpandMem switchables */ pc->emScriptAppGlobals = GetExpandMemScriptAppGlobals(); pc->emAppleEvents = GetExpandMemAppleEvents(); pc->emEditionMgrPerApp = GetExpandMemEditionMgrPerApp(); pc->emMessageManagerGlobals = GetExpandMemMessageManagerGlobals(0); /* Save OS lomem */ pc->bootvol = BOOTVOL; pc->switchdata = SWITCHDATA; pc->defvcbptr = DEFVCBPTR; pc->defaultwdcb = *((WDCBPtr) (WDCBSPTR + 2)); pc->defvrefnum = DEFVREFNUM; pc->dserrcode = DSERRCODE; /* Save toolbox lomem */ save_lmemtab(*pp->p_lmemtool); /* Save randomly accessed toolbox lomem */ pc->stklowpt = STKLOWPT; pc->appllimit = APPLLIMIT; pc->applzone = APPLZONE; pc->currenta5 = (unsigned long)CURRENTA5; BlockMoveData(CURAPNAME, &pc->curapname, *((unsigned char *)CURAPNAME) + 1); pc->curlayer = GetCurLayer(); pc->topmaphandle = TOPMAPHANDLE; pc->curmap = CURMAP; if (Colorized) pc->menucinfo = MENUCINFO; pc->sfsavedisk = SFSAVEDISK; pc->curdirstore = CURDIRSTORE; } /* restore_lomem. Switches in the low memory settings of the given process. * Low memory should be switched in before any executable objects that * use the low memory have been switched in. These executables include VBLs, * drivers, and "hooks". */ void restore_lomem(PEntryPtr pp, Boolean shouldRestoreVolatile) { PCB *pc; unsigned short ps; assert(pp->p_pcb != nil && pp->p_lmemtool != nil); pc = *pp->p_pcb; /* Restore location zero to it's startup time value */ LOCATIONZERO = initLocationZero; /* Restore ExpandMem switchables */ SetExpandMemScriptAppGlobals(pc->emScriptAppGlobals); SetExpandMemAppleEvents(pc->emAppleEvents); SetExpandMemEditionMgrPerApp(pc->emEditionMgrPerApp); SetExpandMemMessageManagerGlobals(0, pc->emMessageManagerGlobals); /* Restore OS lomem */ BOOTVOL = pc->bootvol; SWITCHDATA = pc->switchdata; DEFVCBPTR = pc->defvcbptr; *((WDCBPtr) (WDCBSPTR + 2)) = pc->defaultwdcb; DEFVREFNUM = pc->defvrefnum; DSERRCODE = pc->dserrcode; /* Restore toolbox lomem */ restore_lmemtab(*pp->p_lmemtool); /* Restore interrupt sensitive lomem. */ if (shouldRestoreVolatile) { ps = disable(); STKLOWPT = pc->stklowpt; APPLZONE = pc->applzone; spl(ps); } else { STKLOWPT = 0; APPLZONE = pc->applzone; } /* Restore randomly accessed toolbox lomem. * NOTE: These must be done after the other toolbox entries since we may * explicitly store values here (e.g. newproc()). */ APPLLIMIT = pc->appllimit; CURRENTA5 = pc->currenta5; BlockMoveData(&pc->curapname, CURAPNAME, Length(&(pc->curapname)) + 1); SetCurLayer(pc->curlayer); TOPMAPHANDLE = pc->topmaphandle; CURMAP = pc->curmap; if (Colorized) MENUCINFO = pc->menucinfo; SFSAVEDISK = pc->sfsavedisk; CURDIRSTORE = pc->curdirstore; } /* save_vbl. Go through all of the current VBL tasks and replace the receiver * addresses with dummies, and set all counts to a large number, making the VBL * a NOP. * This must be called before save_or_restore_dispatch and save_lomem, to ensure * that no VBLs run without their required context. */ void save_vbl(PEntryPtr pp) { unsigned long base, bound; base = (u_long) pp->p_zone; bound = base + pp->p_size; AtomicVBLSave((*pp->p_pcb)->vblvars, base, bound); } /* restore_vbl. Reinstate the VBLs that were swapped out by previous save_vbl. */ void restore_vbl(PEntryPtr pp) { VBLTask *pVBL; VBLDescPtr pVBLSave; VBLDescHdl hVBLSave; short vblDescCount; unsigned short ps; assert(pp->p_pcb != nil); hVBLSave = (*pp->p_pcb)->vblvars; vblDescCount = (u_long) GetHandleSize((Handle) hVBLSave) / sizeof(VBLDesc); pVBLSave = *hVBLSave; while (--vblDescCount >= 0) // <6> { pVBL = pVBLSave->v_eladdr; assert(pVBL->qType == vType); ps = disable(); pVBL->vblCount = pVBLSave->v_count; pVBL->vblAddr = pVBLSave->v_addr; spl(ps); pVBLSave++; } } /* kill_vbl. Remove all VBL tasks local to the given process. * Note that under A/UX tasks above PhysMemTop should be killed as well. */ void kill_vbl(PEntryPtr pProc) { register VBLTask *pThisVBL, *pNextVBL; register unsigned long vblAddr, base, bound; #ifdef HAS_AUX_PROCESSMGR register unsigned long secondBound; #endif HAS_AUX_PROCESSMGR base = (unsigned long) pProc->p_zone; bound = base + pProc->p_size; #ifdef HAS_AUX_PROCESSMGR secondBound = (AUXIsPresent) ? PHYSMEMTOP : (0xFFFFFFFF); #endif HAS_AUX_PROCESSMGR pNextVBL = VBLQHDR->qHead; while (pNextVBL != nil) { pThisVBL = pNextVBL; pNextVBL = pNextVBL->qLink; vblAddr = StripAddress((Ptr) pThisVBL->vblAddr); if (((((unsigned long) pThisVBL) >= base) && (((unsigned long) pThisVBL) <= bound)) || ((vblAddr >= base) && (vblAddr <= bound #ifdef HAS_AUX_PROCESSMGR || ( AUXIsPresent && vblAddr > secondBound) #endif ))) (void) VRemove(pThisVBL); } } /* save_dce. Switch out all drivers/DAs associated the specified process. * This should be called before save_or_restore_dispatch and save_lomem. */ void save_dce(PEntryPtr pp) { DCtlHandle *h; u_short i, n, daNumber; u_long crit, base, bound; DCEDescPtr del; assert(pp->p_pcb != nil); daNumber = (*(pp->p_pcb))->p_daResource; base = (unsigned long) pp->p_zone; bound = base + pp->p_size; /* tally up the DAs/drivers we'll be switching */ h = UNITTABLE; i=0; n=0; while (n < UNITNTRYCNT) { if (*h != nil) { /* Is it a DA handler one? */ if (n == daNumber) i++; /* swap local drivers */ else { if ( (((crit = (unsigned long) (**h)->dCtlWindow) == 0) && ((crit = (unsigned long) (**h)->dCtlStorage) == 0)) ) crit = (unsigned long) (**h)->dCtlDriver; crit = (u_long) StripAddress((Ptr) crit); if ( (crit >= base) && (crit < bound) ) i++; } } h++; n++; } /* Make handle correct size (back to 0 if drivers all went away) */ SetHandleSize((*pp->p_pcb)->dces, i*sizeof(DCEDesc)); /* No use going on */ if (i == 0) return; /* Do the save now that we have storage for it. */ del = *(*pp->p_pcb)->dces; n = 0; h = UNITTABLE; while (n < UNITNTRYCNT) { Boolean removeIt; if (*h != nil) { /* Check whether current DCE is local driver or DA from DA Handler */ if ((removeIt = (n == daNumber)) == false) { if ( ((crit = (unsigned long) (**h)->dCtlWindow) == 0) && ((crit = (unsigned long) (**h)->dCtlStorage) == 0) ) crit = (unsigned long) (**h)->dCtlDriver; crit = (u_long) StripAddress((Ptr) crit); removeIt = ((crit >= base) && (crit < bound)); } /* Act on our findings */ if (removeIt) { assert(i-- != 0); del->d_unit = n; del++->d_handle = *h; *h = nil; } } h++; n++; } } /* restore_dce. Replace all the DCEs we switched out in save_dce. */ void restore_dce(PEntryPtr pProc) { register DCEDescHdl hDCEDesc; register DCEDescPtr pDCEDesc; short dceDescCount; assert(pProc->p_pcb != nil); hDCEDesc = (*pProc->p_pcb)->dces; pDCEDesc = *hDCEDesc; dceDescCount = GetHandleSize(hDCEDesc) / sizeof(DCEDesc); while (--dceDescCount >= 0) { *(UNITTABLE + pDCEDesc->d_unit) = pDCEDesc->d_handle; pDCEDesc++; } } /* kill_dce. Fix up the unit table when the given appl zone is nuked. * This contains the basic functionality of the ROM routine InstallRDrivers(). * Zeroes out the DCE except for dCtlRefNum (restored because _DrvrInstall won't be * called again on an allocated DCE). Also, gives appropriate GBKisses to drivers. * NOTE: A known bug is that a system DA opened locally with no window or storage but * a menu id will not be cleaned up or get a gb kiss. * NOTE: There should be no need to StripAddress handles, only the MPs. * NOTE: It is somewhat dangerous to not _ReleaseResource on the drivers if there are * misbehaved drivers (such as the "Windows" DA) that store data in their 'DRVR' handles * and depend on the data being reinitialized from disk since they assume they are app * heap resource handles and thus will be removed from memory by _RsrcZoneInit. */ void kill_dce(THz applZone) { u_long tmp; Ptr baseCurAppHeap, boundsCurAppHeap; DCtlHandle *unitEntry; short unitNTryCnt; DCtlPtr dCtlPtr; short dRefNum; baseCurAppHeap = (Ptr)applZone; boundsCurAppHeap = ((THz) baseCurAppHeap)->bkLim; unitEntry = UNITTABLE; dRefNum = FIRSTDREFNUM+1; unitNTryCnt = UNITNTRYCNT; while (--unitNTryCnt >= 0) { /* Keep dRefNum and unitNTryCnt in synch */ dRefNum -= 1; /* Get table entry (handle to driver) */ if ((tmp = (u_long) *unitEntry++) == nil) continue; /* Dereference it to get pointer to driver, can do nothing if it was purged */ if ((tmp = (u_long) *((DCtlHandle) tmp)) == nil) continue; /* Convert type just once */ dCtlPtr = (DCtlPtr) tmp; /* Can do nothing if driver isn't available. */ if ((tmp = (u_long) StripAddress((Ptr) dCtlPtr->dCtlDriver)) == nil) continue; /* Look for driver based in application heap. Wipe it out! */ if (tmp > baseCurAppHeap && tmp < boundsCurAppHeap) { GiveGoodByeKiss(dCtlPtr); MemClear(dCtlPtr,sizeof(DCtlEntry)); dCtlPtr->dCtlRefNum = dRefNum; continue; } /* Look for driver based in system heap who has storage in the application heap. */ if (tmp < (u_long)SYSZONE->bkLim) { /* Is the storage handle in current application heap? */ tmp = (u_long) StripAddress((Ptr) dCtlPtr->dCtlStorage); if (tmp > baseCurAppHeap && tmp < boundsCurAppHeap) { OSErr resErr; Str255 driverNameString; /* Get driver name now, since goodbye kiss may prompt the driver * to nuke dCtlPtr->dCtlDriver. */ GetResInfo(dCtlPtr->dCtlDriver, nil, nil, &driverNameString); resErr = RESERR; /* Smooch! */ GiveGoodByeKiss(dCtlPtr); /* Look for DA vs regular driver. If it's a DA, wipe it out altogether. * If a driver, just nil the handle into the application heap (driver * can later simple restore it before re-using). Do any drivers take * advantage of this? * NOTE: This assumes that all non-DA drivers start with a '.'. */ if ((resErr != noErr) || ((Length(&driverNameString) != 0) && (StringByte(&driverNameString, 0) == '\0'))) { HPurge(dCtlPtr->dCtlDriver); MemClear(dCtlPtr, sizeof(DCtlEntry)); dCtlPtr->dCtlRefNum = dRefNum; } else dCtlPtr->dCtlStorage = nil; continue; } } /* At this point we have a valid driver w/ last tries for GBKiss if it has * a window in the current heap. This is done primarily for the benefit of * Calculator, which allocates its storage in the sys heap, but never removes it. */ tmp = (u_long) StripAddress((Ptr) dCtlPtr->dCtlWindow); if (tmp > baseCurAppHeap && tmp < boundsCurAppHeap) { GiveGoodByeKiss(dCtlPtr); HPurge(dCtlPtr->dCtlDriver); MemClear(dCtlPtr,sizeof(DCtlEntry)); dCtlPtr->dCtlRefNum = dRefNum; } } } /* GiveGoodByeKiss. Give this driver a goodbye kiss if it wants one. */ void GiveGoodByeKiss(DCtlPtr dCtlPtr) { CntrlParam goodByeParamBlock; /* Give him a goodbye kiss, if necessary */ if ((dCtlPtr->dCtlFlags & (DNeedGoodbye | DOpened)) == (DNeedGoodbye | DOpened)) { goodByeParamBlock.csCode = csCodeGoodBye; goodByeParamBlock.ioCRefNum = dCtlPtr->dCtlRefNum; PBControl(&goodByeParamBlock, SyncHFS); } } /* save_hooks. Switch out any "hook" that may depend on the application's patches * or low memory. This should be called before save_or_restore_dispatch and * save_lomem. */ void save_hooks(PEntryPtr pProc) { PCB *pc; pc = *pProc->p_pcb; pc->fsqueuehook = FSQUEUEHOOK; pc->pmsphook = *((Ptr *)(PMSPPTR+PMSPHOOKINDEX)); kill_hooks(); } /* restore_hooks. Switch in the hooks that may depend on the application's patches * or low memory. This should be called before save_or_restore_dispatch and * restore_lomem. */ void restore_hooks(PEntryPtr pProc) { PCB *pc; pc = *pProc->p_pcb; FSQUEUEHOOK = pc->fsqueuehook; *((Ptr *)(PMSPPTR+PMSPHOOKINDEX)) = pc->pmsphook; } /* kill_hooks. Restore hooks to their neutral values. */ void kill_hooks(void) { FSQUEUEHOOK = (void (**)())initFSQueueHook; *((Ptr *)(PMSPPTR+PMSPHOOKINDEX)) = nil; } #pragma segment INIT /* GetSwitchTab. Get table of toolbox switch locations and set up related globals */ void GetSwitchTab(void) { short *pLen, len; Handle switchTabHdl; short lmemID; /* Which table depends on whether the machine has color */ lmemID = (Colorized) ? COLOR_LOMEMTAB_ID : BW_LOMEMTAB_ID; if ((switchTabHdl = GetResource(LOMEM_TAB_TYPE, lmemID)) == nil) { assert(RESERR == memFullErr); SysError(dsMemFullErr); } /* Remember table in globals (resource must be marked resLocked) */ switchTabPtr = *switchTabHdl; /* NOTE: This assumes only data addresses in the table */ for (pLen = switchTabPtr, lmemToolDataSize = 0; (len = *pLen) != 0; (Ptr)pLen += 6) lmemToolDataSize += len; } #pragma segment kernel_segment