[comp.os.minix] shared code segments, multi-tasking

walls@killer.Dallas.TX.US (Monty Walls) (05/06/89)

--------------------------------------------------------------------------
	One method people might consider is using shared text segments.
Since most of the reason to use swapping is running out of memory while
doing a 'make' in the background while editing, the shared text segments
will free up 30k+ memory reasonably simply.  
	The changes to use shared code segments should all be 
confined to the MM and resonable simple.  I am assuming that the ACK
C compiler is segment aligning the starting point for the data space
in i+d mode.
	To improve the multi-tasking behavior when you are running
multiple tasks you might consider changing the size of a processor
time-slice.  I'm using MILLISEC defined as 40 in kernel/clock.c on a 
8mhz AT.  This seems to give a decent response time when using multiple
tasks.

	-Monty Walls
Work:						Home:
	MIS Division, Tech. Support			2224 Houston Apt #8
	Oklahoma Tax Commission				Norman, OK, 73701
	2501 N. Lincoln					USA
	OKC, OK, 73194 					Phone - 405-364-5123
	USA						uucp - killer!walls
	Phone - 405-521-4300

go@jacobs.CS.ORST.EDU (Gary Oliver) (05/08/89)

... Funny you should mentioned shared text...

A few weeks ago I happened to hack together this set of changes (for 1.3
though it has been tested on a 1.3c system belonging to a friend) and
thought you all might get some use out of it.

The basic strategy was to implement a "shared text" structure in the
memory manager task maintains a global copy of any text descriptors
that are candidates for sharing.  For my purposes, I defined "sharable"
any program compiled with separate I & D space.

Whenever a "shareable" program is exec'd, a scan is made in the
shareable text structure for a segment matching the device number,
inode number and change date of the program being exec'd.

If a match is found, a copy is made of the shared text info into the
callers [T] memory map and the reference counter in the shared text
structure is advanced.

If no match is found, the program is exec'd in the normal way.

After an exec of a non-shared program, a test is made to see if it
can be made shareable (separate I & D) and if so, a new shared info
structure is allocated (if possible) and the necessary information
is copied into the structure and the shared info reference count
is set to 1.

A change was made to the mproc structure (mproc.h) that added a
pointer to the shared info structure (if the program currently
running is shared or NULL if not shared.)

Whenever a fork() call is made, the proc structure is duplicated and
if the duplicate is of a shared text program, then no copy of the
text portion of memory is made and the shared info reference counter
is upped by 1.

Whenever a shared text program exits, the reference count in the
shared text structure is decremented and if zero, the shared text
memory is deallocated (unless sticky) and the structure is marked free.

The number of shared text elements is set by the constant NR_SHARE
(I placed mine in ../h/const.h)  Additionally, another ifdef "STICKY_SHARE"
is available that prevents freeing the text memory when all users
of the program have exited.  The upside of this addition is that
shared programs remain a little faster to exectute when run one
at a time.  The down side is that memory tends to get a little
fragmented from the haphazard order of freeing of these text segments.
No memory is lost, it just doesn't become available until no other
memory is available.   I chose a rather simple means to free the
unused shared text segments:  When alloc_mem is called (in alloc.c)
it first tries to allocate from free memory and if none is found,
all unreferenced shared text segments are freed and the alloc is
attempted a second time.  Cheap.

Anyway, I've been running about a week and no problems seem to exist
(running on two XTs - mine and a friend - and on an AT - three systems
in all.)  Try it.

