[comp.os.minix] Why the 8086 architecture is wonderful :-)

preston@felix.UUCP (Preston Bannister) (02/22/89)

Before we get carried away trashing the Intel 8086 architecture lets not
forget it's advantages...

1.  If you can restrict your program to 16-bit pointers (i.e. the "small"(?)
memory model), as does Minix, then:

- Your code will tend to be both smaller and faster.  (The code for "small"
8086 programs tend to be smaller than "small" 68000 programs).

- You can treat each segment as a seperate address space.  Segments can be
moved around in memory as needed.  This is the rather nifty trick Minix uses
to efficiently implement fork() without a real MMU.  Since the 68000 has
a flat address space, the following program under ST Minix is incredibly
inefficient:

	main ()
	{
	  if (fork())
	    for (;;) printf("Heave");
	  else
	    for (;;) printf("Ho");
	}

On each process switch ST Minix _must_ swap the parent and child data so
that the running process is in the same place in the address space.  In PC
Minix the process switch only has to change a couple of segment registers.
(This _not_ a criticism of ST Minix, as this approach is a necessary
function of the 68000 architecture).

Guess which one is faster :-)


2.  If you want a real virtual memory system, using the Intel 286 (i.e. an
AT-clone) in "protected" mode is the least expensive solution.  The 286
effectively has an MMU on chip.  True, it is segment-based (no paging), but
you can't beat the price.  The MMU on-chip also means fewer delays between
the CPU and memory, i.e. for a given clock rate you can use slower (less
expensive) RAM.


All this is not to say the I think the Intel 8086 architecture is truly
wonderful, just that it does have _some_ advantages.

When I read about the 8086 architecture I was working on a UCSD Pascal
system.  If you are used to thinking in terms of shared libraries, a 64kb
limit on the size of one code segment is not bad.  In fact good programming
practice would tend to break up such large modules.  Given a good set of
shared libraries, large monolithic programs are much less likely.

None of which is much help when trying to port GNU Emacs...
--
Preston L. Bannister
USENET	   :	hplabs!felix!preston
BIX	   :	plb
CompuServe :	71350,3505
GEnie      :	p.bannister

allbery@ncoast.ORG (Brandon S. Allbery) (03/01/89)

As quoted from <84154@felix.UUCP> by preston@felix.UUCP (Preston Bannister):
+---------------
| Before we get carried away trashing the Intel 8086 architecture lets not
| forget it's advantages...
| 
| 1.  If you can restrict your program to 16-bit pointers (i.e. the "small"(?)
| memory model), as does Minix, then:
| 
| - Your code will tend to be both smaller and faster.  (The code for "small"
| 8086 programs tend to be smaller than "small" 68000 programs).
| 
| - You can treat each segment as a seperate address space.  Segments can be
| moved around in memory as needed.  This is the rather nifty trick Minix uses
| to efficiently implement fork() without a real MMU.  Since the 68000 has
| a flat address space, the following program under ST Minix is incredibly
| inefficient:
> (...deleted...)
| On each process switch ST Minix _must_ swap the parent and child data so
| that the running process is in the same place in the address space.  In PC
| Minix the process switch only has to change a couple of segment registers.
| (This _not_ a criticism of ST Minix, as this approach is a necessary
| function of the 68000 architecture).
+---------------

It is NOT necessary.  For example, the Macintosh uses relative addressing
almost exclusively.  This results in 32K relocatable segments, as the 68000
uses signed 16-bit displacements.  ST Minix could use the same trick (and
with some hacking, it could use 64K segments if it wanted to deal with 32K
of "negative" addresses), in which case your example reduces to exchanging
the chosen base register, which is done when the process's quantum starts
anyway.

The main reason that this is not normally done is that it introduces the
joys [ ;-) ] of large-model programming to the 68000.  An ST-Minix program
can be much larger than a PC-Minix program (witness gcc), because accessing
an address outside the current segment is possible without munging the
segment registers.  (Note that this can still be done on the 68000, even if
you use relative addressing:  it's not a processor mode, it's the choice of
instruction.)  The Mac uses it so it can rearrange memory as necessary, as
noted above for the "fork" trick -- otherwise, certain combinations of
actions could generate lots of holes in memory (open DA, open application,
close DA -- suddenly there's a sizeable chunk of "dead space" between the
system heap and the application, where the DA used to be).

An-relative addressing also makes for smaller and faster code, since only
two bytes need be read from memory to fetch an address, vs. 4 for absolute
addressing.  (Counterpoint:  protected-mode 386 programs can also use 4-byte
addresses, so they are also potentially slower.  I don't know whether any
existing 386 programs actually use 4-byte addressing, though; with the 386es
at Telotech came sdb, so I didn't have to learn the processor the way I had
to with the adb-only 68000 in ncoast.)

++Brandon
-- 
Brandon S. Allbery, moderator of comp.sources.misc	     allbery@ncoast.org
uunet!hal.cwru.edu!ncoast!allbery		    ncoast!allbery@hal.cwru.edu
      Send comp.sources.misc submissions to comp-sources-misc@<backbone>
NCoast Public Access UN*X - (216) 781-6201, 300/1200/2400 baud, login: makeuser

