[mod.os.os9] OS-9 technical response

kempf@mcrware.UUCP (Kim Kempf, Microware Systems Corporation) (08/20/86)

[This is a response to a question posted in February.  Kim Kempf from
 Microware asked that it be posted for technical understanding.
 By the way, I have copies of the five January to February digests 
 for those interested.  Just send a request to cbosgd!os9-request -JDD]

> Can anyone give me a clear explanation of how to use the chain()
> and os9fork() calls in the microware 'C' Compiler?  One thing 
> I would like to do is simulate the un*x fork() call where a program
> simply clones itself.  As to chain(), how do you know when you get
> ready to chain to another program what all the other program's
> data, parameter, and other needs are?  i.e. how do you know what paramters
> pass to the call? Replies by mail or net.

You forgot to state whether your machine is a Level I or Level II system.
OS-9 Level I operates with no memory management hardware.  The design basis
of OS-9 is to provide a modular UNIX-like environment without the memory and/or
disk requirements of real UNIX.  Unfortunately, simply cloning a process is
not so trivial without memory management or a disk.  OS-9 relies on a memory
module memory management scheme where all code modules are position-independent
and re-entrant.  Since OS-9 does not require a disk to operate, the kernel's
memory and process scheduling routines were designed to operate without one.
The os9fork() (F$Fork) and chain() F$Chain) functions (system calls) create a
new process by the following (simplified) algorithm:

	1. Create a new process descriptor entry.
	2. Link to the desired code module (load in or find in memory)
	3. Assign memory for the process' data area of the required size.
	4. Activate the new process
	5. Forget the parent process if an F$Chain

The os9fork() function is equivalent to the UNIX fork()/exec() rolled into a
single call.  The chain() function is directly equivalent to just the exec()
call.  This is why os9fork() is called os9fork() and not fork(); the expected
semantics are different.

To implement the semantics of the fork() function on an OS-9 Level I system
(Level I is a 6809 with no memory management hardware to re-arrange the logical
address space) requires swapping the forking process' data memory to disk and
executing the forked process from exactly the same data addresses as that of
the parent process.  An implementation such as this would require significant
changes to the OS-9 kernel, not to mention deviating from the fundamental design
of OS-9.

OS-9 Level II requires memory management hardware to 1) allow more than 64k of
physical memory and 2) assign a 64k address map to each process.  Since each
process has its own 64k memory map (addresses 0x0000 to 0xffff), a fork() call
with UNIX semantics could be implemented with minor changes to the kernel.
Programs that depended on this ability could not be moved downward to OS-9
Level I systems without changes, though.

The only difference between os9fork() and chain() is that the control is
returned to the calling process afterwards with the former and not in the
later.  After os9fork(), two processes will exist.  After chain() only the
newly-created process exists.  Both functions use identical arguments, only
the action after the call is different:

	os9fork/chain(modname,paramsize,paramptr,type,lang,datasize)
	char *modname,*paramptr;
	unsigned paramsize,datasize;
	short type,lang;

"modname" is the name of the code module to execute.  The module directory is
searched to locate its address.  If the module is not in the module directory,
a file of that name is located in the execution directory on disk (if any).
"paramsize" and "paramptr" are a pointer to and the size of the data area
passed to the new process.  Type and lang qualify the desired module.
"datasize" is additional memory size to be added to the stack of the new
process.  If zero is given, the default stack size is determined from the
code module value.

	  highest addresses
	|					| 
	|					|
	+-------------------+	<--- sbrk() increases memory here (via F$Mem)
	|  parameter area  	|
	+-------------------+	<--- X, SP (D contains size of parameter area)
	|					|		 (X points to the parameter area, SP starts
	|	   stack		|		  here and moves down.)
	|	   memory       |
	|					|
	+-------------------+
	|		 |			|
	|		 V			|
	|					|
	|	free memory		|
	|					|
	|		 ^			|
	|		 |			|
	+-------------------+	<-- end
	|	uninitialized	|
	|	  data area		|
	|		(bss)		|
	+-------------------+	<-- edata
	|	 initialized	|
	|	  data area		|
	|	    (data)		|
	+-------------------+
	|	 direct page	|
	|	   memory		|
	+-------------------+	<-- Y, DP (DP is set to the high byte of Y)

	   lowest addresses

This memory allocation scheme is used by the C compiler execution environment
for both Level I and Level II systems.  The only difference is on Level I,
the Y register is never zero and on Level II the Y register is always zero.

For Level I, the data memory is assigned from free memory within the 64k
total address space.  The kernel uses the lowest memory addresses so the
memory assigned is usually somewhere around 0x0400 depending of memory usage.
For Level II, each process has its own logical 64k address space.  Data memory
is always mapped into the lowest logical addresses, i.e. 0x0000.

The direct page register DP always reflects the high order byte of the Y
register to allow direct page addressing into the first 256 bytes of the
data area.

The size of the data area and stack is determined from the module header of
the module to be executed.  The parameter area will be copied into the data
area of the new process and its address and size made available via registers
when the process is born.  It is important that no pointers be passed in this
area as the pointers point into the parent process' memory and not the child's
(hidden feature?).  This parameter area is unstructured, that is, OS-9 makes
no assumptions as to the context of this area.  It is up to the parent and
child to determine the structure and contents of this data.  The chain() and
os9fork() and the cstart.r startup routine communicate the argc, argv business
to C programs.

An example of os9fork() usage follows:

	main()
	{
		int pid;
		.
		.
		.
		if ((pid = os9fork("dir","-e\n",3,0,0,0)) != -1) {
			.
			.
			.
			wait(0);
		} else printf("can't fork dir\n");
		.
		.
		.
	}

Note the \n terminating the parameter string.  This is a convention used by
the OS-9/6809 assembly-language utilities.  Always pad the parameter area
with a \n.

Since I do not know exactly what you are trying to do, I will suggest a couple
of hints I use when porting UNIX-style forks to OS-9.  It seems to me that most
forks are used to create a process that does an immediate exec().  In this case,
change the code to do an OS-9 chain() instead of fork()/exec().  If the fork()
is used to create two processes that run concurrently, isolate the code from
each side of the fork into a separate program and use os9fork() to execute the
other half of the program.

I hope this clarifies one of the major operational differences of OS-9 and UNIX.
----------------
Kim Kempf, Microware Systems Corporation

  {{cornell,decvax,ihnp4,sdcsvax,tektronix}!uw-beaver}\
 {allegra,gatech!sb1,hplabs!lbl-csam,decwrl!sun,sunup} >!fluke!mcrware!kim
{ssc-vax,hplsla,wavetek,physio,cae780,tikal,telematic}/