home *** CD-ROM | disk | FTP | other *** search
- //
- // process.c:
- //
- // Implementation of process class.
- //
- // Main routine p_wait is where each scheduling thread lives.
- //
- // Modification History:
- //
- // 12-Jan-90 John Faust
- // Add support for processor affinity on Sequent Symmetry.
- //
- // 05-Dec-89 John Faust
- // Remove all stack free lists. Allocate stacks of fixed size when thread
- // is allocated. Stack remains associated with owning thread. More
- // efficient (removes search of stack freelist for stack of proper size,
- // allocation of stack if one of proper size not found, and eliminates
- // need to balance stack freelists).
- //
- // 30-Nov-1989 John Faust
- // Incorporate Bob Sandstro fix which allows gprof monitor files to be
- // captured.
- //
- // 16-Nov-1989 John Faust
- // Implement per-processor free lists of stacks.
- //
- // 08-Nov-1989 John Faust
- // Implement per-processor free lists of thread templates.
- //
-
-
- #define _PROCESS_C
-
-
- #include <sys/types.h>
- #include <signal.h>
- #include <osfcn.h>
- #include "presto.h"
-
- #ifdef i386
- #include <sys/tmp_ctl.h>
- #endif i386
-
- Process staticproc;
- Process *sysproc = &staticproc;
-
- #ifdef GPROF
- extern char *_mon_file;
- static char mon_name[20];
- #endif GPROF
-
- private_t Process *thisproc = 0; // always ME!
-
- extern Main *MAIN;
-
- //
- // This is the global free list of thread templates. Deleted threads
- // go here when the local free list of the owning processor is full.
- // Threads are taken from here only when a local free list is exhausted.
- // The local lists (see process.h) are non-shared and are thus unlocked,
- // for speed. The global list is shared and is thus locked. That makes
- // it slow, so we only access it when we really have to.
- //
- static shared_t ThreadQ thread_global_freelist (TS_ANY);
-
-
- //
- // This constructor is called to create the root process.
- //
- // ptag proc constructor is useful for getting a handle on the root
- // process. Since we weren't around when he got forked, we kluge
- // with this.
- //
- Process::Process(int ptag, int id)
- {
- int p_continue(); // signal handler
- int i;
- Thread* tlist [1+NUMPROCS+NUM_PREALLOC_THREADS];
-
- #ifdef DEBUG_STARTUP
- cout << "making root process\n";
- #endif DEBUG_STARTUP
-
- if (ptag != P_ROOT /* || no root exists */ ) {
- error("Invalid attempt to flag root Process\n");
- }
-
- //
- // Create process local thread freelist, and preallocate
- // some threads.
- //
- // The root process is responsible for creating the
- // schuduler_starter thread as well as each of the main threads.
- // Therefore, preallocate enough extra threads (1 for the
- // scheduler_starter, one for each possible processor).
- //
- // Be sure to use MAINSTACKSIZE for the main threads,
- // and STACKSIZE for the remaining preallocated threads.
- // For now, STACKSIZE is used for all preallocated threads,
- // and MAINSTACKSIZE ends up being ignored.
- //
- p_thread_freelist = new ThreadQUnlocked (TS_ANY);
- for (i=0; i<1+NUMPROCS+NUM_PREALLOC_THREADS; i++)
- tlist [i] = thisthread->newthread ("prealloc", 0,
- DEFSTACKSIZ);
- for (i=0; i<1+NUMPROCS+NUM_PREALLOC_THREADS; i++)
- free_thread (tlist[i]); // put on local freelist
-
- //cout << "threads have been preallocated for root process\n";
-
- p_id = id;
- p_ppid = getppid();
- p_pid = getpid();
- p_name = "ROOT";
- p_flags = P_ROOT;
- p_state = S_RUN; // obviously
- p_request = 0;
- p_thread = 0;
- p_schedthread = thisthread;
- thisproc = this;
- #ifdef DEBUG_STARTUP
- cout << form ("root process - this = %x\n", this); cout.flush();
- #endif DEBUG_STARTUP
-
- //
- // Set processor affinity if applicable.
- //
- #ifdef i386
-
- #ifdef DEBUG_STARTUP
- cout << form ("root - affinity = %d\n", MAIN->get_affinity());
- cout.flush ();
- #endif DEBUG_STARTUP
- if (MAIN->get_affinity ())
- {
- extern int tmp_affinity (int);
-
- if (tmp_affinity (thisproc->id()) == -1)
- cout << "unable to set affinity on root process "
- << thisproc->id() << "\n";
- // else
- // cout << "affinity set on root process "
- // << thisproc->id() << "\n";
- cout.flush ();
- }
- #endif i386
-
- signal(SIGCONT, p_continue);
-
- #ifdef DEBUG_STARTUP
- cout << "done making root process\n";
- #endif DEBUG_STARTUP
- }
-
-
- //
- // This constructor is the one invoked by newprocess. It creates all
- // processes with the exception of the root process and the original
- // static process.
- //
- Process::Process(char* name, int id, int delayedfork)
- {
- int i;
- Thread* tlist [NUM_PREALLOC_THREADS];
-
- #ifdef DEBUG_STARTUP
- cout << "making new process " << name << "\n";
- #endif DEBUG_STARTUP
-
- //
- // Create process local thread freelist, and preallocate
- // some threads.
- //
- p_thread_freelist = new ThreadQUnlocked (TS_ANY);
- for (i=0; i<NUM_PREALLOC_THREADS; i++)
- tlist [i] = thisthread->newthread ("prealloc", 0, DEFSTACKSIZ);
- for (i=0; i<NUM_PREALLOC_THREADS; i++)
- free_thread (tlist[i]); // put on local freelist
-
- //cout << "threads have been preallocated for process " << name << "\n";
-
- p_name = name;
- p_request = 0;
- p_flags = 0;
- p_id = id;
- p_schedthread = p_thread = 0;
- if (!delayedfork) {
- #ifdef DEBUG_STARTUP
- cout << "not delayed fork, calling p_fork\n";
- #endif DEBUG_STARTUP
- p_fork();
- }
- else {
- #ifdef DEBUG_STARTUP
- cout << "delayed fork\n";
- #endif DEBUG_STARTUP
- p_state = S_DELAYEDFORK;
- }
- return;
- }
-
- //
- // Constructor for staticproc (sysproc).
- //
- Process::Process()
- {
- #ifdef DEBUG_STARTUP
- // cout << "making static sysproc\n"; cout here = segmentation fault
- #endif DEBUG_STARTUP
-
- #if (sun && THREAD_HAS_INTERRUPTIBLE_FIELD)
- p_interruptible = 0;
- #endif
- #ifdef vax
- p_interruptible = 0;
- #endif vax
- p_name = "sysproc";
- p_thread_freelist = new ThreadQUnlocked (TS_ANY);
-
- #ifdef DEBUG_STARTUP
- // cout << "done making static sysproc\n";
- #endif DEBUG_STARTUP
- }
-
- //
- // Delay the fork until after the return of the constructor. Derived
- // classes will need to take advantage of this to ensure that the
- // virtual table in the derived class is properly initalized. Use
- // for a derived class is:
- //
- // DerivedProcess::DerivedProcess(args) : (name, id, S_DELAYEDFORK)
- // {
- // initialize derived part
- // Process::p_fork();
- // /* only parent returns to here. Child never does */
- // }
- //
- // Must be careful to use private stack when forking into child; else
- // parent and child can race on the shared stack (leads to strange
- // core dumps -- hard to diagnose). This should be cleaned up; use of
- // asm-functions may help.
-
- void
- Process::p_fork()
- {
- int pid;
- int spinonfork = 1; // hold child until done
- static private_t int private_stack[256];
-
- extern int _rtmp;
-
- #ifdef DEBUG_STARTUP
- cout << "in p_fork\n";
- #endif DEBUG_STARTUP
-
- #ifdef sun
- _rtmp = (int) &private_stack[sizeof(private_stack) / sizeof(int)];
- #endif sun
- #ifdef sequent
- _rtmp = (int) &private_stack[sizeof(private_stack) / sizeof(int)];
- #endif sequent
-
- #ifdef mc68020
- asm("movl sp, a0");
- asm("movl __rtmp, sp");
- asm("movl a0, __rtmp");
- #endif mc68020
- #ifdef i386
- asm("xchgl %esp, __rtmp");
- #endif i386
- #ifdef ns32000
- asm("sprd sp, r0");
- asm("lprd sp, __rtmp");
- asm("movd r0, __rtmp");
- #endif ns32000
-
- p_state = S_FORKING;
-
- pid = fork();
- switch (pid) {
- case -1: // fork error
- #ifdef mc68020
- asm("movl __rtmp, sp");
- #endif mc68020
- #ifdef i386
- asm("movl __rtmp, %esp");
- #endif i386
- #ifdef ns32000
- asm("lprd sp, __rtmp");
- #endif ns32000
- p_pid = -1;
- return;
- case 0: // child
- p_ppid = getppid();
- p_pid = getpid();
- #ifdef GPROF
- (void) sprintf (mon_name, "%s.%d", _mon_file, p_pid);
- _mon_file = mon_name;
- #endif GPROF
- p_runchild(&spinonfork);
- // NOTREACHED
- error("Process forked child returned???");
- default: // parent: can't return until child finished with stack
- #ifdef mc68020
- asm("movl __rtmp, sp");
- #endif mc68020
- #ifdef i386
- asm("movl __rtmp, %esp");
- #endif i386
- #ifdef ns32000
- asm("lprd sp, __rtmp");
- #endif ns32000
- p_pid = pid;
- while (spinonfork)
- continue;
- }
- }
-
-
- //
- // Fake being able to virtualize the constructor
- //
- Process*
- Process::newprocess(char* name, int id)
- {
- return new Process(name, id);
- }
-
-
- //
- // Run the child scheduler. Hold onto the parent until we get all
- // that we need from the args (this especially). See comments.
- //
- void
- Process::p_runchild(int *spinonfork)
- {
- extern int _rtmp;
-
- Thread* t = thisthread->newthread(p_name, 0, DEFSTACKSIZ);
-
- #ifdef DEBUG_STARTUP
- cout << "in p_runchild (after fork)\n";
- #endif DEBUG_STARTUP
-
- thisthread = t; // get what we need from our parents
- thisproc = this; // stack
-
- //
- _rtmp = (int)(thisthread->stack()->top());
- {
- #ifdef vax
- // asm("movl __rtmp, sp");
- #endif
- #ifdef mc68020
- asm("movl __rtmp, sp");
- #endif mc68020
- #ifdef ns32000
- asm("lprd sp, __rtmp");
- #endif
- #ifdef i386
- asm("movl __rtmp, %esp");
- #endif
- }
- //
- // should have secondary entry point into start to avoid the test
- // for a scheduler.
- //
- //
- thisthread->setflags(TF_SCHEDULER|TF_KEEPSTACK|TF_NONPREEMPTABLE);
- thisthread->start(thisproc, (PFany)(thisproc->invoke));
- thisthread->setproc(thisproc);
-
- thisproc->p_schedthread = thisthread;
- *spinonfork = 0; // Let him go...
- //
- // The parent has just returned using the frame that our
- // fp references. This means that we can never return beyond
- // this routine, AND we can't reference any of the params
- // passed to us on the stack (use thisthread and thisproc from
- // here on).
- //
-
- //
- // Set processor affinity if applicable.
- //
- #ifdef i386
-
- #ifdef DEBUG_STARTUP
- cout << form ("affinity = %d\n", MAIN->get_affinity()); cout.flush ();
- #endif DEBUG_STARTUP
- if (MAIN->get_affinity ())
- {
- extern int tmp_affinity (int);
-
- if (tmp_affinity (thisproc->id()) == -1)
- cout << "unable to set affinity on process "
- << thisproc->id() << "\n";
- // else
- // cout << "affinity set on process "
- // << thisproc->id() << "\n";
- cout.flush ();
- }
- #endif i386
- #ifdef PROFILE
- QProcInit(thisproc->id());
- #endif
-
- //
- // Can't use run since it won't let us idle "thisthread" and run
- // "thisthread" at the same time.
- //
- thisthread->isrunning();
- thisthread->runrun(); // fall into p_wait
- thisthread->isnotrunning();
-
- //
- // fall out of p_wait on its return to here
- //
- delete thisproc;
- //
- // should never get here
- //
- error("PROCESS DESTRUCTOR RETURNED");
- }
-
- //
- // ~Process: kill a Process.
- // If we are the process to be killed, then we mark ourselves as a
- // zombie and _exit() (no destructors will be called). If we are
- // trying to destroy another process, then we mark it
- // as S_EXITING and then ask the process to actually return.
- // This will have it fall back into runrun and then into
- // p_runchild, where we will get called to kill it running
- // as the process which is being asked to die.
- //
- // Doing it this way allow processes to perform whatever cleanup
- // they feel like before actually disappearing.
- //
- // If we are the root process, we just return quietly
- //
- //
-
- Process::~Process()
- {
- extern void shfree(Process*);
- extern void free(Process*);
-
-
- if (/*this &&*/ ((this->p_state&S_EXITING) == 0)) {
- int pid = p_pid;
- if (this == thisproc) {
- this->p_state = S_ZOMBIE;
- if (this->isroot()) {
- this = 0;
- return;
- }
- //
- // Our stack frame is gonna be all screwed up here
- // since we started off with the fp running
- // on our parent's frame, and he has now return.
- // We can never return from this routine.
- //
- #ifdef GPROF
- monitor(0);
- #endif GPROF
- _exit(0);
- } else {
- this->p_state = S_EXITING;
- this->request(R_RETURN);
- }
- }
- this = 0;
- }
-
- int
- Process::invoke()
- {
- #ifdef DEBUG_STARTUP
- cout << "in Process::invoke()\n"; cout.flush ();
- #endif DEBUG_STARTUP
-
- p_wait();
- return 0;
- }
-
- //
- // Parent (or sibling) wakes up a looping Process here with the
- // "special" request code
- //
- int
- Process::request(int req)
- {
-
- //
- // If someone is stupid enough to make a request on a proc that
- // is already servicing someone else... then they are just
- // gonna have to wait their turn!
- //
- while (p_request != R_NULL) {
- if (p_request&(R_DIE|R_RETURN)) // no way to satisfy
- return -1;
- }
-
- p_request = req;
- #ifdef TDEBUG
- cerr << thisproc->name() << "making request " << req <<
- " on " << this->name() << "\n";
- #endif
-
- if (p_state&S_OSPAUSE) { // sleeping in os
- cerr << "\nWaking up " << this << "\n";
- return (kill(p_pid, SIGCONT));
- } else
- return 0;
- }
-
-
- //
- // p_wait: hang around and wait for something interesting
- // to happen. That is, loop until someone bangs
- // on our door, or until there is a readythread to
- // start working on, OR we are not the root process, but our
- // parent process seems to have disappeared (in which case
- // we abort the scheduler, killing ourselves and all our
- // siblings). We only check the last case "every so often" when
- // can't find anything else to do.
- // This is an example of a "heuristic." AI in action!
- //
- // This doesn't guarantee that the system will always stop. If
- // the parent dies of as the result of an uncaught signal while
- // holding a spinlock, the rest of the system could be blocked.
- // If all other process are blocked, then the system will
- // spin forever waiting for the holding process to relase
- // the spinlock (which will never happend). Lesson:
- // Don't kill -9 the root process.
- //
- void
- Process::p_wait()
- {
- int idlespan = 0;
-
- #ifdef PROFILE
- QIdleLoopBegin();
- #endif
- #ifdef DEBUG_STARTUP
- cout << "in Process::p_wait()\n"; cout.flush ();
- #endif DEBUG_STARTUP
-
- for (;;) {
- p_state = S_WAIT;
- // wait until readythread, or until we get asynch request
- if ( (p_request == NULL) && (p_thread = sched->getreadythread()) == NULL) {
- if (idlespan++ == 50000) {
- // check if our parent has died and we are not root
- if (!isroot() && getppid() != this->ppid()) {
- // kill myself and all siblings
- sched->abort(-SIGKILL);
- } else
- idlespan = 0;
- }
- continue;
- }
-
- if (p_request) {
- //
- // assumption is that only one thread can be diddling
- // with processes at a time... otherwise this might
- // not work.
- //
-
- if (p_request & R_RETURN) {
- // if ( (p_flags&P_ROOT) == 0)
- // error("PROCS CANT RETURN YET\n");
- // else
- return;
- }
-
- //
- // They can only die
- //
- if (p_request & R_DIE) {
- delete this;
- // not reached!
- }
-
- if (p_request & R_PARK) {
- this->p_pause();
- continue;
- };
- } else { // must have a readythread
- #ifdef DEBUG_STARTUP
- cout << "p_wait - got ready thread\n"; cout.flush ();
- #endif DEBUG_STARTUP
- if (p_thread->flags()&TF_SCHEDULER) {
- p_thread->error("Can't schedule a scheduling thread");
- continue; // NOT REACHED?
- }
- p_state = S_RUN;
- (void)p_thread->run();
- }
- // others
- }
- }
-
-
- //
- // p_pause: internal version of park
- //
- void
- Process::p_pause()
- {
- if (p_state != S_WAIT)
- error("p_pause called to pause non spinning process");
-
- p_state = S_OSPAUSE;
- p_request = 0; // must clear here, not above
- ::pause();
- p_state = S_WAIT;
- }
-
- //
- // park: Give it a rest buddy boy
- //
- void
- Process::park()
- {
- if (this == thisproc) {
- cerr << "Warning: putting myself to sleep\n";
- this->p_pause();
- }
- else {
- // baby you can drive my car
- p_request = R_WAKEUP|R_PARK;
- }
- }
-
- void
- Process::drive()
- {
- if (this == thisproc)
- return;
- else {
- if (p_state != S_OSPAUSE)
- cerr << "Warning: proc not parked\n";
- ::kill(p_pid, SIGCONT);
- }
- }
-
-
- void
- Process::error(char *s)
- {
- cerr << "Process error " << s << " " << this << "\n";
- fatalerror();
- }
-
- // signal handler
- int p_continue()
- {
- return 0;
- }
-
-
- //
- // A process comes here when it's local thread freelist is empty.
- // Get any extra threads that are lying around in the global thread freelist.
- // Could optimize to lock global list and grab all threads before unlock,
- // instead of doing lock-get-unlock for each thread.
- //
- void
- Process::get_gbl_threads ()
- {
- register Thread* t;
- int i;
-
- if (thread_global_freelist.length() == 0) return;
- for (i=0; i<LCL_THREAD_FREELIST_THRESH/2-1; i++)
- {
- t = thread_global_freelist.get ();
- if (t == 0) break;
- p_thread_freelist->append (t);
- }
- }
-
- //
- // A process comes here when it's local thread freelist is too full.
- // Put some of the extra threads into the global thread freelist.
- // Could optimize to lock global list and append all extras before unlock,
- // instead of doing lock-append-unlock for each thread.
- //
- void
- Process::free_gbl_threads ()
- {
- Thread* t;
- int i;
-
- for (i=p_thread_freelist->length(); i>LCL_THREAD_FREELIST_THRESH/2; i--)
- {
- t = p_thread_freelist->get ();
- if (t == 0) break;
- if (thread_global_freelist.length() > GBL_THREAD_FREELIST_MAX)
- {
- //#ifdef DEBUG_STARTUP
- cout << "Global thread freelist too big, deleting thread\n";
- //#endif DEBUG_STARTUP
- delete (t);
- }
- else
- thread_global_freelist.append (t);
- }
- }
-
-
- void
- Process::print(ostream& s)
- {
- s << form("(Process)=0x%x, p_id=%d, p_name=%s, p_pid=%d, p_ppid=%d, p_state=0x%x, p_request=%d",this,p_id,p_name,p_pid,p_ppid,p_state,p_request);
- }
-