preston@felix.UUCP (Preston Bannister) (03/03/89)

From article <13428@ncoast.ORG>, by allbery@ncoast.ORG (Brandon S. Allbery):
> As quoted from <84154@felix.UUCP> by preston@felix.UUCP (Preston Bannister):
> +---------------
> | - You can treat each segment as a seperate address space.  Segments can be
> | moved around in memory as needed.  This is the rather nifty trick Minix uses
> | to efficiently implement fork() without a real MMU.  Since the 68000 has
> | a flat address space, the following program under ST Minix is incredibly
> | inefficient:
>> (...deleted...)
> | On each process switch ST Minix _must_ swap the parent and child data so
> | that the running process is in the same place in the address space.  In PC
> | Minix the process switch only has to change a couple of segment registers.
> | (This _not_ a criticism of ST Minix, as this approach is a necessary
> | function of the 68000 architecture).
> +---------------
> 
> It is NOT necessary.  For example, the Macintosh uses relative addressing
> almost exclusively.  This results in 32K relocatable segments, as the 68000
> uses signed 16-bit displacements.  ST Minix could use the same trick (and
> with some hacking, it could use 64K segments if it wanted to deal with 32K
> of "negative" addresses), in which case your example reduces to exchanging
> the chosen base register, which is done when the process's quantum starts
> anyway.

Almost, but not quite.  The problem is with pointers and the stack
on the 68000.  There are absolute memory addresses embedded in the
stack.  Absolute pointers to within the stack (frame pointers at
least) make the stack not movable.  Return addresses (and function
pointers) in the stack make the pointed to code not movable.

On the other hand, the frame pointers on the 8086 are all offsets
within the stack segment.  If you use only the 8086's "short" calls
then the return addresses are all offsets within the same code
segment.  

You _do_ bring up a good point, as the 68000's relative addressing
mode could be very useful in implementing shared libraries, as done
(surprise, surprise) on the Macintosh.  (Anyone listening?? :-)

(deleted...)
> The Mac uses it so it can rearrange memory as necessary, as
> noted above for the "fork" trick -- otherwise, certain combinations of
> actions could generate lots of holes in memory (open DA, open application,
> close DA -- suddenly there's a sizeable chunk of "dead space" between the
> system heap and the application, where the DA used to be).

Er, I should point out that the Macintosh does NOT have a fork()...

I've never been particularly fond of fork() as a primitive operation,
because it assumes more than is necessary about the architecture of
the machine.  To do fork() efficently, you pretty much have to have
a virtual memory machine.  In the "real" world, most fork() calls
are immediately followed with an exec() call.  The copy of the
process that fork() creates, exec() discards, so why bother making
the copy...
--
Preston L. Bannister
USENET	   :	hplabs!felix!preston
BIX	   :	plb
CompuServe :	71350,3505
GEnie      :	p.bannister

ugkamins@sunybcs.uucp (John Kaminski) (03/08/89)

>I've never been particularly fond of fork() as a primitive operation,
>because it assumes more than is necessary about the architecture of
>the machine.  To do fork() efficently, you pretty much have to have
>a virtual memory machine.  In the "real" world, most fork() calls
>are immediately followed with an exec() call.  The copy of the
>process that fork() creates, exec() discards, so why bother making
>the copy...
>--
>Preston L. Bannister
>USENET	   :	hplabs!felix!preston
>BIX	   :	plb
>CompuServe :	71350,3505
>GEnie      :	p.bannister

It is my opinion that fork() assumes nothing about anything.  It is merely a
standard system call that has standard semantics -- i.e., you will get another
independently running process with the same program and the same variables, the
same state of just about everything...and the parent gets a pid (the returned
value).  The manual for the UNIX system in question usually clearly tells you
the semantics (in the case of MINIX, I guess that would be the description
for V7 UNIX).

Also, I agree that most of the time that exec() of some kind is usually per-
formed after most fork()s.  However, it is not always desired.  Take for
example my attempt a a real-time communications program for OS9 (which is also
supposed to mimic UNIX, at least in its system calls).  What I wished to happen
was that the program immediately split into two parts -- one for taking
keyboard input and sending it out, and another to receive characters and
display them.  Without getting OS9-specific by going on calls to see if there
was anything in the buffer (either keyboard or serial line -- and later from
another user logged on, i.e., not a terminal program), it seemed to me that
the only way to prevent the program from blocking on read() calls was to have
two independent processes, each of which could block all it wanted while
waiting for characters, because the other process would still be running or
ready to run.  A fork() as defined under UNIX would have been great, but
os9fork() requires a string argument which is the module name (executable files
are roughly the equivalent of modules) to be "exec()ed" after the new process
is created.  I had given up on the project due to lack of time, but what I was
attempting is something like os9fork(argv[0]) and have the program somehow
determine if it had already been started and if not, signal the started one
that it was the copy....as you can see, it got complicated REAL fast.

In short, if you are given the choice of fork() then exec() or forkexec(),
I'll take the control offered by separate functions any day.

steve@basser.oz (Stephen Russell) (03/14/89)

