Minix1.5/amoeba/kernel/portcache.c

/****************************************************************************/
/*									    */
/* (c) Copyright 1988 by the Vrije Universiteit, Amsterdam, The Netherlands */
/*									    */
/*    This product is part of the  Amoeba  distributed operating system.    */
/*									    */
/*    Permission to use, sell, duplicate or disclose this software must be  */
/* obtained in writing.  Requests for such permissions may be sent to	    */
/*									    */
/*									    */
/*		Dr. Andrew S. Tanenbaum					    */
/*		Dept. of Mathematics and Computer Science		    */
/*		Vrije Universiteit					    */
/*		Postbus 7161						    */
/*		1007 MC Amsterdam					    */
/*		The Netherlands						    */
/*									    */
/****************************************************************************/

#define NDEBUG
#define PORTCACHE

/*
 * This module does the port management. It keeps track of the local servers
 * doing a ``getreq'' on a port, local clients waiting for a server on some
 * port, and interlocal servers addressed by some port. This last category of
 * ports may be forgotten, or may be incorrect.
 *
 * The following routines are defined:
 *	portinstall(port, where, wait);
 *	portlookup(port, wait, delete);
 *	portremove(port, location);
 *	portquit(port, task);
 *
 * ``Portinstall'' is called when a port is assumed to be at location
 * ``where.'' If ``wait'' is set, the port is local.
 * ``Portlookup'' is called to find a port in the cache. If ``wait'' is
 * set, the routine will block until the port is found. If ``delete'' is
 * set, the port must be removed when it is found.
 * ``Portremove'' removes a port from the cache which is thought
 * of to be at the specified location.
 * When a port doesn't have to be located anymore for some task, ``portquit''
 * takes care of that.
 */

#include "kernel.h"
#include "amoeba.h"
#include "global.h"
#include "task.h"
#include "internet.h"
#include "assert.h"

extern struct task	task[];

#ifdef STATISTICS
#include "portstat.h"

struct portstat portstat;
#define STINC(x)	portstat.x++
#else
#define STINC(x)
#endif

#include "conf.h"

#define LOGHASH		   5	/* log sizeof hash table of local ports */

struct porttab {
	port p_port;			/* the port this entry is about */
	unshort p_idle;			/* idle timer */
	address p_location;		/* where is it? (0 = being located) */
	address p_asker;		/* address of someone interested */
	struct porttab *p_nextport;	/* port with same hash value */
	struct task *p_tasklist;	/* list of tasks */
};

#define NILPORTTAB	((struct porttab *) 0)

#define NHASH		(1 << LOGHASH)
#define HASHMASK	(NHASH - 1)

#define hash(p)		(* (unshort *) (p) & HASHMASK)

/* MINIX can't allocate memory in the kernel at run-time
extern unshort nport;
PRIVATE struct porttab *porttab, *lastport, *hashtab[NHASH], *portfree;
*/
PRIVATE struct porttab porttab[NPORTS];
PRIVATE struct porttab *lastport, *hashtab[NHASH], *portfree;

#ifndef NONET
#ifndef NOCLIENT

#define NLOCATE            8    /* max. number of ports to locate */
PRIVATE port loctab[NLOCATE];
PRIVATE unshort loccnt, loctim, locthissweep;
extern unshort minloccnt, maxloccnt;

#endif
#endif

/* Allocate an entry in the hash table at location ``ht.'' */
static struct porttab *allocate(ht, p)
struct porttab **ht;
port *p;
{
  register struct porttab *pt;
  STINC(pts_alloc);
  if ((pt=portfree) == 0) {
	STINC(pts_full);
	portpurge();		/* total cleanup, not likely to happen */
	if ((pt=portfree) == 0)
		return 0;
  }
  portfree = pt->p_nextport;
  pt->p_nextport = *ht;
  *ht = pt;
  pt->p_port = *p;
  return pt;
}

/* Install a port in the hash table.  If ``wait'' is set, the location will
 * be this machine and is certain.  If not, the location is somewhere else
 * and uncertain.
 */
portinstall(p, where, wait)
register port *p;
address where;
{
  register struct porttab **ht, *pt;
  register struct task *t;
  extern address local;

  ht = &hashtab[hash(p)];
  for (pt = *ht; pt != NILPORTTAB; pt = pt->p_nextport)
	if (PortCmp(&pt->p_port, p)) {
		if (pt->p_location == SOMEWHERE) {
			compare(pt->p_tasklist, !=, NILTASK);
			do {
				t = pt->p_tasklist;
				t->pe_location = where;
				STINC(pts_wakeup);
				wakeup((event_t) &t->pe_location);
			} while ((pt->p_tasklist = t->pe_link) != NILTASK);
		}
#ifndef NOCLIENT
		else if (siteaddr(pt->p_location) == local && !wait &&
							pt->p_tasklist != 0)
			return;
#endif
		break;
	}
  if (pt == NILPORTTAB && (pt = allocate(ht, p)) == NILPORTTAB)
#ifndef NOCLIENT
	if (wait)
#endif
		panic("portcache full for servers", 0x8000);
#ifndef NOCLIENT
	else		/* no room left, so forget it */
		return;
#endif
  pt->p_location = where;
#ifndef NOCLIENT
  if (wait) {	/* task is going to await a client, so leave it immortal */
#endif
	compare(area(where), ==, LOCAL);
	t = &task[tasknum(where)];
	t->pe_location = where;
	t->pe_link = pt->p_tasklist;
	pt->p_tasklist = t;
#ifndef NONET
	if (pt->p_asker != NOWHERE) {
		STINC(pts_here);
		hereport(pt->p_asker, p, (unsigned )1);
		pt->p_asker = NOWHERE;
	}
#endif
#ifndef NOCLIENT
  }
#endif
  pt->p_idle = 0;
}

