For "home" computer systems such as the Apple II, the "operating
system" wasn't anything like a modern one with processes and device
drivers and so on; by the standards of modern OSes there wasn't really
one at all.
As a warning: all these explanations (long as they are) are for the
most part considerably simplified. This answer is intended to give you
the general sense of how things worked, but you should ask separate,
more specific questions if you need to know the exact details.
The "BASIC" System
The most common "operating system" of late-70s/early-80s, home
computers, especially starting with the "1977 trinity" of the Apple
II, Commodore PET 2001, and TRS-80 Model I, was the BASIC interpreter.
Here you could type in either line numbers followed by BASIC code or
"direct" commands, which along with many regular BASIC commands
(PRINT
, LET
, etc.) would usually include commands such as LOAD
and SAVE
. This was just as single process with full access to and
control over all aspects of the hardware.
Typically the binary code (in ROM or RAM) wasn't entirely monolithic.
Generally it consisted of two parts. The BIOS (Basic Input Output
System) or KERNAL (if you were Commodore and couldn't spell) had
low-level routines that dealt with things such as input/output and
other handling of devices. The BASIC interpreter itself, most often
provided by Microsoft, would handle most higher-level stuff and talk
to the BIOS to interact with the hardware. The line here was pretty
hazy, though, and it was quite normal for BASIC programs to directly
PEEK
and POKE
the hardware or directly call machine-language
routines to do what they wanted to get done.
The BIOS could be as simple as a few routines to check for, read and
write a character from/to an attached terminal or considerably more
sophisticated.
Simple Example: Altair
One of the simplest examples of a BIOS was the 1975 Altair 680 PROM
Monitor. This provided POLCAT
(check for character
available), INCH
(read character) and OUTCH
(print character)
routines used by both itself and BASIC, and a few simple commands for
examining and depositing values to memory, loading memory from a paper
tape, and jumping to a memory location to start a program, all in 256
bytes. This would be used to load Altair BASIC (which
expected POLCAT
, INCH
and OUTCH
to be at specific addresses in
memory) from paper tape and start it, at which point that would be
doing everything you could do. (If it had LOAD and SAVE commands,
those would have been built directly into the BASIC binary. But if you
weren't so lucky to have a cassette tape interface and a BASIC that
knew how to use it, you would "save" your program merely by typing
LIST
, turning on the paper tape punch on your Teletype, and hitting
Return.)
If you didn't have any ROM at all in your system, even the INCH
etc.
routines would have been "baked in" to the BASIC itself, and you'd
toggle in a small routine to load the tape and run it, as demonstrated
in this Altair 8800 video.
Sophisticated Example: Commodore
At the other extreme, Commodore from the start had split their
software into two parts. The "KERNAL", usually in its own 8K
ROM, was a fairly sophisticated I/O core that handled regular keyboard
scanning via interrupts, had a longish list of routines to do I/O, and
even had a concept of device-independent I/O. For example, you could
open/setup devices 1 (cassette), 3 (screen), 4 (printer) and 8
(diskette drive), assigning them file numbers of your choice, and use
the same CHROUT
routine with all of them, passing it the file
number. CHROUT
would, based on the file number, find the correct
device and use the correct "device driver" for each of these.
Commodore's version of Microsoft BASIC, in its own 8K ROM, was then
built on top of this and the BASIC LOAD
OPEN
, PRINT
etc.
commands let you specify the device numbers or logical file numbers
assigned with OPEN
to do I/O to various devices via the KERNAL.
"Had to Hack It" Example: Apple II
The Apple II did have a logical split between the "monitor ROM" and
the BASIC ROM, but it wasn't as well planned out as the Commodore
version and, in particular, it didn't start with much support for
devices beyond the keyboard, screen and cassette. The system design
did provide for code in ROM on each card intended to handle input and
output, callable with the BASIC INPUT
and PRINT
statements, but
this was fairly limited. So when DOS came along it had to wedge itself
between the BASIC and the BIOS without any real coöperation from
either.
It did this by having a "print" routine in ROM to start the bootstrap
process (thus the famous PR#6
to boot a disk). This would load in
DOS from disk and run its initialization routine, which in turn would
replace some vectors that let it intercept keyboard input and screen
output. So after booting, when you typed a command at the BASIC prompt
it would first be checked by DOS to see if it was a DOS command. If it
was one, like CATALOG
, DOS would execute it, print the output and
return you to the prompt. If not, the input line would be passed on to
BASIC which would execute your LIST
command or whatever you gave it.
While this worked for interactive commands, BASIC had no facilities
for calling DOS. So DOS hooked into the screen output routines as well
and you'd use an escape character, Ctrl-D at the start of an output
line, to inform DOS that it should treat output as a command. Thus, to
print a list of the files on the disk from BASIC you'd use something
like
100 PRINT CHR$(4);"CATALOG"
If you wanted to read or write a file, this would have to be done by
changing the source or destination of the standard input and output
routines via a sequence of commands like the one above. This of course
meant that you couldn't get input from the user or print to the screen
while you had a file open for input or output.
Other Operating Systems
There were other systems not oriented around BASIC (and not used so
much on "home" computers) that are much closer to what you'd think of
as a "real OS" today. The most famous of these is CP/M, but there was
also North Star DOS and a few others. These provided a a core set of
"system calls" to do disk and terminal I/O and a few programs that ran
on top of them, such as a command-line interpreter, a program to copy
files, and so on. These would be used to launch your applications
(word processor, editor, assembler, the assembly-language program
you'd written) in a way much like you do today when using a
command-line interface. But once running, they were still a single
process with no real separation (other than what the programmer
desired) between the application code and the OS code, or even the
hardware.
In 1979 Microware introduced OS-9 on the Motorola 6809, which did
support multiple processes (using co-operative multitasking) and many
other modern features. But for various reasons (high pricing as
mentioned by RichF below, using a less popular—in the U.S.,
anyway—processor), it started out as a niche product. Though these
problems were addressed (including a port to the very popular 68000)
it never really overcame this, probably because by the time these
problems were finally getting worked out we were on significantly
larger machines and could get something even closer to a "real" Unix,
such as Coherent or maybe even Xenix.