Coherent4.2.10/coh.386/proc.c

Compare this file to the similar file:
Show the results in this format:

/* $Header: /ker/coh.386/RCS/proc.c,v 2.6 93/10/29 00:55:30 nigel Exp Locker: nigel $ */
/* (lgl-
 *	The information contained herein is a trade secret of Mark Williams
 *	Company, and  is confidential information.  It is provided  under a
 *	license agreement,  and may be  copied or disclosed  only under the
 *	terms of  that agreement.  Any  reproduction or disclosure  of this
 *	material without the express written authorization of Mark Williams
 *	Company or persuant to the license agreement is unlawful.
 *
 *	COHERENT Version 3.x, 4.x
 *	Copyright (c) 1982, 1993.
 *	An unpublished work by Mark Williams Company, Chicago.
 *	All rights reserved.
 -lgl) */
/*
 * Process handling and scheduling.
 */

#define	_DDI_DKI	1
#define	_DDI_DKI_IMPL	1
#define	_SYSV3		1

#include <sys/coherent.h>
#include <common/_wait.h>
#include <sys/types.h>

#include <kernel/proc_lib.h>
#include <kernel/sig_lib.h>
#include <sys/cmn_err.h>
#include <sys/ksynch.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <stddef.h>
#include <signal.h>
#include <limits.h>

#define	_KERNEL		1

#include <kernel/_timers.h>
#include <kernel/ddi_cpu.h>
#include <kernel/trace.h>
#include <kernel/reg.h>
#include <sys/uproc.h>
#include <sys/proc.h>
#include <sys/mmu.h>
#include <sys/acct.h>
#include <sys/inode.h>
#include <sys/ptrace.h>
#include <coh/proc.h>

/*
 * Number of entries in sleep/wakeup queue.
 */

#define	NHPLINK	97U
#define CLEAR_SIGN_BIT(foo)	((foo) & ~0x80000000)


/*
 * Sleep/wakeup queues.
 */

struct plink {
	PROC	      *	p_lforw;	/* Working forward pointer */
	PROC	      *	p_lback;	/* Working backward pointer */
};

__LOCAL__ struct plink	linkq [NHPLINK];	/* Sleep/wakeup hash queue */


#if	_USE_IDLE_PROCESS
/*
 * Pointer to idle process. For now, we still allocate a PROC *, but it
 * exists only to satisfy the stupid circular-list scheme for process
 * queueing; in actual fact, this process is never ever run.
 */

PROC	      *	iprocp;

#endif


/*
 * Function to hash a wakeup channel. To disappear ASAP along with the whole
 * uniprocessor sleep mechanism.
 */

#define hash(e)	((unsigned)(e) % NHPLINK)


/*
 * Initialization.
 * Set up the hash table queues.
 */

void
pcsinit ()
{
	struct plink * lp;

	/*
	 * Explicitly initialize everything in the first process. We use the
	 * kernel initialize routine with SELF set to NULL so that the first
	 * process gets started out with default values for fields that are
	 * normally inherited.
	 */

	procq.p_nforw = & procq;
	procq.p_nback = & procq;
	procq.p_lforw = & procq;
	procq.p_lback = & procq;

	/* Segments are initialized in mchinit () and eveinit (). */
	/* procq is static, so p_shmsr [] initializes to nulls.	*/

	(void) new_process_init (& procq);

	for (lp = linkq ; lp < linkq + NHPLINK ; lp ++) {
		lp->p_lforw = lp;
		lp->p_lback = lp;
	}

	/*
	 * After we have set things up, we can have a current process.
	 */

	SELF = & procq;
}


/*
 * Initiate a process.
 */

static PROC *
process ()
{
	PROC * pp1;
	PROC * pp;

static	pid_t		next_pid;

	if ((pp = new_process_init (NULL)) == NULL)
		return NULL;

	__GLOBAL_LOCK_PROCESS_TABLE ("process ()");

next:
	/*
	 * Pick the next process id.
	 */

	if (++ next_pid >= PID_MAX)
		next_pid = SYSPID_MAX + 1;
	pp->p_pid = next_pid;


	/*
	 * Make sure that process id is not in use.
	 */

	pp1 = & procq;
	while ((pp1 = pp1->p_nforw) != & procq) {
		if (pp1->p_pid < pp->p_pid)
			break;
		if (pp1->p_pid == pp->p_pid)
			goto next;
	}

	/*
	 * We've got a valid pid, so let's put this process into
	 * the process table.
	 */

	pp->p_nback = pp1->p_nback;
	pp1->p_nback->p_nforw = pp;
	pp->p_nforw = pp1;
	pp1->p_nback = pp;

	__GLOBAL_UNLOCK_PROCESS_TABLE ();
	return pp;
}

