/* :ts=8 bk=0 * * proc.c: Illustration of how to create a full-fledged DOS process * without needing to LoadSeg() it first. Based on an idea * presented at BADGE (don't remember the name of the guy * who thought it up). * * Leo L. Schwab 8903.01 */ #include #include extern void *AllocMem(), *CreatePort(), *GetMsg(), *FindTask(); extern LONG Open(), Output(), CreateProc(); /* * Cursor manipulation strings. */ char fwdcursor[] = "\033[C"; char backcursor = '\b'; /* * Buried deep in the AmigaDOS Technical Reference Manual, the structure of * of memory lists and SegLists are described. A SegList consists of a BPTR * to the next element in the list (NULL-terminated), followed by code. * The pointers point to the NextSeg field. The size of the node in bytes * is stored in the longword just before NextSeg field. However, the * size is only of real interest to UnLoadSeg(), which you won't be calling * in this case. So we don't need it. * * So the PhonySegList structure consists merely of a NextSeg field, which * is initialized to NULL (CreateProc() ignores this field, anyway), followed * by code to execute, which CreateProc() will jump to. The code we give it * to execute is an absolute jump to the real entry point, which you * initialize appropriately. */ struct PhonySegList { BPTR psl_NextSeg; /* BPTR to next element in list */ UWORD psl_JMP; /* A 68000 JMP abs.l instruction */ LONG (*psl_EntryPoint)(); /* The address of the function */ }; /* * This is NOT the structure that will actually get passed to CreateProc(). * AmigaDOS demands that everything, including SegLists, be on longword * boundaries. Short of compiler-dependent switches, the only way to * guarantee correct alignment is to AllocMem() a PhonySegList structure, * and copy this template into it. You should also remember to initialize * the psl_EntryPoint field (the argument to the JMP instruction) in the * allocated structure, for obvious reasons. */ struct PhonySegList template = { NULL, /* No next element. */ 0x4EF9, /* JMP abs.l */ NULL /* Argument for JMP instruction */ }; /* * This is the routine that will be CreateProc()ed. Due to the global nature * of library base variables, any library routines this routine calls will * be using library base variables that, strictly speaking, belong to the * "parent" process. Despite the fact that it will work, this is * nevertheless a dangerous practice. In an ideal world, you would want to * convince the linker to resolve all library base pointer references to * your co-process's own copies. Lattice, on the other hand, due to its * #pragmas, may not have this problem (can someone confirm this?). */ LONG coprocess () { register int i, n; struct Process *me; struct Message *startupmsg; LONG doswin; char buf[256]; #ifdef AZTEC_C #ifndef _LARGE_DATA /* * This gets to be a bloody nuisance. */ geta4 (); #endif #endif /* * Startup messages are important. Not only can they provide * configuration information for the newly-created process (what * directory you're in, what size to make your window, etc.), but * they also serve to inform the program that started you that * you have finished executing. This is important, because the * parent program will want to know when it's okay to free the * resources it allocated to start you. In this example, we * grab the message so that we can reply it. The act of replying * the startup message will inform the parent that we're done. */ me = FindTask (NULL); WaitPort (&me -> pr_MsgPort); startupmsg = GetMsg (&me -> pr_MsgPort); /* * Recall that, because a global DOSBase pointer has been initialized * by the parent, and because the stubs will reference it at the link * stage, we don't need to open dos.library here. */ if (!(doswin = Open ("CON:0/0/320/100/Sub-Process", MODE_NEWFILE))) goto xit; /* * Say hello. */ Write (doswin, "This is the child speaking.\n", 28L); /* * Print the value of FindTask(NULL) to prove we're a separate * task, and print some values in the Process structure to prove * we're a real process. * * (Another caveat: The stdio functions in this example (like * sprintf()) are being shared by both processes. Some stdio * routines utilize a set of global variables, access to which is * not arbitrated. Therefore, it is possible for the processes to * collide when calling stdio functions, causing Bad Things to * happen. Be aware of this when doing your own multiprogramming. * (I'm pretty sure sprintf() is safe.)) */ sprintf (buf, "I'm at 0x%lx\n", me); Write (doswin, buf, (LONG) strlen (buf)); sprintf (buf, "My stack size appears to be\n%ld bytes.\n", me -> pr_StackSize); Write (doswin, buf, (LONG) strlen (buf)); sprintf (buf, "pr_StackBase is 0x%lx\n", me -> pr_StackBase); Write (doswin, buf, (LONG) strlen (buf)); /* * Make the cursor in the window zot back and forth, which will * happen concurrently with the same action in the CLI window, * proving beyond a shadow of a doubt that there really are two * separate programs running. */ for (n = 20; n--; ) { for (i = 35; i--; ) Write (doswin, fwdcursor, sizeof (fwdcursor) - 1L); for (i = 35; i--; ) Write (doswin, &backcursor, 1L); } /* * We've proved our existence. We now reply our startup message, * and exit. Note the use of Forbid() without a corresponding * Permit(). This prevents this process from being switched out * before exiting completely. When this process is totally gone, * Exec will notice, and do the equivalent of a Permit() internally. */ Close (doswin); /* Get ridda da window. */ xit: Forbid (); ReplyMsg (startupmsg); return (0); } /* * Here is the main program. Its job will be to create the support * structures to fire off the sub-process, print out some relevant * information, then while away the time until the child exits. */ main () { register int i; struct Process *me; struct MsgPort *replyport = NULL; struct Message startupmsg; struct PhonySegList *fakelist = NULL; LONG child, priority, term; /* * Get a handle on the current output stream so that we can Write() * to it. Note that this implies this program really ought to be * run from a CLI. */ if (!(term = Output ())) goto xit; /* * Create a message port for the startup message to be replied to. */ if (!(replyport = CreatePort (NULL, NULL))) goto xit; /* * Allocate a PhonySegList structure. */ if (!(fakelist = AllocMem ((LONG) sizeof (*fakelist), NULL))) goto xit; /* * Copy the template into the allocated memory, and set the entry * point to the sub-process. */ CopyMem (&template, fakelist, (LONG) sizeof (template)); fakelist -> psl_EntryPoint = coprocess; /* * Initialize the startup message. There's nothing really amazing * happening here. Its sole purpose in life is to be replied. */ startupmsg.mn_Node.ln_Type = NT_MESSAGE; startupmsg.mn_Node.ln_Pri = 0; startupmsg.mn_ReplyPort = replyport; /* * Find ourselves. Discover what priority we're running at, so that * we can start the sub-process at the same priority. */ me = FindTask (NULL); priority = me -> pr_Task.tc_Node.ln_Pri; /* * Print some information about ourselves. */ puts ("This is the parent speaking."); printf ("I'm at 0x%lx\n", me); printf ("My stack size appears to be\n%ld bytes.\n", me -> pr_StackSize); printf ("pr_StackBase is 0x%lx\n", me -> pr_StackBase); /* * Now, create and start the sub-process. The current release of * AmigaDOS CreateProc() does nothing special with SegLists. * All it uses it for is to find the first bit of code to execute. * By passing it 'fakelist', we're essentially feeding it a JMP * instruction which jumps to the real start of our sub-process. * (Note that we have to convert 'fakelist' into a BPTR.) * Thus, we get a full-fledged DOS process, which we can do things * with, and we didn't have to LoadSeg() it. */ if (!(child = CreateProc ("Sub-Process", priority, (LONG) fakelist >> 2, 2048L))) goto xit; /* * Send the startup message. This will get the sub-process doing * its thing. * (Note that CreateProc() returns a pointer, not to the process * structure, but to the pr_MsgPort structure *within* the process * structure.) */ PutMsg (child, &startupmsg); /* * Make our cursor fly back and forth, which hopefully will happen * concurrently with the sub-process's activity. We continue to * do this until the sub-process replies its message, making * GetMsg() return a non-NULL value. Since we "know" what's arriving * at the replyport, we can safely throw the result from GetMsg() * away. */ while (!GetMsg (replyport)) { for (i = 64; i--; ) Write (term, fwdcursor, sizeof (fwdcursor) - 1L); for (i = 64; i--; ) Write (term, &backcursor, 1L); } /* * At this point, the sub-process has completely exited. We may * now safely deallocate all the support structures, and exit. */ puts ("Child terminated."); xit: if (fakelist) FreeMem (fakelist, (LONG) sizeof (*fakelist)); if (replyport) DeletePort (replyport); }