/* * _fork(), _wait(): create a new process, and wait for it to finish. * the parameter to _fork() controls the way memory is shared with the * parent; 0 means share all data and stack, non-zero means just share * stack up to the address given. Note that _fork(0) is callable only * when malloc's are being done from the heap. If _heapbase is * non-zero, _fork(0) fails and errno is set to EACCESS. * * written by Eric R. Smith and placed in the public domain. * use at your own risk. */ #include #include /* for size_t */ #include #include #include #include #include "fork.h" struct _fork_block *_forks = 0; extern long _stksize; static short wait_return; /* newly forked processes start here */ /* all we do is set up a temporary stack and longjmp() back to _fork() */ static void in_fork(bp) long bp; { __asm__ volatile("movl %0,sp"::"r"(bp+500)); longjmp(_forks->ctxt, 1); } /* * the guts of fork() and vfork(). _fork(0) is the same as fork(); * _fork(x) gives a vfork(), and in this case "x" is the top address * we should save to. note that the parent is suspended until the child * exits (this is unlike Unix). also note that _fork(0) requires that * _heapbase be non-zero, i.e. that mallocs be done from the heap. * otherwise, we would have to keep track of all memory requests from * the system, and somehow put them all into the saved block. this is * doable, but would be a pain. */ int _fork(save_to) char *save_to; { struct _fork_block *new; char *cpystart; long siz, *to, *from; long r; BASEPAGE *newbase; extern void *_heapbase; if (!save_to) { /* fork() requires stack based memory allocation */ if (!_heapbase) { errno = EACCESS; return -1; } siz = ((char *)_heapbase - _base->p_dbase) + _stksize; cpystart = (char *)_base->p_dbase; } else { /* * for vfork, start copying just below where the stack is now, and * copy up to just past save_to, leaving some slush on either * side for return addresses, linkages, and saved regs */ cpystart = ((char *)&save_to) - 128; siz = (save_to - cpystart) + 72; } /* * KLUDGE! * make sure that there is enough free memory handled by malloc, so that * it doesn't have to get any more from TOS if the "child" process * does a malloc() before it does an execve() (otherwise malloc() could * later return invalid memory blocks!) ++andreas */ free(malloc((size_t)16*1024)); /* hope this is enough! */ /* now malloc a _fork_block to describe this fork */ if (!(new = (void *)Malloc(siz+sizeof(struct _fork_block)))) { errno = ENOMEM; return -1; } /* adjust size, since pointers are (long *) */ siz = siz/sizeof(long); /* link the new fork block into the list and fill in some entries */ new->next = _forks; new->start_addr = (long *)cpystart; new->data_size = siz; new->ppid = _base; _forks = new; /* make a new basepage for the child process */ newbase = (BASEPAGE *)Pexec(PE_CBASEPAGE, 0L, "", 0L); if ((long)newbase < 0) { errno = -((int)newbase); _forks = _forks->next; Mfree(new); return -1; } new->pid = newbase; /* save the necessary data */ to = new->data; from = (long *)cpystart; r = siz; while (r-- > 0) *to++ = *from++; /* make the new basepage just like ours, so that the child will be able to fork() too */ newbase->p_dbase = _base->p_dbase; newbase->p_bbase = _base->p_bbase; newbase->p_dlen = _base->p_dlen; newbase->p_blen = _base->p_blen; newbase->p_tbase = (char *)in_fork; newbase->p_tlen = 0; if (r = setjmp(new->ctxt)) { /* the second return is caused by the child, from in_fork() */ Mshrink(_base, 256L); return 0; } else { /* If the child stomps on the parent's stack, then Pexec won't return to * the right place. So we set to a temporary stack before the Pexec. */ _base = newbase; r = (long)&(new->tmp_stack[62]); __asm__ volatile (" movel sp, a0; movel %0, sp; movel a0, sp@- " : : "r"(r) : "a0", "sp" ); wait_return = Pexec(PE_GO, 0L, newbase, 0L); __asm__ volatile (" movel sp@+, a0; movel a0, sp " ::: "a0", "sp" ); /* we're back in the parent here, so restore everything */ new = *((struct _fork_block * volatile *)&_forks); _base = new->ppid; new->return_code = wait_return; /* restore data */ to = new->start_addr; from = new->data; siz = new->data_size; while (siz-- > 0) *to++ = *from++; Mfree(newbase->p_env); Mfree(newbase); /* shrink the fork block to the minimum size (it can't be freed yet, * because wait() may need it) */ Mshrink(new, sizeof(struct _fork_block)); return BP_TO_PID(newbase); } } /* * wait() emulation; goes through the fork block structure and returns * the pid of a child we haven't waited for yet. if such a child exists, * and exit_code is non-zero, the integer pointed at by exit_code is * set to the child's exit status. if no child is found, -1 is returned * and errno is set. */ int _wait(exit_code) int *exit_code; { struct _fork_block *f, *g; int r; g = 0; f = _forks; while (f) { if (f->ppid == _base) { /* is this one our child?? */ r = BP_TO_PID(f->pid); if (exit_code) *exit_code = f->return_code; if (g) g->next = f->next; else _forks = f->next; Mfree(f); return r; } g = f; f = f->next; } errno = ENMFILES; /* FIX_ME:: should be ESRCH */ return -1; }