One other potential change that you may want to make.  Since implementing
this change, it is quite a bit harder to run out of memory before
running out of process slots, so you may want to up NR_PROCS in ../h/const.h
( I made mine 32 instead of 16 and don't run out anymore.)

Chose any number you want for NR_SHARE -- the structure consumes
16 bytes per item.  Too few and programs won't be shared as efficiently
and too many and you waste 16 byte chunks of kernel memory.

The total effect to system memory seems to be about 800 more bytes.
Pretty cheap.

I've made NO attempts to make this work with the ATARI_ST ifdefs.

Thanks to Dave Regan for acting as guinea pig for these changes.

Gary Oliver
go@jacobs.cs.orst.edu

Below is the shar file for the five files needing change.  The produced
files are .cdiff and "patch" compatible.  All of these changes are
applied to files in ../mm directory.
---------------as they say: cut here and run through sh ------------
echo x - alloc.cdiff
sed '/^X/s///' > alloc.cdiff << '/'
X*** alloc.c.old	Wed Jan  1 00:35:24 1970
X--- alloc.c	Wed Jan  1 00:34:12 1970
X***************
X*** 13,23 ****
X--- 13,25 ----
X   *   free_mem:	release a previously allocated chunk of memory
X   *   mem_init:	initialize the tables when MM start up
X   *   max_hole:	returns the largest hole currently available
X+  *   free_user_mem: added 26-Apr-89 G. Oliver -- frees mem for entire task
X   */
X  
X  #include "../h/const.h"
X  #include "../h/type.h"
X  #include "const.h"
X+ #include "mproc.h"
X  
X  #define NR_HOLES         128	/* max # entries in hole table */
X  #define NIL_HOLE (struct hole *) 0
X***************
X*** 35,43 ****
X--- 37,67 ----
X  /*===========================================================================*
X   *				alloc_mem				     *
X   *===========================================================================*/
X+ #ifdef	STICKY_SHARE
X  PUBLIC phys_clicks alloc_mem(clicks)
X  phys_clicks clicks;		/* amount of memory requested */
X  {
X+   phys_clicks	alloc_try();
X+   phys_clicks	memseg;
X+ 
X+   memseg = alloc_try(clicks);
X+   if (memseg == NO_MEM)
X+ 	{
X+ 	sh_purge();	/* Purge empty shared text segments */
X+ 	memseg = alloc_try(clicks);
X+ 	}
X+   return (memseg);
X+   }
X+ #endif	/* STICKY_SHARE */
X+ 
X+ #ifdef	STICKY_SHARE
X+ /* Change name to helper function if SHARED CODE system */
X+ PRIVATE phys_clicks alloc_try(clicks)
X+ #else
X+ PUBLIC phys_clicks alloc_mem(clicks)
X+ #endif	/* STICKY_SHARE */
X+ phys_clicks clicks;		/* amount of memory requested */
X+ {
X  /* Allocate a block of memory from the free list using first fit. The block
X   * consists of a sequence of contiguous bytes, whose length in clicks is
X   * given by 'clicks'.  A pointer to the block is returned.  The block is
X***************
X*** 56,66 ****
X--- 80,95 ----
X  		hp->h_base += clicks;	/* bite a piece off */
X  		hp->h_len -= clicks;	/* ditto */
X  
X+ #ifdef	NOTUSED
X  		/* If hole is only partly used, reduce size and return. */
X  		if (hp->h_len != 0) return(old_base);
X  
X  		/* The entire hole has been used up.  Manipulate free list. */
X  		del_slot(prev_ptr, hp);
X+ #else
X+ 		if (hp->h_len == 0)
X+ 			del_slot(prev_ptr, hp);
X+ #endif	/* NOTUSED */
X  		return(old_base);
X  	}
X  
X***************
X*** 222,224 ****
X--- 251,254 ----
X    hole[0].h_base = 0;
X    hole[0].h_len = clicks;
X  }
X+ 
/
echo x - exec.cdiff
sed '/^X/s///' > exec.cdiff << '/'
X*** exec.c.old	Wed Jan  1 00:34:03 1970
X--- exec.c	Wed Jan  1 00:34:40 1970
X***************
X*** 10,15 ****
X--- 10,20 ----
X   *    - tell kernel about EXEC
X   *
X   *   The only entry point is do_exec.
X+  *
X+  *  25-Apr-89 G. Oliver.  The above is statement is no longer true.
X+  *	The sh_purge() routine (purge shared structures of unused
X+  *	entries) is now callable by memory allocation routines when
X+  *	STICKY_SHARE is defined.
X   */
X  
X  #include "../h/const.h"
X***************
X*** 30,36 ****
X  #define TOTB               6	/* location of total size in header */
X  #define SYMB               7	/* location of symbol size in header */
X  
X! #ifdef ATARI_ST
X  PUBLIC long lseek();
X  #endif
X  
X--- 35,41 ----
X  #define TOTB               6	/* location of total size in header */
X  #define SYMB               7	/* location of symbol size in header */
X  
X! #if	defined(ATARI_ST) || NR_SHARE != 0
X  PUBLIC long lseek();
X  #endif
X  
X***************
X*** 57,62 ****
X--- 62,71 ----
X    long sym_bytes;
X    vir_clicks sc;
X    struct stat s_buf;
X+ #if	NR_SHARE != 0
X+   struct shared_info *sh_ptr;
X+   struct shared_info *shared_text();
X+ #endif	/* NR_SHARE */
X  
X    /* Do some validity checks. */
X    rmp = mp;
X***************
X*** 92,99 ****
X    }
X  
X    /* Allocate new memory and release old memory.  Fix map and tell kernel. */
X!   r = new_mem(text_bytes, data_bytes, bss_bytes, stk_bytes, tot_bytes,
X! 							u.zb, ZEROBUF_SIZE);
X    if (r != OK) {
X  	close(fd);		/* insufficient core or program too big */
X  	return(r);
X--- 101,118 ----
X    }
X  
X    /* Allocate new memory and release old memory.  Fix map and tell kernel. */
X! #if	NR_SHARE != 0
X!   if (ft == SEPARATE)
X! 	sh_ptr = shared_text(&s_buf);
X!   else
X! 	sh_ptr = 0;	/* Not shared at all */
X! 
X!   r = new_mem(sh_ptr, text_bytes, data_bytes, bss_bytes, stk_bytes,
X! 					tot_bytes, u.zb, ZEROBUF_SIZE);
X! #else
X!   r = new_mem(text_bytes, data_bytes, bss_bytes, stk_bytes, tot_bytes,
X! 							u.zb, ZEROBUF_SIZE);
X! #endif	/* NR_SHARE */
X    if (r != OK) {
X  	close(fd);		/* insufficient core or program too big */
X  	return(r);
X***************
X*** 108,115 ****
X    r = mem_copy(MM_PROC_NR, D, (long) src, who, D, (long) vsp, (long) stk_bytes);
X    if (r != OK) panic("do_exec stack copy err", NO_NUM);
X  
X!   /* Read in text and data segments. */
X!   load_seg(fd, T, text_bytes);
X    load_seg(fd, D, data_bytes);
X  #ifdef ATARI_ST
X    if (lseek(fd, sym_bytes, 1) < 0)
X--- 127,142 ----
X    r = mem_copy(MM_PROC_NR, D, (long) src, who, D, (long) vsp, (long) stk_bytes);
X    if (r != OK) panic("do_exec stack copy err", NO_NUM);
X  
X! #if	NR_SHARE != 0
X!   /* Read in text and data segments. */
X!   if (sh_ptr)
X! 	lseek(fd, (long) text_bytes, 1);	/* Skip text on file */
X!   else
X! 	load_seg(fd, T, text_bytes);
X! #else
X!   load_seg(fd, T, text_bytes);
X! #endif	/* NR_SHARE */
X! 
X    load_seg(fd, D, data_bytes);
X  #ifdef ATARI_ST
X    if (lseek(fd, sym_bytes, 1) < 0)
X***************
X*** 117,123 ****
X    if (relocate(fd, mbuf) < 0)
X  	;	/* error */
X  #endif
X!   close(fd);			/* don't need exec file any more */
X  
X    /* Take care of setuid/setgid bits. */
X    if (s_buf.st_mode & I_SET_UID_BIT) {
X--- 144,163 ----
X    if (relocate(fd, mbuf) < 0)
X  	;	/* error */
X  #endif
X! 
X!   close(fd);
X! 
X! #if	NR_SHARE != 0
X!   /* See if creating a separate I/D program for first time and create
X!    * a shared segment descriptor if so.
X!    */
X! 
X!   if (!sh_ptr && (ft == SEPARATE))
X! 	{
X! 	/* Create a shared-text structure to remember this one */
X! 	make_shared(rmp, &s_buf);
X! 	}
X! #endif	/* NR_SHARE */
X  
X    /* Take care of setuid/setgid bits. */
X    if (s_buf.st_mode & I_SET_UID_BIT) {
X***************
X*** 228,234 ****
X  /*===========================================================================*
X   *				new_mem					     *
X   *===========================================================================*/
X! PRIVATE int new_mem(text_bytes, data_bytes, bss_bytes,stk_bytes,tot_bytes,bf,zs)
X  vir_bytes text_bytes;		/* text segment size in bytes */
X  vir_bytes data_bytes;		/* size of initialized data in bytes */
X  vir_bytes bss_bytes;		/* size of bss in bytes */
X--- 268,279 ----
X  /*===========================================================================*
X   *				new_mem					     *
X   *===========================================================================*/
X! #if	NR_SHARE != 0
X! PRIVATE int new_mem(sh_ptr, text_bytes, data_bytes, bss_bytes,stk_bytes,tot_bytes,bf,zs)
X! struct shared_info *sh_ptr;
X! #else
X! PRIVATE int new_mem(text_bytes, data_bytes, bss_bytes,stk_bytes,tot_bytes,bf,zs)
X! #endif	/* NR_SHARE */
X  vir_bytes text_bytes;		/* text segment size in bytes */
X  vir_bytes data_bytes;		/* size of initialized data in bytes */
X  vir_bytes bss_bytes;		/* size of bss in bytes */
X***************
X*** 260,265 ****
X--- 305,315 ----
X     * boundary.  The data and bss parts are run together with no space.
X     */
X  
X+ #if	NR_SHARE != 0
X+   if (sh_ptr)
X+ 	text_bytes = 0;	/* No text if shared segment */
X+ #endif	/* NR_SHARE */
X+ 
X    text_clicks = (text_bytes + CLICK_SIZE - 1) >> CLICK_SHIFT;
X    data_clicks = (data_bytes + bss_bytes + CLICK_SIZE - 1) >> CLICK_SHIFT;
X    stack_clicks = (stk_bytes + CLICK_SIZE - 1) >> CLICK_SHIFT;
X***************
X*** 276,285 ****
X--- 326,341 ----
X    /* There is enough memory for the new core image.  Release the old one. */
X    rmp = mp;
X  #ifndef ATARI_ST
X+ #if	NR_SHARE != 0
X+   if (sh_ptr)
X+ 	++(sh_ptr->sh_users);	/* Count another so it isn't freed under us */
X+   free_user_mem(rmp);
X+ #else
X    old_clicks = (phys_clicks) rmp->mp_seg[S].mem_len;
X    old_clicks += (rmp->mp_seg[S].mem_vir - rmp->mp_seg[D].mem_vir);
X    if (rmp->mp_flags & SEPARATE) old_clicks += rmp->mp_seg[T].mem_len;
X    free_mem(rmp->mp_seg[T].mem_phys, old_clicks);	/* free the memory */
X+ #endif /* NR_SHARE */
X  #endif
X  
X    /* We have now passed the point of no return.  The old core image has been
X***************
X*** 287,294 ****
X     */
X    new_base = alloc_mem(text_clicks + tot_clicks);	/* new core image */
X    if (new_base == NO_MEM) panic("MM hole list is inconsistent", NO_NUM);
X!   rmp->mp_seg[T].mem_len = text_clicks;
X!   rmp->mp_seg[T].mem_phys = new_base;
X    rmp->mp_seg[D].mem_len = data_clicks;
X    rmp->mp_seg[D].mem_phys = new_base + text_clicks;
X    rmp->mp_seg[S].mem_len = stack_clicks;
X--- 343,365 ----
X     */
X    new_base = alloc_mem(text_clicks + tot_clicks);	/* new core image */
X    if (new_base == NO_MEM) panic("MM hole list is inconsistent", NO_NUM);
X! #if	NR_SHARE != 0
X!   if (sh_ptr)
X! 	{
X! 	/* Set tasks map to shared segment */
X! 	rmp->mp_seg[T].mem_phys = sh_ptr->sh_seg.mem_phys;
X! 	rmp->mp_seg[T].mem_len = sh_ptr->sh_seg.mem_len;
X! 	rmp->mp_shared = sh_ptr;	/* Shared text ptr */
X! 	}
X!   else
X! 	{
X! 	rmp->mp_seg[T].mem_len = text_clicks;
X! 	rmp->mp_seg[T].mem_phys = new_base;
X! 	}
X! #else
X!   rmp->mp_seg[T].mem_len = text_clicks;
X!   rmp->mp_seg[T].mem_phys = new_base;
X! #endif	/* NR_SHARE */
X    rmp->mp_seg[D].mem_len = data_clicks;
X    rmp->mp_seg[D].mem_phys = new_base + text_clicks;
X    rmp->mp_seg[S].mem_len = stack_clicks;
X***************
X*** 313,320 ****
X    for (rzp = &bf[0]; rzp < &bf[zs]; rzp++) *rzp = 0;	/* clear buffer */
X    bytes = (phys_bytes) (data_clicks + gap_clicks + stack_clicks) << CLICK_SHIFT;
X    vzb = (vir_bytes) bf;
X!   base = (long) rmp->mp_seg[T].mem_phys + rmp->mp_seg[T].mem_len;
X!   base = base << CLICK_SHIFT;
X    bss_offset = (data_bytes >> CLICK_SHIFT) << CLICK_SHIFT;
X    base += bss_offset;
X    bytes -= bss_offset;
X--- 384,395 ----
X    for (rzp = &bf[0]; rzp < &bf[zs]; rzp++) *rzp = 0;	/* clear buffer */
X    bytes = (phys_bytes) (data_clicks + gap_clicks + stack_clicks) << CLICK_SHIFT;
X    vzb = (vir_bytes) bf;
X! #if	NR_SHARE != 0
X!   base = (long) rmp->mp_seg[D].mem_phys << CLICK_SHIFT;
X! #else
X!   base = (long) rmp->mp_seg[T].mem_phys + rmp->mp_seg[T].mem_len;
X!   base = base << CLICK_SHIFT;
X! #endif	/* NR_SHARE */
X    bss_offset = (data_bytes >> CLICK_SHIFT) << CLICK_SHIFT;
X    base += bss_offset;
X    bytes -= bss_offset;
X***************
X*** 467,469 ****
X--- 542,763 ----
X    return(0);	/* ok */
X  }
X  #endif
X+ 
X+ #if	NR_SHARE != 0
X+ /*
X+  *	Shared text segment stuff is here.  If NR_SHARE is non-zero, this
X+  *	code is included.  Further, the def STICKY_SHARE should be defined
X+  *	if you wish to implement the "sticky" shared text feature.  With
X+  *	the sticky text, the text segment is not deallocated after the
X+  *	last user finishes, but rather the freeing is delayed until
X+  *	the memory allocator (alloc_mem) needs memory and no free chunks
X+  *	of appropriate size are available.  The effect of enabling
X+  *	STICKY_SHARE is a performance increase when repeatedly calling
X+  *	separate I&D programs at the expense of increased memory
X+  *	fragmentation.
X+  */
X+ 
X+ /*
X+  *	shared_text()
X+  *
X+  *	Lookup the dev/inode/time info in the shared text table
X+  *	and return its TRUE if found, otherwise return FALSE.
X+  *	If found, plug the user proc structure with the details.
X+  */
X+ struct shared_info		*
X+ shared_text(st_buf)
X+   register struct stat		*st_buf;
X+     {
X+     register struct shared_info	*sh_ptr;
X+ 
X+     for (sh_ptr = sh_text; sh_ptr < &sh_text[NR_SHARE]; ++sh_ptr)
X+ 	{
X+ #ifdef	STICKY_SHARE
X+ 	if ((sh_ptr->sh_seg.mem_len != 0) &&
X+ 	    (sh_ptr->sh_dev == st_buf->st_dev) &&
X+ 	    (sh_ptr->sh_ino == st_buf->st_ino) &&
X+ 	    (sh_ptr->sh_mtime == st_buf->st_mtime))
X+ #else
X+ 	if ((sh_ptr->sh_users != 0) &&
X+ 	    (sh_ptr->sh_dev == st_buf->st_dev) &&
X+ 	    (sh_ptr->sh_ino == st_buf->st_ino) &&
X+ 	    (sh_ptr->sh_mtime == st_buf->st_mtime))
X+ #endif	/* STICKY_SHARE */
X+ 	    {
X+ 	    return (sh_ptr);
X+ 	    }
X+ 	}
X+ 
X+     return (0);		/* Didn't find it */
X+     }
X+ 
X+ 
X+ /*
X+  *	make_shared()
X+  *
X+  *	Create a shared_text structure for the current users text info.
X+  */
X+ make_shared(rmp, st_buf)
X+   struct mproc			*rmp;
X+   struct stat			*st_buf;
X+     {
X+ #ifdef	STICKY_SHARE
X+     register struct shared_info	*sh_free;
X+ #endif	/* STICKY_SHARE */
X+     register struct shared_info	*sh_ptr;
X+ 
X+     /* Look for an unused entry.  If no unused entries (sh_seg.mem_len
X+      * equal to 0, then take the first entry not currently used for a
X+      * running program and free its memory.  Note that this approximates
X+      * the Un*x "sticky" bit procedure -- shared text segments will lie
X+      * around for awhile after no running program uses them until such
X+      * time as a new memory is needed.  Note that alloc_mem
X+      * needs to free these things too, if non-shared text programs are
X+      * run when no memory is available.
X+      */
X+ #ifdef	STICKY_SHARE
X+     for (sh_free = &sh_text[NR_SHARE], sh_ptr = sh_text;
X+ 	 sh_ptr < &sh_text[NR_SHARE];
X+ 	 ++sh_ptr)
X+ 	{
X+ 	if ((sh_ptr->sh_users == 0) && (sh_free == NULL))
X+ 	    sh_free = sh_ptr;	/* Remember first available */
X+ 
X+ 	if (sh_ptr->sh_seg.mem_len == 0)
X+ 	    break;	/* Found an empty -- reuse it */
X+ 	}
X+ 
X+     if (sh_ptr >= sh_text)
X+ 	sh_ptr = sh_free;	/* Use free entry */
X+ #else
X+     for (sh_ptr = sh_text; sh_ptr < &sh_text[NR_SHARE]; ++sh_ptr)
X+ 	{
X+ 	if (sh_ptr->sh_users == 0)
X+ 	    break;	/* Found an unused item */
X+ 	}
X+ #endif	/* STICKY_SHARE */
X+ 
X+     if (sh_ptr < &sh_text[NR_SHARE])
X+ 	{
X+ #ifdef	STICKY_SHARE
X+ 	if (sh_ptr->sh_seg.mem_len !=0)
X+ 	    {
X+ 	    /* First free the memory used on this segment */
X+ 	    free_mem(sh_ptr->sh_seg.mem_phys, sh_ptr->sh_seg.mem_len);
X+ 	    }
X+ #endif	/* STICKY_SHARE */
X+ 
X+ 	/* Free entry -- use this one */
X+ 	sh_ptr->sh_dev = st_buf->st_dev;
X+ 	sh_ptr->sh_ino = st_buf->st_ino;
X+ 	sh_ptr->sh_mtime = st_buf->st_mtime;
X+ 	sh_ptr->sh_seg.mem_phys = rmp->mp_seg[T].mem_phys;
X+ 	sh_ptr->sh_seg.mem_len = rmp->mp_seg[T].mem_len;
X+ 	sh_ptr->sh_users = 1;	/* One user thus far */
X+ 	rmp->mp_shared = sh_ptr;	/* Shared text ptr */
X+ 	}
X+     }
X+ 
X+ 
X+ #ifdef	STICKY_SHARE
X+ /*
X+  *	Purge shared text table of all text segments not currently
X+  *	allocated to any running programs.
X+  */
X+ sh_purge()
X+     {
X+     register struct shared_info	*sh_ptr;
X+ 
X+     for (sh_ptr = sh_text; sh_ptr < &sh_text[NR_SHARE]; ++sh_ptr)
X+ 	{
X+ 	if ((sh_ptr->sh_users == 0) && (sh_ptr->sh_seg.mem_len != 0))
X+ 	    {
X+ 	    /* Free this entry */
X+ 	    free_mem(sh_ptr->sh_seg.mem_phys, sh_ptr->sh_seg.mem_len);
X+ 
X+ 	    sh_ptr->sh_seg.mem_len = 0;	/* None alloc'd */
X+ 	    }
X+ 	}
X+     }
X+ #endif	/* STICKY_SHARE */
X+ 
X+ sh_init()
X+     {
X+     register struct shared_info	*sh_ptr;
X+ 
X+     for (sh_ptr = sh_text; sh_ptr < &sh_text[NR_SHARE]; ++sh_ptr)
X+ #ifdef	STICKY_SHARE
X+ 	sh_ptr->sh_seg.mem_len = sh_ptr->sh_users = 0;
X+ #else
X+ 	sh_ptr->sh_users = 0;
X+ #endif	/* STICKY_SHARE */
X+     }
X+ 
X+ free_user_mem(rmp)
X+   register struct mproc *rmp;
X+     {
X+     phys_clicks		s;
X+ 
X+     s = (phys_clicks) rmp->mp_seg[S].mem_len +
X+                       (rmp->mp_seg[S].mem_vir - rmp->mp_seg[D].mem_vir);
X+ 
X+     if (rmp->mp_shared)
X+ 	{
X+ 	/* Shared code -- free data and text separately */
X+ 	free_mem(rmp->mp_seg[D].mem_phys, s);
X+ #ifdef	STICKY_SHARE
X+ 	--(rmp->mp_shared->sh_users);	/* Just decrease use count */
X+ #else
X+ 	if (--(rmp->mp_shared->sh_users) == 0)
X+ 	    {
X+ 	    free_mem(rmp->mp_shared->sh_seg.mem_phys,
X+ 		     rmp->mp_shared->sh_seg.mem_len);
X+ 	    }
X+ #endif	/* STICKY_SHARE */
X+ 	rmp->mp_shared = (struct shared_info *) 0; /* No longer accessed */
X+ 	}
X+     else
X+ 	{
X+ 	if (rmp->mp_flags & SEPARATE)
X+ 	    s += rmp->mp_seg[T].mem_len;
X+ 
X+ 	free_mem(rmp->mp_seg[T].mem_phys, s);
X+ 	}
X+     }
X+ 
X+ 
X+ #ifdef	NOTUSED
X+ /*
X+  *	Used by F4 key function processor (future)
X+  */
X+ sh_dump()
X+     {
X+     struct shared_info	*sh_ptr;
X+     int			hdr;
X+ 
X+     for (hdr = TRUE, sh_ptr = sh_text; sh_ptr < &sh_text[NR_SHARE]; ++sh_ptr)
X+ 	{
X+ #ifdef	STICKY_SHARE
X+ 	if (sh_ptr->sh_seg.mem_len != 0)
X+ #else
X+ 	if (sh_ptr->sh_users != 0)
X+ #endif	/* STICKY_SHARE */
X+ 	    {
X+ 	    if (hdr)
X+ 		{
X+ 		printf("Index  Seg  Len  Users\n");
X+ 		hdr = FALSE;
X+ 		}
X+ 
X+ 	    /* Print contents of this segment */
X+ 	    printf("  %3d %4x %4x  %3d\n",
X+ 			sh_ptr - sh_text,
X+ 			sh_ptr->sh_seg.mem_phys,
X+ 			sh_ptr->sh_seg.mem_len,
X+ 			sh_ptr->sh_users);
X+ 	    }
X+ 	}
X+     }
X+ #endif	/* NOTUSED */
X+ #endif	/* NR_SHARE */
X+ 
/
echo x - forkexit.cdiff
sed '/^X/s///' > forkexit.cdiff << '/'
X*** forkexit.c.old	Wed Jan  1 00:35:19 1970
X--- forkexit.c	Wed Jan  1 00:34:44 1970
X***************
X*** 59,65 ****
X    prog_clicks = (phys_clicks) rmp->mp_seg[S].mem_len;
X    prog_clicks += (rmp->mp_seg[S].mem_vir - rmp->mp_seg[D].mem_vir);
X  #ifndef ATARI_ST
X!   if (rmp->mp_flags & SEPARATE) prog_clicks += rmp->mp_seg[T].mem_len;
X    prog_bytes = (long) prog_clicks << CLICK_SHIFT;
X  #endif
X    if ( (child_base = alloc_mem(prog_clicks)) == NO_MEM) return(EAGAIN);
X--- 59,71 ----
X    prog_clicks = (phys_clicks) rmp->mp_seg[S].mem_len;
X    prog_clicks += (rmp->mp_seg[S].mem_vir - rmp->mp_seg[D].mem_vir);
X  #ifndef ATARI_ST
X! #if	NR_SHARE != 0
X!   /* If separate I&D and not shared, include the size of the Text region */
X!   if ((rmp->mp_flags & SEPARATE) && !rmp->mp_shared)
X! 	prog_clicks += rmp->mp_seg[T].mem_len;
X! #else
X!   if (rmp->mp_flags & SEPARATE) prog_clicks += rmp->mp_seg[T].mem_len;
X! #endif	/* NR_SHARE */
X    prog_bytes = (long) prog_clicks << CLICK_SHIFT;
X  #endif
X    if ( (child_base = alloc_mem(prog_clicks)) == NO_MEM) return(EAGAIN);
X***************
X*** 67,73 ****
X  #ifndef ATARI_ST
X    /* Create a copy of the parent's core image for the child. */
X    child_abs = (long) child_base << CLICK_SHIFT;
X!   parent_abs = (long) rmp->mp_seg[T].mem_phys << CLICK_SHIFT;
X    i = mem_copy(ABS, 0, parent_abs, ABS, 0, child_abs, prog_bytes);
X    if ( i < 0) panic("do_fork can't copy", i);
X  #endif
X--- 73,93 ----
X  #ifndef ATARI_ST
X    /* Create a copy of the parent's core image for the child. */
X    child_abs = (long) child_base << CLICK_SHIFT;
X! #if	NR_SHARE != 0
X!   if (rmp->mp_shared)
X! 	{
X! 	/* Shared -- copy only the data region */
X! 	parent_abs = (long) rmp->mp_seg[D].mem_phys << CLICK_SHIFT;
X! 	}
X!   else
X! 	{
X! 	/* If not shared, copy entire text + data region */
X! 	parent_abs = (long) rmp->mp_seg[T].mem_phys << CLICK_SHIFT;
X! 	}
X! #else
X!   parent_abs = (long) rmp->mp_seg[T].mem_phys << CLICK_SHIFT;
X! #endif	/* NR_SHARE */
X! 
X    i = mem_copy(ABS, 0, parent_abs, ABS, 0, child_abs, prog_bytes);
X    if ( i < 0) panic("do_fork can't copy", i);
X  #endif
X***************
X*** 86,93 ****
X  
X    rmc->mp_parent = who;		/* record child's parent */
X  #ifndef ATARI_ST
X!   rmc->mp_seg[T].mem_phys = child_base;
X!   rmc->mp_seg[D].mem_phys = child_base + rmc->mp_seg[T].mem_len;
X    rmc->mp_seg[S].mem_phys = rmc->mp_seg[D].mem_phys + 
X  			(rmp->mp_seg[S].mem_phys - rmp->mp_seg[D].mem_phys);
X  #endif
X--- 106,127 ----
X  
X    rmc->mp_parent = who;		/* record child's parent */
X  #ifndef ATARI_ST
X! #if	NR_SHARE != 0
X!   if (rmc->mp_shared)
X! 	{
X! 	/* A shared text segment -- Text is ok -- fix D and S */
X! 	rmc->mp_seg[D].mem_phys = child_base; /* Only D & S copied */
X! 	rmc->mp_shared->sh_users++;	/* Add to counter */
X! 	}
X!   else
X! 	{
X! 	rmc->mp_seg[T].mem_phys = child_base;
X! 	rmc->mp_seg[D].mem_phys = child_base + rmc->mp_seg[T].mem_len;
X! 	}
X! #else
X!   rmc->mp_seg[T].mem_phys = child_base;
X!   rmc->mp_seg[D].mem_phys = child_base + rmc->mp_seg[T].mem_len;
X! #endif	/* NR_SHARE */
X    rmc->mp_seg[S].mem_phys = rmc->mp_seg[D].mem_phys + 
X  			(rmp->mp_seg[S].mem_phys - rmp->mp_seg[D].mem_phys);
X  #endif
X***************
X*** 186,196 ****
X--- 220,234 ----
X    tell_fs(EXIT, proc_nr, 0, 0);  /* file system can free the proc slot */
X  
X  #ifndef ATARI_ST
X+ #if	NR_SHARE != 0
X+   free_user_mem(rmp);
X+ #else
X    /* Release the memory occupied by the child. */
X    s = (phys_clicks) rmp->mp_seg[S].mem_len;
X    s += (rmp->mp_seg[S].mem_vir - rmp->mp_seg[D].mem_vir);
X    if (rmp->mp_flags & SEPARATE) s += rmp->mp_seg[T].mem_len;
X    free_mem(rmp->mp_seg[T].mem_phys, s);	/* free the memory */
X+ #endif	/* NR_SHARE */
X  #endif
X  
X  }
/
echo x - main.cdiff
sed '/^X/s///' > main.cdiff << '/'
X*** main.c.old	Wed Jan  1 00:35:40 1970
X--- main.c	Wed Jan  1 00:34:47 1970
X***************
X*** 122,127 ****
X--- 122,131 ----
X    mproc[INIT_PROC_NR].mp_flags |= IN_USE;
X    procs_in_use = 3;
X  
X+ #if	NR_SHARE != 0
X+   sh_init();		/* Initialize shared code tables in exec.c */
X+ #endif	/* NR_SHARE */
X+ 
X    /* Set stack limit, which is checked on every procedure call. */
X  }
X  
X***************
X*** 188,193 ****
X--- 192,200 ----
X    rmp->mp_seg[S].mem_vir  = init_clicks;
X  #endif
X    if (init_text_clicks != 0) rmp->mp_flags |= SEPARATE;
X+ #if	NR_SHARE != 0
X+   rmp->mp_shared = 0;	/* Not a shared program */
X+ #endif /* NR_SHARE */
X  
X    return(OK);
X  }
/
echo x - mproc.cdiff
sed '/^X/s///' > mproc.cdiff << '/'
X*** mproc.h.old	Wed Jan  1 00:35:21 1970
X--- mproc.h	Wed Jan  1 00:34:08 1970
X***************
X*** 7,12 ****
X--- 7,15 ----
X  
X  EXTERN struct mproc {
X    struct mem_map mp_seg[NR_SEGS];	/* points to text, data, stack */
X+ #if	NR_SHARE != 0
X+   struct shared_info *mp_shared;	/* != NULL if shared code segment running */
X+ #endif	/* NR_SHARE */
X    char mp_exitstatus;		/* storage for status when process exits */
X    char mp_sigstatus;		/* storage for signal # for killed processes */
X    int mp_pid;			/* process id */
X***************
X*** 27,36 ****
X    unsigned mp_flags;		/* flag bits */
X  } mproc[NR_PROCS];
X  
X! /* Flag values */
X! #define IN_USE           001	/* set when 'mproc' slot in use */
X! #define WAITING          002	/* set by WAIT system call */
X! #define HANGING          004	/* set by EXIT system call */
X! #define PAUSED           010	/* set by PAUSE system call */
X! #define ALARM_ON         020	/* set when SIGALRM timer started */
X! #define SEPARATE	 040	/* set if file is separate I & D space */
X--- 30,49 ----
X    unsigned mp_flags;		/* flag bits */
X  } mproc[NR_PROCS];
X  
X! #if	NR_SHARE != 0
X! EXTERN struct shared_info {
X!   unsigned int		sh_dev;
X!   unsigned int		sh_ino;
X!   long			sh_mtime;
X!   struct mem_map	sh_seg;
X!   int			sh_users;
X!   } sh_text[NR_SHARE];
X! #endif	/* NR_SHARE */
X! 
X! /* Flag values */
X! #define IN_USE           001	/* set when 'mproc' slot in use */
X! #define WAITING          002	/* set by WAIT system call */
X! #define HANGING          004	/* set by EXIT system call */
X! #define PAUSED           010	/* set by PAUSE system call */
X! #define ALARM_ON         020	/* set when SIGALRM timer started */
X! #define SEPARATE	 040	/* set if file is separate I & D space */
/