In article <4566@cs.Buffalo.EDU> ugkamins@sunybcs.UUCP (John Kaminski) writes:

>
>It is my opinion that fork() assumes nothing about anything.

Your own example shows that fork() _DOES_ assume certain properties of the
underlying architecture.

> [Example of problems with an OS9 application due to lack of separate
>  fork()/exec() calls deleted]

Ask yourself why the OS9 designers left out the UNIX-style fork(). The
answer, of course, is that fork() is difficult/expensive to perform without
appropriate MMU hardware. The 6809/68000 machines either lack any MMU, or
suffer from the lack of a standard (at least until the very late introduction
of the 68881 (?) PMMU for the 68020 series).

Why is the MMU important? After a fork() the child's data segment will
obviously occupy different physical memory addresses than the parent's.
However, the program text contains addresses fixed by the linker
for static/extern data, and the data contain the addresses of auto or
malloc'd variables. Without an MMU to translate these addresses to the
child's new data region, all memory refs from the child will still refer
to the parent data. This breaks the UNIX fork() semantics.

Of course, we could postulate restrictions on the semantics of fork()
so that this is not a problem. For example,
	
	- the child cannot access any data that existed before the fork(),
	or if it does, it may incur the wrath of its parent.

	- the child _can_ access data that it allocates itself, using malloc
	or by calling a function, thus growing its stack.

	- the child cannot return from the function that called fork()

	- etc.

These restrictions don't exist in UNIX, so they should not exist in Minix.
Fortunately, the 8x86 series allow separate data segments, thanks to the
DS register. While the architecture may be ugly in other ways, it does
allow efficient fork() implementation.

henry@utzoo.uucp (Henry Spencer) (03/17/89)

In article <1845@basser.oz> steve@basser.oz (Stephen Russell) writes:
>... fork() is difficult/expensive to perform without
>appropriate MMU hardware...

The Atari ST has no MMU, and Minix runs fine on it.  fork() is not as
cheap as it would be with an MMU, but the cost is manageable.
-- 
Welcome to Mars!  Your         |     Henry Spencer at U of Toronto Zoology
passport and visa, comrade?    | uunet!attcan!utzoo!henry henry@zoo.toronto.edu

preston@felix.UUCP (Preston Bannister) (03/21/89)

From article <1989Mar16.184945.23152@utzoo.uucp>, by henry@utzoo.uucp (Henry Spencer):
> In article <1845@basser.oz> steve@basser.oz (Stephen Russell) writes:
>>... fork() is difficult/expensive to perform without
>>appropriate MMU hardware...
> 
> The Atari ST has no MMU, and Minix runs fine on it.  fork() is not as
> cheap as it would be with an MMU, but the cost is manageable.

Er, Henry are you saying that fork() without an MMU is NOT hundreds
or thousands of times as expensive as with an MMU? :-) :-)

"Cheap" is relative.

If you are forking the shell to exec() a program in response to user
typed command making a copy of the shell's address space as part of
the fork() is no big deal.  Forking GNU Emacs (or some other large
program) to exec() _is_ likely to be painful.  

The previous poster's example of communications program that uses
fork() to generate a clone of itself is _enormously_ inefficent if
the child and parent have to be swapped around in memory of each
process switch.

BTW, my objection to fork() is not just esthetic.  In daily
development I use a symbolic debugger.  The process size of the
debugger with all symbols loaded can easily get up to a megabyte.
Since we don't have copy-on-write, running (fork() and exec()) a
program from within the debugger is at best slow.  When memory is
low it can be fatal (we have a version 7 variant, i.e. swapping, no
paging).
--
Preston L. Bannister
USENET	   :	hplabs!felix!preston
BIX	   :	plb
CompuServe :	71350,3505
GEnie      :	p.bannister

henry@utzoo.uucp (Henry Spencer) (03/23/89)

In article <88236@felix.UUCP> preston@felix.UUCP (Preston Bannister) writes:
>Er, Henry are you saying that fork() without an MMU is NOT hundreds
>or thousands of times as expensive as with an MMU? :-) :-)

If the programs are small, yes, that's exactly what I'm saying.  Maybe
two or three times more expensive, but NOT hundreds or thousands.

>If you are forking the shell to exec() a program in response to user
>typed command making a copy of the shell's address space as part of
>the fork() is no big deal.  Forking GNU Emacs (or some other large
>program) to exec() _is_ likely to be painful.  

Well, serves you right for running such elephantine software! :-)
The GNU software is quite explicitly built for tomorrow's machines,
or maybe the next millenium but one's, not today's.

>The previous poster's example of communications program that uses
>fork() to generate a clone of itself is _enormously_ inefficent if
>the child and parent have to be swapped around in memory of each
>process switch.

Yes, that particular case -- where an exec() is not imminent, which
it *is* in almost all forks -- needs special handling on such systems.


Note that I didn't say it was wonderful in general; I said it was not
a big problem under Minix (which tends to avoid elephantine programs).
-- 
Welcome to Mars!  Your         |     Henry Spencer at U of Toronto Zoology
passport and visa, comrade?    | uunet!attcan!utzoo!henry henry@zoo.toronto.edu