Source to mint/proc.c
/*
* This file has been modified as part of the FreeMiNT project. See
* the file Changes.MH for details and dates.
*/
/*
Copyright 1990,1991,1992 Eric R. Smith.
Copyright 1992,1993,1994 Atari Corporation.
All rights reserved.
*/
/* routines for handling processes */
#ifdef MAC
#include <Memory.h>
#endif
#include "mint.h"
#include "xbra.h"
static void swap_in_curproc P_((void));
static void do_wakeup_things P_((short sr, int newslice, long cond));
INLINE static void do_wake P_((int que, long cond));
extern short proc_clock;
/* global process variables */
PROC *proclist; /* list of all active processes */
PROC *curproc; /* current process */
PROC *rootproc; /* pid 0 -- MiNT itself */
PROC *sys_q[NUM_QUEUES];
short time_slice = 2; /* default; actual value comes from mint.cnf */
#if 0
#define TIME_SLICE 2 /* number of 20ms ticks before process is
pre-empted */
#else
#define TIME_SLICE time_slice
#endif
/* macro for calculating number of missed time slices, based on a
* process' priority
*/
#define SLICES(pri) (((pri) >= 0) ? 0 : -(pri))
extern FILESYS bios_filesys;
/*
* get a new process struct
*/
PROC *
new_proc()
{
PROC *p;
void *pt;
pt = kmalloc(page_table_size + 16);
if (!pt) return 0;
p = (PROC *)kmalloc(SIZEOF(PROC));
if (!p) {
kfree(pt);
return 0;
}
/* page tables must be on 16 byte boundaries, so we
* round off by 16 for that; however, we will want to
* kfree that memory at some point, so we squirrel
* away the original address for later use
*/
p->page_table = ROUND16(pt);
p->pt_mem = pt;
#ifdef MAC
TRACE(("new_proc: ssp from %lx to %lx",(long)p->stack, (long)p->stack+STKSIZE));
if (HAS_VM && HoldMemory(p->stack,STKSIZE)) {
DEBUG(("Failure to hold memory"));
kfree(p);
kfree(pt);
return 0;
}
#endif
return p;
}
/*
* dispose of an old proc
*/
void
dispose_proc(p)
PROC *p;
{
TRACELOW(("dispose_proc"));
#ifdef MAC
TRACE(("dispose_proc: freeing ssp from %lx to %lx",(long)p->stack, (long)p->stack+STKSIZE));
if (HAS_VM && UnholdMemory(p->stack,STKSIZE))
DEBUG(("Failure to unhold memory"));
#endif
kfree(p->pt_mem);
kfree(p);
}
/*
* create a new process that is (practically) a duplicate of the
* current one
*/
PROC *
fork_proc(p)
PROC *p;
{
int i;
FILEPTR *f;
long_desc *pthold;
void *ptmemhold;
if (p == 0 && (p = new_proc()) == 0) {
nomem:
DEBUG(("fork_proc: insufficient memory"));
mint_errno = ENSMEM; return 0;
}
/* child shares most things with parent, but hold on to page table ptr */
pthold = p->page_table;
ptmemhold = p->pt_mem;
*p = *curproc;
p->page_table = pthold;
p->pt_mem = ptmemhold;
/* these things are not inherited */
p->ppid = curproc->pid;
p->pid = newpid();
p->sigpending = 0;
p->nsigs = 0;
p->sysstack = (long)(p->stack + STKSIZE - 12);
p->ctxt[CURRENT].ssp = p->sysstack;
p->ctxt[SYSCALL].ssp = (long)(p->stack + ISTKSIZE);
p->alarmtim = 0;
p->curpri = p->pri;
p->slices = SLICES(p->pri);
p->starttime = timestamp;
p->startdate = datestamp;
p->itimer[0].interval = 0;
p->itimer[0].reqtime = 0;
p->itimer[0].timeout = 0;
p->itimer[1].interval = 0;
p->itimer[1].reqtime = 0;
p->itimer[1].timeout = 0;
p->itimer[2].interval = 0;
p->itimer[2].reqtime = 0;
p->itimer[2].timeout = 0;
((long *)p->sysstack)[1] = FRAME_MAGIC;
((long *)p->sysstack)[2] = 0;
((long *)p->sysstack)[3] = 0;
p->usrtime = p->systime = p->chldstime = p->chldutime = 0;
/* allocate space for memory regions: do it here so that we can fail
* before we duplicate anything else. The memory regions are
* actually copied later
*/
p->mem = (MEMREGION **) kmalloc(p->num_reg * SIZEOF(MEMREGION *));
if (!p->mem) {
dispose_proc(p);
goto nomem;
}
p->addr = (virtaddr *)kmalloc(p->num_reg * SIZEOF(virtaddr));
if (!p->addr) {
kfree(p->mem);
dispose_proc(p);
goto nomem;
}
/* copy open handles */
for (i = MIN_HANDLE; i < MAX_OPEN; i++) {
if ((f = p->handle[i]) != 0) {
if (f == (FILEPTR *)1 || f->flags & O_NOINHERIT)
/* oops, we didn't really want to copy this handle */
p->handle[i] = 0;
else
f->links++;
}
}
/* copy root and current directories */
for (i = 0; i < NUM_DRIVES; i++) {
dup_cookie(&p->root[i], &curproc->root[i]);
dup_cookie(&p->curdir[i], &curproc->curdir[i]);
}
/* jr: copy ploadinfo */
strncpy(p->cmdlin, curproc->cmdlin, 128);
strcpy(p->fname, curproc->fname);
/* clear directory search info */
zero((char *)p->srchdta, NUM_SEARCH * SIZEOF(DTABUF *));
zero((char *)p->srchdir, SIZEOF(p->srchdir));
p->searches = 0;
/* copy memory */
for (i = 0; i < curproc->num_reg; i++) {
p->mem[i] = curproc->mem[i];
if (p->mem[i] != 0)
p->mem[i]->links++;
p->addr[i] = curproc->addr[i];
}
/* now that memory ownership is copied, fill in page table */
init_page_table(p);
/* child isn't traced */
p->ptracer = 0;
p->ptraceflags = 0;
p->starttime = Tgettime();
p->startdate = Tgetdate();
p->q_next = 0;
p->wait_q = 0;
p->gl_next = proclist;
proclist = p; /* hook into the process list */
return p;
}
/*
* initialize the process table
*/
void
init_proc()
{
int i;
FILESYS *fs;
fcookie dir;
long_desc *pthold;
void *ptmemhold;
rootproc = curproc = new_proc();
assert(curproc);
pthold = curproc->page_table;
ptmemhold = curproc->pt_mem;
zero((char *)curproc, (long)sizeof(PROC));
curproc->page_table = pthold;
curproc->pt_mem = ptmemhold;
curproc->ppid = -1; /* no parent */
curproc->domain = DOM_TOS; /* TOS domain */
curproc->sysstack = (long) (curproc->stack+STKSIZE-12);
curproc->magic = CTXT_MAGIC;
curproc->memflags = F_PROT_S; /* default prot mode: super-only */
((long *)curproc->sysstack)[1] = FRAME_MAGIC;
((long *)curproc->sysstack)[2] = 0;
((long *)curproc->sysstack)[3] = 0;
/* NOTE: in main.c this could be changed, later */
curproc->base = _base;
strcpy(curproc->name, "MiNT");
/* get some memory */
curproc->mem = (MEMREGION **)kmalloc(NUM_REGIONS*SIZEOF(MEMREGION *));
curproc->addr = (virtaddr *)kmalloc(NUM_REGIONS*SIZEOF(virtaddr));
assert(curproc->mem && curproc->addr);
/* make sure it's filled with zeros */
zero((char *)curproc->addr, NUM_REGIONS * SIZEOF(virtaddr));
zero((char *)curproc->mem, NUM_REGIONS * SIZEOF(MEMREGION *));
curproc->num_reg = NUM_REGIONS;
/* get root and current directories for all drives */
for (i = 0; i < NUM_DRIVES; i++) {
if ((fs = drives[i]) != 0 && (*fs->root)(i, &dir) == E_OK) {
dup_cookie(&curproc->curdir[i], &dir);
curproc->root[i] = dir;
} else {
curproc->root[i].fs = curproc->curdir[i].fs = 0;
curproc->root[i].dev = curproc->curdir[i].dev = i;
}
}
init_page_table(curproc);
/* Set the correct drive. The current directory we
* set later, after all file systems have been loaded.
*/
curproc->curdrv = Dgetdrv();
proclist = curproc;
curproc->umask = 0;
/*
* some more protection against job control; unless these signals are
* re-activated by a shell that knows about job control, they'll have
* no effect
*/
curproc->sighandle[SIGTTIN] = curproc->sighandle[SIGTTOU] =
curproc->sighandle[SIGTSTP] = SIG_IGN;
/* set up some more per-process variables */
curproc->starttime = Tgettime();
curproc->startdate = Tgetdate();
if (has_bconmap)
/* init_xbios not happened yet */
curproc->bconmap = (int) Bconmap(-1);
else
curproc->bconmap = 1;
curproc->logbase = (void *)Logbase();
#ifdef MAC
curproc->criticerr = 0L;
#else
curproc->criticerr = *((long ARGS_ON_STACK (**) P_((long)))0x4080000AL);
#endif
}
/*
* reset all process priorities to their base level
* called once per second, so that cpu hogs can get _some_ time
* slices :-).
*/
void
reset_priorities()
{
PROC *p;
for (p = proclist; p; p = p->gl_next) {
if (p->slices >= 0) {
p->curpri = p->pri;
p->slices = SLICES(p->curpri);
}
}
}
/*
* more priority code stuff:
* run_next(p, slices): schedule process "p" to run next, with "slices"
* initial time slices; "p" does not actually start running until
* the next context switch
* fresh_slices(slices): give the current process "slices" more slices in
* which to run
*/
void
run_next(p, slices)
PROC *p;
int slices;
{
short sr = spl7();
p->slices = -slices;
p->curpri = MAX_NICE;
p->wait_q = READY_Q;
p->q_next = sys_q[READY_Q];
sys_q[READY_Q] = p;
spl(sr);
}
void
fresh_slices(slices)
int slices;
{
reset_priorities();
curproc->slices = 0;
curproc->curpri = MAX_NICE+1;
proc_clock = TIME_SLICE+slices;
}
/*
* add a process to a wait (or ready) queue.
*
* processes go onto a queue in first in-first out order
*/
void
add_q(que, proc)
int que;
PROC *proc;
{
PROC *q, **lastq;
/* "proc" should not already be on a list */
assert(proc->wait_q == 0);
assert(proc->q_next == 0);
lastq = &sys_q[que];
q = *lastq;
while(q) {
lastq = &q->q_next;
q = *lastq;
}
*lastq = proc;
proc->wait_q = que;
if (que != READY_Q && proc->slices >= 0) {
proc->curpri = proc->pri; /* reward the process */
proc->slices = SLICES(proc->curpri);
}
}
/*
* remove a process from a queue
*/
void
rm_q(que, proc)
int que;
PROC *proc;
{
PROC *q;
PROC *old = 0;
assert(proc->wait_q == que);
q = sys_q[que];
while (q && q != proc) {
old = q;
q = q->q_next;
}
if (q == 0)
FATAL("rm_q: unable to remove process from queue");
if (old)
old->q_next = proc->q_next;
else
sys_q[que] = proc->q_next;
proc->wait_q = 0;
proc->q_next = 0;
}
/*
* preempt(): called by the vbl routine and/or the trap handlers when
* they detect that a process has exceeded its time slice and hasn't
* yielded gracefully. For now, it just does sleep(READY_Q); later,
* we might want to keep track of statistics or something.
*/
void ARGS_ON_STACK
preempt()
{
extern short bconbsiz; /* in bios.c */
if (bconbsiz)
(void)bflush();
else {
/* punish the pre-empted process */
if (curproc->curpri >= MIN_NICE)
curproc->curpri -= 1;
}
sleep(READY_Q, curproc->wait_cond);
}
/*
* swap_in_curproc(): for all memory regions of the current process swaps
* in the contents of those regions that have been saved in a shadow region
*/
static void
swap_in_curproc()
{
int i;
long txtsize = curproc->txtsize;
MEMREGION *m, *shdw, *save;
for (i = 0; i < curproc->num_reg; i++) {
m = curproc->mem[i];
if (m && (save = m->save)) {
for (shdw = m->shadow; shdw->save; shdw = shdw->shadow)
assert(shdw != m);
assert(m->loc == shdw->loc);
shdw->save = save;
m->save = 0;
if (i != 1 || txtsize == 0)
quickswap((char *)m->loc, (char *)save->loc,
m->len);
else {
quickswap((char *)m->loc,
(char *)save->loc, 256);
quickswap((char *)m->loc + (txtsize+256),
(char *)save->loc + 256,
m->len - (txtsize+256));
}
}
}
}
/*
* sleep(que, cond): put the current process on the given queue, then switch
* contexts. Before a new process runs, give it a fresh time slice. "cond"
* is the condition for which the process is waiting, and is placed in
* curproc->wait_cond
*/
INLINE static void
do_wakeup_things(sr, newslice, cond)
short sr;
int newslice;
long cond;
{
/*
* check for stack underflow, just in case
*/
auto int foo;
PROC *p;
p = curproc;
if ((sr & 0x700) < 0x500) {
/* skip all this if int level is too high */
if ( p->pid != 0 &&
((long)&foo) < (long)p->stack + ISTKSIZE + 512 ) {
ALERT("stack underflow");
handle_sig(SIGBUS);
}
/* see if process' time limit has been exceeded */
if (p->maxcpu) {
if (p->maxcpu <= p->systime + p->usrtime) {
DEBUG(("cpu limit exceeded"));
raise(SIGXCPU);
}
}
/*
* check for alarms and similar time out stuff (see timeout.c)
*/
checkalarms();
if (p->sigpending && cond != (long)p_waitpid)
check_sigs(); /* check for signals */
}
if (newslice) {
if (p->slices >= 0) {
proc_clock = TIME_SLICE; /* get a fresh time slice */
} else {
proc_clock = TIME_SLICE-p->slices; /* slices set by run_next */
p->curpri = p->pri;
}
p->slices = SLICES(p->curpri);
}
}
static long sleepcond, iwakecond;
/*
* sleep: returns 1 if no signals have happened since our last sleep, 0
* if some have
*/
int ARGS_ON_STACK
sleep(_que, cond)
int _que;
long cond;
{
PROC *p;
short sr, que = _que & 0xff;
ulong onsigs = curproc->nsigs;
extern short kintr; /* in bios.c */
int newslice = 1;
#ifndef MULTITOS
#ifdef FASTTEXT
extern int hardscroll; /* in fasttext.c */
#endif
#endif
/* save condition, checkbttys may just wake() it right away...
* note this assumes the condition will never be waked from interrupts
* or other than thru wake() before we really went to sleep, otherwise
* use the 0x100 bit like select
*/
sleepcond = cond;
/*
* if there have been keyboard interrupts since our last sleep, check for
* special keys like CTRL-ALT-Fx
*/
sr = spl7();
if ((sr & 0x700) < 0x500) {
/* can't call checkkeys if sleep was called with interrupts off -nox */
spl(sr);
(void)checkbttys();
if (kintr) {
(void)checkkeys();
kintr = 0;
}
sr = spl7();
if ((curproc->sigpending & ~(curproc->sigmask)) &&
curproc->pid && que != ZOMBIE_Q && que != TSR_Q) {
spl(sr);
check_sigs();
sr = spl7();
sleepcond = 0; /* possibly handled a signal, return */
}
}
/*
* kay: If _que & 0x100 != 0 then take curproc->wait_cond != cond as an
* indicatation that the wakeup has already happend before we actually
* go to sleep and return immediatly.
*/
if ((que == READY_Q && !sys_q[READY_Q]) ||
((sleepcond != cond ||
(iwakecond == cond && cond) ||
(_que & 0x100 && curproc->wait_cond != cond)) &&
(!sys_q[READY_Q] || (newslice = 0, proc_clock)))) {
/* we're just going to wake up again right away! */
iwakecond = 0;
spl(sr);
do_wakeup_things(sr, newslice, cond);
return (onsigs != curproc->nsigs);
}
/*
* unless our time slice has expired (proc_clock == 0) and other
* processes are ready...
*/
iwakecond = 0;
if (!newslice)
que = READY_Q;
else
curproc->wait_cond = cond;
add_q(que, curproc);
/* alright curproc is on que now... maybe there's an interrupt pending
* that will wakeselect or signal someone
*/
spl(sr);
if (!sys_q[READY_Q]) {
/* hmm, no-one is ready to run. might be a deadlock, might not.
* first, try waking up any napping processes; if that doesn't work,
* run the root process, just so we have someone to charge time
* to.
*/
wake(SELECT_Q, (long)nap);
sr = spl7();
if (!sys_q[READY_Q]) {
p = rootproc; /* pid 0 */
rm_q(p->wait_q, p);
add_q(READY_Q, p);
}
spl(sr);
}
sr = spl7();
/*
* Walk through the ready list, to find what process should run next.
* Lower priority processes don't get to run every time through this
* loop; if "p->slices" is positive, it's the number of times that they
* will have to miss a turn before getting to run again
*/
/*
* Loop structure:
* while (we haven't picked anybody) {
* for (each process) {
* if (sleeping off a penalty) {
* decrement penalty counter
* }
* else {
* pick this one and break out of both loops
* }
* }
* }
*/
p = 0;
while (!p) {
for (p = sys_q[READY_Q]; p; p = p->q_next) {
if (p->slices > 0)
p->slices--;
else
break;
}
}
/* p is our victim */
rm_q(READY_Q, p);
spl(sr);
if (save_context(&(curproc->ctxt[CURRENT]))) {
/*
* restore per-process variables here
*/
#ifndef MULTITOS
#ifdef FASTTEXT
if (!hardscroll)
*((void **)LOGBASE) = curproc->logbase;
#endif
#endif
swap_in_curproc();
do_wakeup_things(sr, 1, cond);
return (onsigs != curproc->nsigs);
}
/*
* save per-process variables here
*/
#ifndef MULTITOS
#ifdef FASTTEXT
if (!hardscroll)
curproc->logbase = *((void **)LOGBASE);
#endif
#endif
curproc->ctxt[CURRENT].regs[0] = 1;
curproc = p;
proc_clock = TIME_SLICE; /* fresh time */
if ((p->ctxt[CURRENT].sr & 0x2000) == 0) { /* user mode? */
leave_kernel();
}
assert(p->magic == CTXT_MAGIC);
change_context(&(p->ctxt[CURRENT]));
/* not reached */
return 0;
}
/*
* wake(que, cond): wake up all processes on the given queue that are waiting
* for the indicated condition
*/
INLINE static void
do_wake (int que, long cond)
{
PROC *p;
top:
for(p = sys_q[que]; p;) {
short s = spl7();
PROC *q;
/* check p is still on the right queue, maybe an interrupt just woke it... */
if (p->wait_q != que) {
spl(s);
goto top;
}
q = p;
p = p->q_next;
if (q->wait_cond == cond) {
rm_q(que, q);
add_q(READY_Q, q);
}
spl(s);
}
}
void ARGS_ON_STACK
wake(que, cond)
int que;
long cond;
{
if (que == READY_Q) {
ALERT("wake: why wake up ready processes??");
return;
}
if (sleepcond == cond)
sleepcond = 0;
do_wake(que, cond);
}
/*
* iwake(que, cond, pid): special version of wake() for IO interrupt
* handlers and such. the normal wake() would lose when its
* interrupt goes off just before a process is calling sleep() on the
* same condition (similar problem like with wakeselect...)
*
* use like this:
* static ipid = -1;
* static volatile sleepers = 0; (optional, to save useless calls)
* ...
* device_read(...)
* {
* ipid = curproc->pid; (p_getpid() for device drivers...)
* while (++sleepers, (not ready for IO...)) {
* sleep(IO_Q, cond);
* if (--sleepers < 0)
* sleepers = 0;
* }
* if (--sleepers < 0)
* sleepers = 0;
* ipid = -1;
* ...
* }
*
* and in the interrupt handler:
* if (sleepers > 0) {
* sleepers = 0;
* iwake(IO_Q, cond, ipid);
* }
*
* caller is responsible for not trying to wake READY_Q or other nonsense :)
* and making sure the passed pid is always -1 when curproc is calling
* sleep() for another than the waked que/condition.
*/
void ARGS_ON_STACK
iwake(que, cond, pid)
int que;
long cond;
short pid;
{
if (pid >= 0) {
short s = spl7();
if (iwakecond == cond) {
spl(s);
return;
}
if (curproc->pid == pid && !curproc->wait_q)
iwakecond = cond;
spl(s);
}
do_wake(que, cond);
}
/*
* wakeselect(p): wake process p from a select() system call
* may be called by an interrupt handler or whatever
*/
void ARGS_ON_STACK
wakeselect (PROC *p)
{
short s;
extern short select_coll; /* in dosfile.c */
s = spl7(); /* block interrupts */
if(p->wait_cond == (long)wakeselect ||
p->wait_cond == (long)&select_coll) {
p->wait_cond = 0;
}
if (p->wait_q == SELECT_Q) {
rm_q(SELECT_Q, p);
add_q(READY_Q, p);
}
spl(s);
}
/*
* dump out information about processes
*/
/*
* kludge alert! In order to get the right pid printed by FORCE, we use
* curproc as the loop variable.
*
* I have changed this function so it is more useful to a user, less to
* somebody debugging MiNT. I haven't had any stack problems in MiNT
* at all, so I consider all that stack info wasted space. -- AKP
*/
#ifdef DEBUG_INFO
static const char *qstring[] = {
"run", "ready", "wait", "iowait", "zombie", "tsr", "stop", "select"
};
/* UNSAFE macro for qname, evaluates x 1, 2, or 3 times */
#define qname(x) ((x >= 0 && x < NUM_QUEUES) ? qstring[x] : "unkn")
#endif
#include "loadave.h"
unsigned long uptime = 0;
unsigned long avenrun[3] = {0,0,0};
short uptimetick = 200;
static int number_running;
static int one_min_ptr = 0, five_min_ptr = 0, fifteen_min_ptr = 0;
static unsigned long sum1 = 0, sum5 = 0, sum15 = 0;
static unsigned char one_min[SAMPS_PER_MIN];
static unsigned char five_min[SAMPS_PER_5MIN];
static unsigned char fifteen_min[SAMPS_PER_15MIN];
void
DUMPPROC()
{
#ifdef DEBUG_INFO
PROC *p = curproc;
FORCE("Uptime: %ld seconds Loads: %ld %ld %ld Processes running: %d",
uptime,
(avenrun[0]*100)/2048 , (avenrun[1]*100)/2048, (avenrun[2]*100/2048),
number_running);
for (curproc = proclist; curproc; curproc = curproc->gl_next) {
FORCE("state %s PC: %lx BP: %lx",
qname(curproc->wait_q),
curproc->ctxt[SYSCALL].pc,
curproc->base);
}
curproc = p; /* restore the real curproc */
#endif
}
unsigned long
gen_average(sum, load_ptr, max_size)
unsigned long *sum;
unsigned char *load_ptr;
int max_size;
{
int old_load, new_load;
old_load = (int)*load_ptr;
new_load = number_running;
*load_ptr = (char)new_load;
*sum += ((long) (new_load - old_load) * LOAD_SCALE);
return (*sum / max_size);
}
void
calc_load_average()
{
PROC *p;
uptime++;
uptimetick += 200;
if (uptime % 5) return;
number_running = 0;
for (p = proclist; p; p = p->gl_next)
if (p != rootproc)
if ((p->wait_q == 0) || (p->wait_q == 1))
number_running++;
avenrun[0] = gen_average(&sum1, &one_min[one_min_ptr++],
SAMPS_PER_MIN);
if (one_min_ptr == SAMPS_PER_MIN)
one_min_ptr = 0;
avenrun[1] = gen_average(&sum5, &five_min[five_min_ptr++],
SAMPS_PER_5MIN);
if (five_min_ptr == SAMPS_PER_5MIN)
five_min_ptr = 0;
avenrun[2] = gen_average(&sum15, &fifteen_min[fifteen_min_ptr++],
SAMPS_PER_15MIN);
if (fifteen_min_ptr == SAMPS_PER_15MIN)
fifteen_min_ptr = 0;
}