/*
 * Remove a process from the next queue and release and space.
 */

void
relproc (pp)
PROC * pp;
{
	SEG * sp;

	/*
	 * Child process still has a user-area.
	 */

	if ((sp = pp->p_segl [SIUSERP].sr_segp) != NULL) {

		/*
		 * Detach user-area from child process.
		 */
		pp->p_segl [SIUSERP].sr_segp = NULL;

		/*
		 * Child process is swapped out.
		 */
		if (pp->p_flags & PFSWAP)
			sp->s_lrefc ++;

		/*
		 * Release child's user-area.
		 */
		sfree (sp);
	}

	/*
	 * Remove process from doubly-linked list of all processes.
	 * Release space allocated for proc structure.
	 */

	__GLOBAL_LOCK_PROCESS_TABLE ("relproc ()");

	pp->p_nback->p_nforw = pp->p_nforw;
	pp->p_nforw->p_nback = pp->p_nback;

	__GLOBAL_UNLOCK_PROCESS_TABLE ();

	proc_destroy (pp);
}


/*
 * Create a clone of ourselves.
 *	N.B. - consave (& mcon) returns twice; anything not initialized
 *	in automatic storage before the call to segadup () will not be
 *	initialized when the second return from consave () commences.
 */

int
pfork ()
{
	PROC * cpp;
	int s;
	struct __mcon	mcon;

	if ((cpp = process ()) == NULL) {
		SET_U_ERROR (EAGAIN, "no more process table entries");
		return -1;
	}

	s = sphi ();

	/*
	 * Create an initial register context for the child process; the most
	 * obvious way to do this is to clone our context.
	 */

	if (consave (cpp->p_sysconp = & mcon) != 0) {
		/*
		 * Child process.
		 */

		/*
		 * WARNING!!! Parent returns first from consave() call
		 * above.  The lines below are executed only after the
		 * first context switch to the new child process.
		 *
		 * There is a brief interval between these times during
		 * which the process queue is fair game, e.g. for /dev/ps.
		 * So, be sure that changes here do not really belong
		 * in process() or something it calls.
		 */
		spl (s);
		u.u_btime = timer.t_time;
		u.u_flag = AFORK;

#if	UPROC_VERSION
		u.u_version = UPROC_VERSION;
#endif /* UPROC_VERSION */
		sproto (0);
		segload ();
		return 0;
	}

	spl (s);

	if (segadup (cpp) == 0) {
		SET_U_ERROR (EAGAIN, "can not duplicate segments");
		relproc (cpp);
		return -1;
	}

	shmDup (cpp);	/* copy shared memory info & update ref counts */

	if (u.u_rdir)
		u.u_rdir->i_refc ++;
	if (u.u_cdir)
		u.u_cdir->i_refc ++;
	fd_dup_all ();

	s = sphi ();
	process_set_runnable (cpp);
	spl (s);

	return cpp->p_pid;
}

PROC *
get_next_process()
{
  PROC *cur_proc;
  PROC *ret_proc;
  int max_prior;

  /*
   * This assumes that the process releasing context is queued 
   * at the **end** of the queue.
   */

  ret_proc = cur_proc = iprocp->p_lback;

  /* See if system is idle. */
  if (cur_proc == iprocp)
    return cur_proc;

  /* Determine max priority by adding priority with anti-starvation. */
  max_prior = cur_proc->p_schedPri + cur_proc->p_foodstamp;

  /* Select the process with the **highest** priority value */
  while (cur_proc != iprocp && cur_proc != NULL) {
    long cur_con;

    cur_con = CLEAR_SIGN_BIT(cur_proc->p_schedPri + cur_proc->p_foodstamp);
    
    if (cur_con > max_prior) {
      max_prior = cur_con;
      ret_proc = cur_proc;
    } else { /* Fend off starvation */
      if (cur_proc != SELF) {
	cur_proc->p_foodstamp++;
	cur_proc->p_foodstamp = CLEAR_SIGN_BIT(cur_proc->p_foodstamp);
      }
    }

    cur_proc = cur_proc->p_lback;
  }

  ASSERT(ret_proc != NULL);

  /* Starvation is not a concern for him who eats. */
  ret_proc->p_foodstamp = 0;
  return ret_proc;
}
   

/*
 * Select next process for execution, working backward from iprocp.
 * If it is not the idle process, delete it from the run queue.
 * If it is not the current process, consave the current process and
 * conrest the selected process.
 */