#ifndef NONET
#ifndef NOCLIENT
/* Broadcast a locate message
 */
static sendloc(){
  register struct porttab *pt;
  register unsigned n = 0;

  if (locthissweep) {
	/* During this clocktick we already sent out a broadcast packet.
	 * To prevent buggy userprograms from creating a broadcast storm
	 * we do not send another one, we just prepare for it to be done 
	 */
	 STINC(pts_nolocate);
	 loccnt = maxloccnt;
	 return;
  }
  for (pt = porttab; pt < lastport; pt++)
	if (pt->p_location == SOMEWHERE) {
		loctab[n++] = pt->p_port;
		if (n == NLOCATE)
			break;
	}
  if (n) {
	locthissweep = 1;
	whereport(loctab, n);	/* send out the broadcast locate */
  } else
	loctim = 0;	/* No locates necessary anymore */
  loccnt = 0;
}

#endif NOCLIENT
#endif NONET

/* Look whether port p is in the portcache.  You can specify whether you
 * want to wait for the information and whether you want to delete it.
 */
address portlookup(p, wait, del)
register port *p;
{
  register struct porttab **ht, *pt;
  register struct task *c, *t;
  register address location;

  STINC(pts_lookup);
  ht = &hashtab[hash(p)];
  for (pt = *ht; pt != NILPORTTAB; pt = pt->p_nextport)
	if (PortCmp(&pt->p_port, p)) {	/* found it */
		location = pt->p_location;
		switch (area(location)) {
		case LOCAL:		/* local server */
			if (pt->p_tasklist == 0)
				break;
			if (del) {	/* remove it */
				pt->p_tasklist = pt->p_tasklist->pe_link;
				if ((t = pt->p_tasklist) != NILTASK)
					pt->p_location = t->pe_location;
			}
			pt->p_idle = 0;
			STINC(pts_flocal);
			return(location);

		case GLOBAL:		/* remote server */
			compare(pt->p_tasklist, ==, NILTASK);
			pt->p_idle = 0;
			STINC(pts_fglobal);
			return(location);
		
		case DONTKNOW:		/* somebody else wants to know too */
			compare(pt->p_tasklist, !=, NILTASK);
			break;
		
		default:
			assert(0);
		}
		break;
	}
  /* The port wasn't in the port cache */
#ifndef NOCLIENT
  if (wait) {		/* wait for it */
	if (pt == NILPORTTAB && (pt = allocate(ht, p)) == NILPORTTAB)
		panic("portcache full for clients", 0x8000);
	pt->p_location = SOMEWHERE;
	c = curtask;
	c->pe_link = pt->p_tasklist;
	pt->p_tasklist = c;
#ifndef NONET
	STINC(pts_locate);
	sendloc();
	loctim = minloccnt;
#endif
	c->pe_location = SOMEWHERE;
	if (sleep((event_t) &c->pe_location))
		assert(pt->p_tasklist != c);
	pt->p_idle = 0;
	return(c->pe_location);
  }
  else
#endif NOCLIENT
	return(NOWHERE);
}

/* Port p isn't at location ``location'' anymore */
portremove(p, location)
port *p;
address location;
{
  register struct porttab *pt, **ht;
  register struct task *t;

  for (ht = &hashtab[hash(p)]; (pt= *ht) != NILPORTTAB; ht = &pt->p_nextport)
	if (PortCmp(&pt->p_port, p)) {
		if (pt->p_location == location) {
			if ((t = pt->p_tasklist) != NILTASK) {
				compare(area(location), ==, LOCAL);
				compare(t->pe_location, ==, location);
				if ((pt->p_tasklist = t->pe_link) != NILTASK) {
					pt->p_location =
						pt->p_tasklist->pe_location;
					break;
				}
			} else {
				*ht = pt->p_nextport;
				pt->p_location = NOWHERE;	/* for dump */
				pt->p_nextport = portfree;
				portfree = pt;
			}
		}
		else if ((t = pt->p_tasklist) != NILTASK)
			while (t->pe_link != NILTASK)
				if (t->pe_link->pe_location == location) {
					t->pe_link = t->pe_link->pe_link;
					break;
				}
				else
					t = t->pe_link;
		return;
	}
}

