427 lines
13 KiB
C
427 lines
13 KiB
C
static char sccsid[] = "@(#)88 1.48 src/bos/kernel/proc/POWER/m_fork.c, sysproc, bos41J, 9513A_all 3/27/95 15:25:59";
|
|
/*
|
|
* COMPONENT_NAME: SYSPROC
|
|
*
|
|
* FUNCTIONS: procdup
|
|
* procentry
|
|
* procinit
|
|
*
|
|
* ORIGINS: 27, 83
|
|
*
|
|
*
|
|
* This module contains IBM CONFIDENTIAL code. -- (IBM
|
|
* Confidential Restricted when combined with the aggregated
|
|
* modules for this product)
|
|
* SOURCE MATERIALS
|
|
*
|
|
* (C) COPYRIGHT International Business Machines Corp. 1988, 1994
|
|
* All Rights Reserved
|
|
* US Government Users Restricted Rights - Use, duplication or
|
|
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
|
|
*/
|
|
/*
|
|
* LEVEL 1, 5 Years Bull Confidential Information
|
|
*/
|
|
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/user.h>
|
|
#include <sys/pseg.h>
|
|
#include <sys/intr.h>
|
|
#include <sys/syspest.h>
|
|
#include <sys/vmuser.h>
|
|
#include <sys/vmker.h>
|
|
#include <sys/adspace.h>
|
|
#include <sys/lockl.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/malloc.h>
|
|
#include "ld_data.h"
|
|
|
|
#define FORK_RETRIES 5
|
|
|
|
/*
|
|
* Machine-dependent #define's
|
|
*/
|
|
#ifdef _POWER /* R2 System machine-dependency */
|
|
/*
|
|
* The following bits are set in the MSR:
|
|
* EE=1 External Int. Enable
|
|
* ME=1 Machine Check Enable
|
|
* AL=1 Align. Check Enable
|
|
* IP=0 Interrupt Prefix
|
|
* I and D relocate on
|
|
*/
|
|
#define DEFAULT_MSR 0x90B0
|
|
#endif _POWER /* R2 System machine-dependency */
|
|
|
|
int pacefork=10;
|
|
|
|
extern struct vmmdseg vmmdseg;
|
|
extern union ptaseg ptaseg;
|
|
extern procentry();
|
|
|
|
/*
|
|
* NAME: procdup
|
|
*
|
|
* FUNCTION: Duplicate process address space
|
|
*
|
|
* NOTES:
|
|
* This routine is platform specific. It is called by fork()
|
|
* to set up an address space for the child process.
|
|
*
|
|
* RETURNS (in PARENT):
|
|
* pointer to the new process's ublock structure, which is attached
|
|
* to the current address space in the kernel. The caller
|
|
* is expected to vm_det() this structure when finished.
|
|
* -1, if unsuccessful (u.u_error has the reason).
|
|
* No vm_det() is required in this case.
|
|
*
|
|
* RETURNS (in CHILD):
|
|
* 0, when successful; never returns in the child when unsuccessful.
|
|
*/
|
|
|
|
struct ublock *
|
|
procdup(struct proc *child)
|
|
/* register struct proc *child; address of child's proc structure */
|
|
{
|
|
register struct thread *t; /* current thread */
|
|
register struct ublock *nub; /* address of child's ublock
|
|
(in parent) */
|
|
vmid_t vm_id; /* ID of child's private VM object */
|
|
register vmhandle_t vm_handle; /* child's VM handle while in kernel */
|
|
int i; /* retry counter */
|
|
char *errorp; /* current error storage */
|
|
int sregval; /* seg reg value for shared data */
|
|
struct loader_anchor *la; /* used to detect overflow segment */
|
|
|
|
t = curthread;
|
|
errorp = &t->t_uthreadp->ut_error;
|
|
|
|
/*
|
|
* Call disown_fp() to ensure that our thread's floating point
|
|
* state (if any) is entirely stored in the mst (in the u-block)
|
|
* and our thread does not currently have access to the floating
|
|
* point hardware. This is required because
|
|
* (1) The child must be given a faithful copy of the entire
|
|
* thread state, including floating point registers.
|
|
* (2) If our thread owns floating point access at the time
|
|
* that the child thread is copied from it, then we will
|
|
* have two threads (parent, child) which "own" the
|
|
* floating point hardware. At the very least, this causes
|
|
* rampant confusion.
|
|
*/
|
|
disown_fp(t->t_tid);
|
|
|
|
/*
|
|
* Copy the parent's process image. The parent process cannot
|
|
* be swapped out while in vm_forkcopy(). If the thread goes
|
|
* to sleep, the vmm puts it in a page wait state (t_wtype == TWPGIN).
|
|
* Currently, the swapper ( process 0 ) does not perform real IO
|
|
* swapping, nor does it unpin pages, so no coordination is needed.
|
|
* However, if the swapper changes, this code must also be changed
|
|
* to prevent the parent's u_block from being swapped out.
|
|
*
|
|
* Because paging space is not preallocated, paging space can be
|
|
* over committed. The vmm kills processes when paging space reaches
|
|
* a low threshold. Process creation is paced below, not because
|
|
* it is a good indication of how much memory the process is going
|
|
* to use, but rather because it is an interesting checkpoint.
|
|
* Consequently, this is not a definitive solution, but should work
|
|
* for homogenous process environments, where processes are created
|
|
* to perform small tasks and do not page fault much.
|
|
*/
|
|
for (i=0; i<FORK_RETRIES; i++)
|
|
{
|
|
/*
|
|
* If vm_forkcopy returns ENOSPC, then delay and retry.
|
|
*/
|
|
if ((*errorp = vm_forkcopy
|
|
(SRTOSID(t->t_procp->p_adspace),&vm_id)) == ENOSPC)
|
|
{
|
|
delay(pacefork);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
if (*errorp == ENOSPC)
|
|
*errorp = ENOMEM;
|
|
if (*errorp)
|
|
return((struct ublock *)(-1));
|
|
|
|
/*
|
|
* Get addressability to the new proc's ublock.
|
|
* The ublock page was long-term pinned in vm_forkcopy()
|
|
* already.
|
|
*/
|
|
vm_handle = SRVAL(vm_id, 0, 0);
|
|
nub = (struct ublock *)(vm_att(vm_handle, (caddr_t)(&__ublock)));
|
|
|
|
/*
|
|
* The thread currently forking may have its uthread block anywhere,
|
|
* however the resulting child thread will use the default uthread
|
|
* block (uthr0). To enforce the semantics of fork(), we must therefore
|
|
* copy it.
|
|
*/
|
|
if (t->t_uthreadp == &uthr0)
|
|
; /* same place, vm_forkcopy has already done the job */
|
|
else
|
|
bcopy(t->t_uthreadp, &nub->ub_uthr0, sizeof(struct uthread));
|
|
|
|
/*
|
|
* initialize the child's address space map
|
|
*/
|
|
child->p_adspace = vm_handle; /* store u-block handle in proc table */
|
|
as_fork(&nub->ub_uthr0.ut_save.as);/* copy the kernel address space */
|
|
as_det(&nub->ub_uthr0.ut_save.as, nub);
|
|
as_seth(&nub->ub_uthr0.ut_save.as, vm_setkey(vm_handle, VM_PRIV), &U);
|
|
as_seth(&nub->ub_user.U_adspace, vm_setkey(vm_handle, VM_UNPRIV), &U);
|
|
if (nub->ub_uthr0.ut_save.as.alloc & ((unsigned)0x80000000>>SHDATASEG))
|
|
as_det(&nub->ub_uthr0.ut_save.as, SHDATAORG);
|
|
|
|
/*
|
|
* forkcopy the shared library data segment. The segment register is
|
|
* protected by the per process loader lock.
|
|
*/
|
|
if (U.U_adspace.alloc & ((unsigned)0x80000000 >> SHDATASEG)) {
|
|
sregval = as_getsrval(&U.U_adspace, SHDATAORG);
|
|
*errorp = vm_forkcopy(SRTOSID(sregval), &vm_id);
|
|
if (*errorp) {
|
|
/*
|
|
* Detach from segment containing the new ublock
|
|
* and destroy it.
|
|
*/
|
|
vm_det(nub);
|
|
vms_delete(SRTOSID(child->p_adspace));
|
|
return((struct ublock *)(-1));
|
|
}
|
|
/* compute the new srval for the child, privileged key */
|
|
vm_handle = SRVAL(vm_id,0,0);
|
|
as_ralloc(&nub->ub_user.U_adspace, SHDATAORG);
|
|
as_seth(&nub->ub_user.U_adspace,vm_setkey(vm_handle,VM_UNPRIV),
|
|
SHDATAORG);
|
|
as_ralloc(&nub->ub_uthr0.ut_save.as, SHDATAORG);
|
|
as_seth(&nub->ub_uthr0.ut_save.as,vm_setkey(vm_handle,VM_PRIV),
|
|
SHDATAORG);
|
|
}
|
|
|
|
/* If an overflow segment was created for this process, then it must
|
|
* be fork copied also.
|
|
*/
|
|
la = (struct loader_anchor *)(U.U_loader);
|
|
if (OVFL_EXISTS(la)) {
|
|
if (*errorp = vm_forkcopy(la->la_ovfl_srval, &vm_id)) {
|
|
/* Error path must undo everything up to this point.
|
|
* Delete the shared library data segment. No locking
|
|
* needed for child's address space.
|
|
*/
|
|
if (nub->ub_user.U_adspace.alloc &
|
|
((unsigned)0x80000000 >> SHDATASEG))
|
|
vms_delete(SRTOSID(as_getsrval(
|
|
&nub->ub_user.U_adspace, SHDATAORG)));
|
|
/*
|
|
* Detach from segment containing the new ublock
|
|
* and destroy it.
|
|
*/
|
|
vm_det(nub);
|
|
vms_delete(SRTOSID(child->p_adspace));
|
|
return((struct ublock *)(-1));
|
|
}
|
|
/* Save segement in loader anchor of child */
|
|
la = (struct loader_anchor *)nub->ub_user.U_loader;
|
|
la->la_ovfl_srval = SRVAL(vm_id, 0, 0);
|
|
}
|
|
|
|
/* loop through the secondary data segments forkcopying each one */
|
|
for (i = BDATASEG; i <= BDATASEGMAX; i++)
|
|
{
|
|
if (!(U.U_segst[i].segflag & SEG_WORKING))
|
|
break;
|
|
|
|
/*
|
|
* If vm_forkcopy is failing due to a lack of paging
|
|
* space, let's nip this big data process in the bud
|
|
* rather than retrying the operation. If the process
|
|
* has any more segments, then the next call to vm_forkcopy
|
|
* is bound to fail.
|
|
*/
|
|
if (*errorp =vm_forkcopy(SRTOSID(U.U_segst[i].ss_srval),&vm_id))
|
|
{
|
|
/*
|
|
* Double back and destroy any segments created
|
|
* so far. We know that all segment numbers from
|
|
* BDATASEG to i-1 are valid SEG_WORKING segments.
|
|
*/
|
|
int j;
|
|
|
|
for (j = BDATASEG; j < i; j++)
|
|
vms_delete(SRTOSID(nub->ub_user.U_segst[j].ss_srval));
|
|
|
|
/* Delete the shared library data segment. No locking
|
|
* is needed for the child's address space.
|
|
*/
|
|
if (nub->ub_user.U_adspace.alloc &
|
|
((unsigned)0x80000000 >> SHDATASEG))
|
|
vms_delete(SRTOSID(as_getsrval(
|
|
&nub->ub_user.U_adspace, SHDATAORG)));
|
|
|
|
/* Delete overflow segment if one was created */
|
|
la = (struct loader_anchor *)nub->ub_user.U_loader;
|
|
if (OVFL_EXISTS(la))
|
|
vms_delete(SRTOSID(la->la_ovfl_srval));
|
|
|
|
/*
|
|
* Detach from segment containing the new ublock
|
|
* and destroy it.
|
|
*/
|
|
vm_det(nub);
|
|
vms_delete(SRTOSID(child->p_adspace));
|
|
return((struct ublock *)(-1));
|
|
}
|
|
|
|
/* compute the new srval for the child, unprivileged key */
|
|
vm_handle = SRVAL(vm_id,0,0);
|
|
as_seth(&nub->ub_user.U_adspace,
|
|
vm_setkey(vm_handle,VM_UNPRIV),i<<SEGSHIFT);
|
|
|
|
/* set up the segstate in the child */
|
|
nub->ub_user.U_segst[i].segflag = SEG_WORKING;
|
|
nub->ub_user.U_segst[i].num_segs = 1;
|
|
nub->ub_user.U_segst[i].ss_srval = vm_handle;
|
|
}
|
|
|
|
|
|
/* child inherits upfbuf */
|
|
if (U.U_message != NULL)
|
|
{
|
|
nub->ub_user.U_message = U.U_message;
|
|
U.U_message = NULL;
|
|
}
|
|
|
|
/*
|
|
* forksave() is a little like setjmp(). It saves the current
|
|
* machine state in the new u-block, returning 0 to the
|
|
* parent and setting up the child to return 1 when it is
|
|
* dispatched, which happens after fork() calls setrq().
|
|
*/
|
|
if (forksave(&nub->ub_uthr0.ut_save))
|
|
return(0); /* child process return */
|
|
else
|
|
return(nub); /* parent process return */
|
|
}
|
|
|
|
/*
|
|
* NAME: procinit
|
|
*
|
|
* FUNCTION: Initialize kernel process machine state.
|
|
*
|
|
* NOTES:
|
|
* This routine is platform specific. It is called by initp() during
|
|
* kernel process start up to initialize the machine state for this
|
|
* platform.
|
|
*
|
|
* Since it is cheap to have an address space on this machine,
|
|
* we go ahead and allocate a process private segment even for
|
|
* kernel processes. Note that this would not necessarily be
|
|
* done for all platforms; kernel processes can be allocated
|
|
* completely within the kernel global address space.
|
|
*
|
|
* RETURNS:
|
|
* pointer to the new process's ublock structure, which is attached
|
|
* to the current address space in the kernel. The caller
|
|
* is expected to vm_det() this structure when finished.
|
|
* NULL, if unsuccessful. In this case, there is no child u-area
|
|
* to be detached by the caller.
|
|
*/
|
|
|
|
struct ublock *
|
|
procinit(p, init_func, init_data, length)
|
|
register struct proc *p; /* process to initialize */
|
|
int (*init_func)(); /* pointer to initial function */
|
|
register char *init_data; /* init data pointer */
|
|
register int length; /* length of init data */
|
|
{
|
|
register struct ublock *nub; /* new proc's u-block */
|
|
register int i; /* register index */
|
|
register char *stack_data; /* address of data in stack */
|
|
vmid_t vm_id; /* process private VM ID */
|
|
register vmhandle_t vm_handle; /* process private VM handle */
|
|
|
|
struct func_desc *proc_desc = (struct func_desc *)procentry;
|
|
int dsize, smax, size;
|
|
|
|
/*
|
|
* Allocate a process private VM object, including u-block.
|
|
* The units of smax and dsize are in bytes. Calculate the
|
|
* boundaries of the stack.
|
|
*/
|
|
dsize = U.U_dsize < SEGSIZE ? U.U_dsize : U.U_sdsize;
|
|
smax = SEGSIZE - K_REGION_SIZE - dsize;
|
|
|
|
/* calculate down limits */
|
|
if ((unsigned)U.U_smax < smax)
|
|
smax = U.U_smax;
|
|
|
|
size = (p == &proc[1]) ? U_REGION_SIZE : 0;
|
|
if (vms_create(&vm_id, V_WORKING|V_PRIVSEG, 0,
|
|
size, dsize, smax + K_REGION_SIZE) != 0)
|
|
return(NULL);
|
|
vm_handle = SRVAL(vm_id, 0, 0);
|
|
|
|
/*
|
|
* Get addressability to the new proc's ublock and pin it.
|
|
*/
|
|
nub = (struct ublock *)(vm_att(vm_handle, (caddr_t)(&__ublock)));
|
|
if (ltpin(round_down(nub, PAGESIZE), PAGESIZE) != 0) {
|
|
vm_det(nub); /* detach from failed segment */
|
|
vms_delete(vm_id);
|
|
return(NULL);
|
|
}
|
|
|
|
p->p_adspace = vm_handle; /* store u-block handle in proc table */
|
|
|
|
/*
|
|
* initialize the new uthread structure (including MST save area)
|
|
*/
|
|
threadinit(&nub->ub_uthr0, NULLSEGVAL, vm_handle, init_func, init_data,
|
|
length, (void *)(DATAORG+USTACK_TOP),
|
|
((char *)nub-(char *)&__ublock));
|
|
|
|
/*
|
|
* build an empty user address space
|
|
*/
|
|
as_init(&nub->ub_user.U_adspace);
|
|
as_ralloc(&nub->ub_user.U_adspace, KERNELORG);
|
|
as_seth(&nub->ub_user.U_adspace, vmker.ukernsrval, KERNELORG);
|
|
as_ralloc(&nub->ub_user.U_adspace, &U);
|
|
as_seth(&nub->ub_user.U_adspace, vm_setkey(vm_handle, VM_UNPRIV), &U);
|
|
|
|
/*
|
|
* The first thread calls procentry, not threadentry.
|
|
*/
|
|
#ifdef _POWER
|
|
nub->ub_uthr0.ut_save.gpr[2] = (ulong_t) proc_desc->toc_ptr;
|
|
nub->ub_uthr0.ut_save.iar = (ulong_t) proc_desc->entry_point;
|
|
#endif _POWER
|
|
|
|
return(nub);
|
|
}
|
|
|
|
/*
|
|
* FUNCTION: procentry()
|
|
*
|
|
* This routine is the front end of all kernel processes.
|
|
* If the kproc returns, call kexit()
|
|
*/
|
|
procentry(int data, char *stack, int length, int (*ep)())
|
|
{
|
|
int rc = 0;
|
|
|
|
/* Call the kproc entry point */
|
|
rc = (*ep)(data, stack, length);
|
|
|
|
/* Cause the kernel process to exit */
|
|
kexit(rc);
|
|
}
|