void
dispatch ()
{
  PROC *pp;
  int s;
  int old_quantum;
  
  LOCK_DISPATCH();
  
  s = sphi();

  while ((pp = get_next_process()) == iprocp) {
    pl_t		prev_pl;
    
    /*
     * The system is idle. The logical thing to do now would be to
     * go to ring 0 and halt. Instead, we'll enable interrupts and
     * wait for the process to change.
     */
    
    __MARK_CPU_IDLE();
    prev_pl = splbase();
    splo();
    
    while (iprocp->p_lback == iprocp)
      check_clock();
    
    /*
     * Re-enable interrupts, and restore normal programming.
     */
    
    (void) sphi();
    (void) splx(prev_pl);
    __CLEAR_CPU_IDLE();
  }
  
  pp->p_lforw->p_lback = pp->p_lback;
  pp->p_lback->p_lforw = pp->p_lforw;
  
  spl(s);
  
  old_quantum = quantum;

  if (pp != iprocp) {
    int newpri;
    int divisor;
    int new_usage;

    /*
     * The actual equation used to compute the priority is this:
     * p(t) = a * (DEFAULT_QUANTUM / (DEFAULT_QUANTUM - old_quantum))
     *        + (1 - a) * p(t - 1)
     *
     * Currently, a == 0.5 and the equation is optimized for this.
     * For p(0), look in ~/coh.386/lib/proc_init.c
     */
    divisor = (DEFAULT_QUANTUM == old_quantum) ? 1 :
      (DEFAULT_QUANTUM - old_quantum);
    
    new_usage = (DEFAULT_QUANTUM * SCHED_SCALE) / divisor;
    newpri = (new_usage + SELF->p_schedPri) >> 1;
    SELF->p_schedPri = CLEAR_SIGN_BIT(newpri);

#if 0
    printf("(%d) - 0x%x\n", SELF->p_pid, SELF->p_schedPri);
#endif

  }
  quantum = DEFAULT_QUANTUM;
  disflag = 0;
  
  if (pp != SELF) {
    struct __mcon	syscon;
    
    /*
     * Consave () returns twice.
     * 1st time is after our context is saved in syscon,
     *	whereupon we should restore other proc's context.
     * 2nd time is after our context is restored by another proc.
     * Conrest () forces a context switch to a new process.
     */
    
    s = sphi ();
    if (consave (SELF->p_sysconp = & syscon) == 0) {
      SELF = pp;
      conrest (* SELF->p_segl [SIUSERP].sr_segp->s_vmem,
	       SELF->p_sysconp);
    }
    
    if (SELF->p_pid) {	/* idle process is special! */
      ndpConRest ();
      segload ();
    }
    
    SELF->p_sysconp = NULL;
    spl (s);
  }
}


/*
 * Die.
 */