#ifndef NOCLIENT
/* Task ``s'' isn't interested anymore */
portquit(p, s)
register port *p;
register struct task *s;
{
  register struct porttab *pt, **ht;
  register struct task *t;

  for (ht = &hashtab[hash(p)]; (pt= *ht) != NILPORTTAB; ht = &pt->p_nextport)
	if (PortCmp(&pt->p_port, p)) {
		if (pt->p_location != SOMEWHERE)
			return;
		if ((t = pt->p_tasklist) == s) {
			if ((pt->p_tasklist = s->pe_link) == NILTASK) {
				*ht = pt->p_nextport;
				pt->p_location = NOWHERE;	/* for dump */
				pt->p_nextport = portfree;
				portfree = pt;
			}
			s->pe_location = NOWHERE;
			wakeup((event_t) &s->pe_location);
		}
		else {
			while (t != NILTASK)
				if (t->pe_link == s) {
					t->pe_link = s->pe_link;
					s->pe_location = NOWHERE;
					wakeup((event_t) &s->pe_location);
					break;
				}
				else
					t = t->pe_link;
		}
		return;
	}
}
#endif NOCLIENT

#ifndef NONET

#define NHERE 8
PRIVATE port heretab[NHERE];

portask(asker, ptab, nports)		/* handle locate request */
address asker;
register port *ptab;
unsigned nports;
{
  register port *p,*q;
  register struct porttab **ht, *pt;
  register unsigned nfound;

  STINC(pts_portask);
  nfound = 0; q = heretab;
  for(p=ptab; nports--; p++) {
	ht = &hashtab[hash(p)];
	for (pt = *ht; pt != NILPORTTAB; pt = pt->p_nextport)
		if (PortCmp(&pt->p_port, p)) {	/* found it */
			if (area(pt->p_location) == LOCAL) {
				if (pt->p_tasklist == 0) {
					/* just record someone was interested */
					pt->p_asker = asker;
					break;
				}
				if (nfound < NHERE) {
					*q++ = *p;
					nfound++;
				}
				pt->p_idle = 0;
			}
		}
  }
  if (nfound) {
	STINC(pts_portyes);
	hereport(asker, heretab, nfound);
  }
}
#endif

#ifndef NDEBUG
portdump(){
  register struct porttab *pt;
  register struct task *t;

  printf("\n PORT  LOCATION IDLE TASKS\n");
  for (pt = porttab; pt < lastport; pt++)
	if (pt->p_location != NOWHERE) {
		prport(&pt->p_port);
		printf("   %4x   %4d", pt->p_location, pt->p_idle);
		for (t = pt->p_tasklist; t != NILTASK; t = t->pe_link)
			printf(" {%d, %x}", t - task, t->pe_location);
		printf("\n");
	}
}
#endif

/* Initialize tables and free list */
portinit(){
  register struct porttab *pt;

/*  MINIX can't allocate data in the kernel at run-time
  extern char *aalloc();

  porttab = (struct porttab *) aalloc(nport * sizeof(struct porttab), 0);
  lastport = &porttab[nport];
*/
  lastport = &porttab[NPORTS];
  for (pt = porttab; pt < lastport; pt++) {
	pt->p_nextport = portfree;
	portfree = pt;
  }
}

/* called when freelist was empty, will throw away all mortal ports */
portpurge() {
  register struct porttab *pt,**ht,**htp;
 
  for (htp=hashtab; htp< &hashtab[NHASH]; htp++) {
	ht = htp;
	while ((pt = *ht) != 0) {
		if (pt->p_tasklist == 0){
			*ht = pt->p_nextport;
			pt->p_location = NOWHERE;	/* for dump */
			pt->p_nextport = portfree;
			portfree = pt;
		} else
			ht = &pt->p_nextport;
	}
  }
}

#define MAXSWEEP	 3000	/* dseconds maximum idle time for port */
#define SWEEPRESOLUTION	  100	/* accuracy */

portsweep() {
  register struct porttab *pt,**ht,**htp;
  static unshort cnt;

#ifndef NOCLIENT
#ifndef NONET
  locthissweep = 0;
  if (loctim && ++loccnt > loctim) {              /* send a locate message */
	STINC(pts_relocate);
        sendloc();
        loctim <<= 1;
        if (loctim > maxloccnt)
                loctim = maxloccnt;
	locthissweep = 0;
  }
#endif NONET
#endif
  if (++cnt<SWEEPRESOLUTION)
	return;
  for (htp=hashtab; htp< &hashtab[NHASH]; htp++) {
	ht = htp;
	while ((pt = *ht) != 0) {
		if (pt->p_tasklist == 0 && (pt->p_idle += cnt) > MAXSWEEP) {
			*ht = pt->p_nextport;
			pt->p_location = NOWHERE;	/* for dump */
			pt->p_nextport = portfree;
			portfree = pt;
			STINC(pts_aged);
		} else
			ht = &pt->p_nextport;
	}
  }
  cnt=0;
}