void
pexit (s)
int	s;
{
	PROC * pp1;
	SEG  * sp;
	int n;
	PROC	      *	parent = NULL;

	T_PIGGY (0x1, printf ("%s:pexit (%x)", SELF->p_comm, s));

	ndpEndProc ();

	/*
	 * Cancel alarm and poll timers [if any].
	 */
	timeout (& SELF->p_alrmtim, 0, NULL, 0);
	timeout (& SELF->p_polltim, 0, NULL, 0);

	/*
	 * Write out accounting directory and close all files associated with
	 * this process.
	 */
	setacct ();
	if (u.u_rdir)
		ldetach (u.u_rdir);
	if (u.u_cdir)
		ldetach (u.u_cdir);

	/*
	 * Block all signals before entering forced close sequence.
	 */

	___SIGSET_SET (SELF->p_signal_mask, -1UL);

	fd_close_all ();

	/*
	 * Free all segments in reverse order, except for user-area.
	 */

	for (n = NUSEG ; -- n > 0 ;) {
		if ((sp = SELF->p_segl [n].sr_segp) != NULL) {
			SELF->p_segl [n].sr_segp = NULL;
			sfree (sp);
		}
	}

	/* Detach remaining shared memory segments. */
	shmAllDt ();

	/* Adjust (undo) all semaphores. */
	semAllAdjust (SELF);

	/*
	 * Wakeup our parent.  If we have any children, init will become the
	 * new parent.  If there are any children we are tracing who are
	 * waiting for us, we wake them up.
	 */

	pp1 = & procq;

	/* pp1 runs through the list of all processes */
	while ((pp1 = pp1->p_nforw) != & procq) {

		/* if pp1 points to parent of the current process...*/
		if (pp1->p_pid == SELF->p_ppid) {
			parent = pp1;	/* Remember our parent.  */
			if (ASLEEP (pp1) && pp1->p_event == (char *) pp1)
				wakeup ((char *) pp1);
		}

		/* if pp1 points to child of the current process...*/
		if (pp1->p_ppid == SELF->p_pid) {
			pp1->p_ppid = 1;

			if (pp1->p_state == PSDEAD)
				wakeup ((char *) eprocp);

			if (pp1->p_flags & PFTRAC)
				ptrace_cleanup ();
		}
	}

	/*
	 * Mark us as dead and give up the processor forever.
	 */
	SELF->p_exit = s;
	SELF->p_state = PSDEAD;

	/*
	 * If this is a process group leader, inform all members of the group
	 * of the recent death with a HUP signal.
	 */

	if (SELF->p_group == SELF->p_pid)
		ukill (- SELF->p_pid, SIGHUP);

	/*
	 * If the parent is ignoring SIGCLD, 
	 * remove the zombie right away.
	 */

	if (parent == NULL)
		panic ("%d (@ %x), status %d has no parent!\n", SELF->p_pid,
		       SELF, s);

	if ((proc_signal_misc (parent) & __SF_NOCLDWAIT) != 0) {
		/*
		 * The parent has requested that no zombie processes be
		 * created out of childen.
		 */

		parent->p_cutime += SELF->p_utime + SELF->p_cutime;
		parent->p_cstime += SELF->p_stime + SELF->p_cstime;
		relproc (SELF);
	} else {
		/*
		 * Send parent a notification of our demise.
		 */
		__siginfo_t	sigchld;

		sigchld.__si_signo = SIGCHLD;
		sigchld.__si_errno = 0;
		if (__WIFEXITED (s)) {
			sigchld.__si_code = __CLD_EXITED;
			sigchld.__si_status = __WEXITSTATUS (s);
		} else {
			sigchld.__si_code = __WCOREDUMP (s) ? __CLD_DUMPED :
							      __CLD_KILLED;
			sigchld.__si_status = __WTERMSIG (s);
		}
		sigchld.__si_pid = SELF->p_pid;
		proc_send_signal (parent, & sigchld);
	}

	dispatch ();
}


/*
 * x_sleep ()
 *
 * Surrender CPU while awaiting some event or resource.
 *
 * Arguments:
 *	event:		key value; so wakeup () can find this sleep
 *	schedPri:	prilo/primed/prihi/pritape/pritty/pridisk/prinet
 *			just copied into proc struct for scheduler to use.
 *	sleepPri:	slpriNoSig	- signals may not interrupt sleep
 *			slpriSigLjmp	- signals cause longjmp (EINTR)
 *			slpriSigCatch	- signals are caught
 *	reason:		up to 10 chars of text for ps command "event"
 *
 * Return values:
 *	PROCESS_NORMAL_WAKE	wakeup received
 *	PROCESS_SIGNALLED	signal (other than SIGSTOP/SIGCONT) received
 *	PROCESS_CONTINUED	SIGSTOP / SIGCONT (unimplemented now)
 *
 * If longjmp occurs, won't return from x_sleep!
 */

#if __USE_PROTO__
__sleep_t x_sleep (char * event, int schedPri, int sleepPri, char * reason)
#else
__sleep_t
x_sleep (event, schedPri, sleepPri, reason)
char * event;
int schedPri;
int sleepPri;
char * reason;
#endif
{
  PROC *bp;
  PROC *fp;
  int s;
  
  /*
   * Before we queue ourselves on the event list, test for the presence
   * of signals if the users wants to know about them. Older versions of
   * the kernel tested after queueing, which meant that they left
   * the lists in an invalid state.
   */
  
  s = sphi();
  
  if (sleepPri != slpriNoSig && curr_signal_pending() != 0) {
    spl(s);
    
    if (sleepPri == slpriSigCatch)
      return PROCESS_SIGNALLED;
    
    /* Do longjmp to beginning of system call. */
    T_HAL (8, printf ("[%d]Ljmps ", SELF->p_pid));
    
    (void) sphi();
    envrest(u.u_sigenvp);
  }
  
  SELF->p_sleep = reason;
  
  /*
   * Get ready to go to sleep and do so.
   */
  
  SELF->p_state = sleepPri == slpriNoSig ? PSSLEEP : PSSLSIG;

  /* Scale priority by 2^n and mask off sign bit */
  SELF->p_foodstamp += (schedPri << 3) + 7;
  SELF->p_foodstamp = CLEAR_SIGN_BIT(SELF->p_foodstamp);

  SELF->p_event = event;
  fp = linkq + hash(event);
  bp = fp->p_lback;
  SELF->p_lforw = fp;
  fp->p_lback = SELF;
  SELF->p_lback = bp;
  bp->p_lforw = SELF;
  spl(s);
  
  /*
   * From here on we count on someone else dequeuing us.
   */
  
  dispatch ();
  SELF->p_sleep = NULL;
  
  if (sleepPri == slpriNoSig || curr_signal_pending () == 0)
    return PROCESS_NORMAL_WAKE;
  
  /* The process has been interrupted from sleep by a signal. */
  
  if (sleepPri == slpriSigCatch)
    return PROCESS_SIGNALLED;
  
  /* Do longjmp to beginning of system call. */
  T_HAL (8, printf ("[%d]Ljmps ", SELF->p_pid));
  (void) sphi ();
  envrest (u.u_sigenvp);
}


/*
 * Defer function to wake up all processes sleeping on the event `e'.
 */

void
wakeup (event)
char * event;
{
#if	0
	void dwakeup ();

#ifdef TRACER
	/*
	 * In diagnostic kernel, keep return addr on queue as well.
	 */
	int * r = (int *) (& e);
	defer0 (dwakeup, e, * (r - 1));
#else
	defer (dwakeup, e);
#endif
}

/*
 * Wake up all processes sleeping on "event".
 */

void
dwakeup (event)
char * event;
{
#endif
	PROC * pp;
	PROC * pp1;
	int s;

	/*
	 * Identify event queue to check.
	 * Disable interrupts.
	 */

	pp1 = linkq + hash (event);
	pp = pp1;
	s = sphi ();


	/*
	 * Traverse doubly-linked circular event-queue.
	 */

	while ((pp = pp->p_lforw) != pp1) {
		/*
		 * Process is waiting on event 'event'.
		 */

		if (pp->p_event == event) {
			/*
			 * Remove process from event queue.
			 * Update process priority.
			 * Insert process into run queue.
			 */

			pp->p_lback->p_lforw = pp->p_lforw;
			pp->p_lforw->p_lback = pp->p_lback;
			process_set_runnable (pp);

			/*
			 * Enable interrupts.
			 * Restart search at start of event queue.
			 * Disable interrupts.
			 */

			spl (s);
			pp = pp1;
			s = sphi ();
		}
	}
	spl (s);
}


/*
 * Wait for the gate `g' to unlock, and then lock it.
 */

void
__GATE_LOCK (g, reason)
__DUMB_GATE_P	g;
__CONST__ char * reason;
{
	int s;

	ASSERT (g != NULL);

	s = sphi ();
	while (g->_lock [0]) {
#if	TRACER
		if (g->_lock [0] != 1)
			cmn_err (CE_PANIC, "Uninitialised gate");
		if (__GATE_LOCK_OWNER (g) == (char *) SELF) {
			cmn_err (CE_WARN, "Recursive lock attempt by pid %d"
				SELF->p_pid);
			backtrace (0);
			break;
		}
#endif
		g->_lock [1] = 1;
		x_sleep ((char *) g, primed, slpriNoSig, reason);
		/* Waiting for a gate to unlock.  */
	}
	g->_lock [0] = 1;
	g->_lastlock = (char *) SELF;

	if ((__GATE_FLAGS (g) & __GATE_COUNT) != 0)
		u.u_lock_cnt ++;
	spl (s);
}


/*
 * Unlock the gate `g'.
 */

void
__GATE_UNLOCK (g)
__DUMB_GATE_P	g;
{
	ASSERT (g != NULL);
	ASSERT (g->_lock [0] != 0);

	g->_lock [0] = 0;
	if (g->_lock [1]) {
		ASSERT (g->_lock [1] == 1);

		g->_lock [1] = 0;
		disflag = 1;
		wakeup ((char *) g);
	}

	if ((__GATE_FLAGS (g) & __GATE_COUNT) != 0) {
		ASSERT (u.u_lock_cnt != 0);

		if (g->_lastlock != (char *) & procq)
			u.u_lock_cnt --;
	}
}


/*
 * Mark the given gate as being "owned" by an interrupt context.
 */

void
__GATE_TO_INTERRUPT (g)
__DUMB_GATE_P	g;
{
	ASSERT (g != NULL);
	ASSERT (g->_lastlock != (char *) & procq);

	if ((__GATE_FLAGS (g) & __GATE_COUNT) != 0) {
		ASSERT (u.u_lock_cnt != 0);
		u.u_lock_cnt --;
	}

	g->_lastlock = (char *) & procq;
}