Advent of Computing - Episode 82 - Juggling Jobs with OS-9
Episode Date: May 15, 2022Multitasking: we all do it. For a feature of modern computing multitasking has surprisingly old roots. It started out as timesharing on vacuum tube based machines, reached ubiquity on large computers..., then hit a wall: the microcomputer. Multitasking didn't smoothly transition over as soon as home computers hit the scene. It took some time, and it took some adaptation. Today we are looking at what made timesharing work, early changes to microprocessors that paved the way for multitasking, and one of the first operating systems to support timesharing in the home.  Selected Sources:  https://www.roug.org/soren/6809/os9sysprog.html - OS-9 System Programmer's Manual  https://archive.org/details/byte-magazine-1979-01/page/n15/mode/2up - Article on the development of the 6809  https://sci-hub.se/10.1109/TEC.1962.5219356 - The One-Level Storage System
Transcript
Discussion (0)
Who uses a computer for just one thing anymore?
We all have way too many tabs open on our browsers.
We always have some other window up in the background to distract us.
We all multitask.
It would be almost laughable if someone tried to sell us a computer that could only do one thing at a time.
That's just not enough throughput.
We've become accustomed to much more.
Multitasking is one of those features that I think offers up a really interesting story.
It's a feature that makes good sense, it's something people really want, and it turns
out we really like it.
It also makes us more productive, at least in theory it's supposed to make us more
productive.
So, it should be no surprise that once we figured
out how to make a computer run more than one program at a time, well, that kind of became
the golden standard. This was initially called timesharing, and while it's always been a pretty
complicated feature, it became near ubiquitous on larger computers. But this did take some doing.
ubiquitous on larger computers. But this did take some doing. Timesharing requires a certain level of sophistication, not every computer is suited to it. Interestingly enough, timesharing was
theorized well before it was ever put into practice. Papers discussing the possibility
appeared in the mid-50s. Implementations don't appear until the next decade, and even then it's all small scale to start with.
But once things get going, there was really no turning back.
Well, mostly there was no turning back.
By the 1970s, timesharing was the norm on computers, but the definition of computers was about to change.
Intel announced the 4004, the first commercial microprocessor.
Competitors followed.
Suddenly, there was a new type of computer, the microcomputer.
It should be pretty clear that microcomputers,
the machines we all use nowadays, can, in fact, multitask.
The thing is, it took a little while to get there.
The process mirrored the development
of timesharing, from theory to early steps to ubiquity. And just like timesharing, the early
steps, while somewhat obscure, are perhaps the most interesting. Welcome back to Advent of Computing.
I'm your host, Sean Haas, and this is episode 82, Juggling Jobs with OS9.
Today, we aren't talking about Apple's OS9, but instead an operating system developed in the late 70s by a smaller company called Microware.
That operating system was also called
OS9, stylized with a dash just before the number. What exactly is OS9, you may ask?
Well, dear listener, I'd like to present this small operating system as a fascinating stepping
stone. Call it an ideological missing link if you want to be a little overly dramatic.
OS9 was an operating system developed initially for the Motorola 6809, an 8-bit microprocessor.
It's a neat little chip that would see some use in the early era of home computing.
Like its contemporary silicon wafers, the 6809 was small, relatively cheap, and relatively
weak.
That was just what microprocessors were in that era.
Then along came OS9.
This system offered one big feature that home computers just lacked.
Time sharing, or if you prefer, multitasking.
OS9 could not only run multiple programs simultaneously,
it could also allow multiple users to share a single computer.
The software features of this operating system were more in line with Big Iron Offerings,
but it was running on a piddly little home system.
The computers OS 9 targeted would have memory measured in kilobytes, maybe
tens of kilobytes if you were lucky, but it could juggle multiple processes and multiple users at
once. On the surface, that should sound at least a little impressive. It honestly is. Diving into
the details, well, that makes OS 9 even more exciting. Most larger computers, many computers
and mainframes, had specialized hardware to facilitate timesharing. There were a number
of reasons for this. The bottom line is that improved hardware tends to make timesharing
easier to manage. This era of microprocessors didn't have equivalent hardware, at least
that equivalent hardware wasn't widespread.
As a consumer, you were happy to be able to loop,
and multitasking wasn't really on your radar.
Not really.
The earliest versions of OS 9, called Level 1,
explicitly targeted computers without any of the special time-sharing helping hardware.
Now, as with everything, there is a larger context here.
What is OS 9 a stepping stone towards? Let's call it the home computer's coming of age.
Early microprocessors, the first generation that started being produced in the early 70s,
they couldn't really do much. The 4004, for instance, was just barely more powerful than a calculator.
Really, perhaps better put, it was more flexible than a desktop calculator.
The next generation chips like the 8080 and the MOS6502 were a bit more powerful.
However, they were still limited.
These chips could function like computers, but they were missing, you know, a bit of the oomph of larger machines.
Once a new slate of chips hit the scene at the end of the 70s, we start seeing more advanced features.
OS 9 was one of the early operating systems to really take advantage of those features. The sum total here is that as the 70s move into the 80s, we're seeing home computers pushing more and more to emulate their larger cousins.
Sure, an Altair 8800 is great and all, but wouldn't you rather have a PDP tucked under your desk?
This episode is going to be one of those comp sci 101 kind of affairs. To really understand why OS 9 is so rad, and
why the march towards mainframeification is such a big deal, we need to cover basics.
Our discussion will start by looking at the special hardware that made timesharing possible,
or at least facilitated it. From there, we're going to jump forward a little bit and start looking at Motorola's new chip, and then finally OS 9 itself.
Welcome to class. Since we're all fancy academics here, I figured it'd be best to start off with a clipping from a very fancy and very academic-sounding paper.
So, if you'll allow me, quote,
If you'll allow me, quote,
The speed at which digital computers operate depends heavily upon how fast the processor can get data and instructions from memory.
Over the past quarter century, the internal organization of computers has become increasingly
sophisticated as a result of efforts to make data more readily accessible to the central
processing unit.
efforts to make data more readily accessible to the central processing unit. In parallel with this engineering activity, systems programmers, installation managers, and computer scientists
have been striving towards the same goal. Their effort to optimize the processor's access to
information has come to be called memory management." That's from a 1981 ditty titled The IBM History of Memory Management.
Once again, you've fallen for my trap. You've thought this was an episode about time sharing
or multitasking, maybe something about computer history. Ah, but you're wrong. This is another
lecture about how cool memory is.
A lot of this episode is going to actually be concerned with how memory is used and how it became better utilized.
So, be prepared.
This IBM paper is talking about the fact that, over time, we've had to find better ways to deal with memory.
A machine like ENIAC didn't really have addressable memory at all.
The same goes for earlier analog machines. The earliest problems people wanted to solve were
simple mathematical calculations. That doesn't always require a lot of memory, and in a lot of
cases, it actually doesn't require any real memory at all. But that changes as problems become more sophisticated, as computers can handle larger tasks.
Really, once computers are created, we can start solving different problems.
We can start asking questions that you can't do with normal math.
Now, it's tempting to break this up into eras.
Maybe call the early period the era of processor
primacy and then we move into the era of memory primacy, but hey, I think that's kind of dumb.
The first stored program computer, EDZAC, was based on designs that were drafted while
ENIAC was still under construction.
The so-called era of processor primacy may have lasted for months,
if we really want to be generous there. Stored program computers are really where it's at,
so that's the memory model that we're going to start with today. In the most basic setup,
you have a processor, the part that does the crunching, and some type of addressable data store.
These are connected by two buses, the data bus and the address bus.
The data bus is just what's used to move numbers back and forth.
It's not super interesting for our purposes today.
The address bus is where a lot of innovation is hidden.
This bus is used to specify the address you want to access.
That may seem simple, but it actually leads to all sorts of interesting and frustrating problems.
A prime issue is the most simple one. How wide is the bus? When talking about the size of digital data, it's traditional to call it
width. I've tried figuring out why we call it that, and I have yet to see a definitive answer.
So here's how I think about it. Each bit on a bus requires a physical connection,
whether that be a wire, a trace, or maybe a thin region of dope silicon.
A bit is just an on-off value, thus it has to have some physical pathway to travel.
When you need to move more than one bit, you bundle those connections up into a bus.
Most often, these buses are laid out with their wires side by side,
so you have what looks like a big wide ribbon.
The width, then, can easily be understood as how many wires actually make up this wider bus,
the width of this physical ribbon. Memory access requires you to use the bus. In the most simple
case, the processor will just put a value on the address line to specify the location and memory it wants.
A pulse is sent out on a separate line to tell the memory device that someone wants to read from it.
Then the memory device will grab the data stored at the location specified on the address bus and send that value back to the processor over the data bus.
It's important to note that addresses aren't
anything special. They're just numbers that have to be expressed in binary like any other number
on a computer. That means that they have all the usual restrictions. These are whole numbers,
I think that should go without saying. You can't have address 1.5. They also exist in a range dictated by the size of the bus.
So if your processor has an 8-bit wide bus, then your highest memory address is 255.
I know, I often talk about how digital computers are great because they have managed to overcome older physical limitations.
Well, this is one case where those
limitations do like to stick around. The point here is that when it comes to the address bus,
size matters a lot. A further consideration, and a bit of a tangent, is how the processor
internally represents data. This becomes relevant when dealing with pointers, that is,
variable values that reference some address in memory. You usually access a pointer by first
loading the desired address into one of the processor's registers. Then you tell the
processor to take that register, treat it as an address, and you're good to go.
The easiest way to account for this addressing by
register is just to have each register that can be used as an address pegged to the same width
as your address bus. If your bus is 8 bits wide, then so are all your addressable registers.
But that's not always the case. On occasion, a processor's registers will be a different size than its address bus.
You can end up with a computer that uses a 22-bit address bus, but only has 16-bit wide registers.
There are ways to deal with this, which we'll get to in a second.
So far, this has all been addressing the most simple possible case,
where you have a direct connection from your processor to your memory. Each line of the
address bus on the processor matches up to a line of the address bus on the memory device.
Simple, direct, easy. But life isn't simple, and computers are rarely easy.
Let me paint a picture for you.
Let's say you have a fancy new research computer.
The processor is complete, it works great, and all wired up to the rest of the system.
You have a shiny new 24-bit address bus, wide enough for any future needs.
However, you don't have enough RAM. You want to
use new magnetic core memory, the fast RAM that's sweeping labs the world over. But your grant
funding can only cover 16 kilobytes of core memory. Your bus is wide enough to fit megabytes of RAM,
but that's just too much for your pocketbook.
A similar situation played out at the University of Manchester in the waning years of the 1950s.
The university, in collaboration with Ferranti and Plessy Corps, were designing a computer named Atlas.
This is a story I'll dive into in more detail on another episode, but it makes for a really good setup here, so I can't resist.
Atlas was targeted to be a commercial system, at least on a small scale.
At the time, magnetic core memory was just the best option for short-term data storage.
It was fast, reliable, and 100% random access. But it wasn't very cheap. A fun little fact is that the
production of magnetic core memory was almost impossible to automate. This kind of memory was
built from tiny ferrite washers woven into a mesh of copper wire. So for many years, it had to
literally be woven by human hands. That kind of labor doesn't come cheap.
Atlas was designed to use 16 kilobytes of core, not really enough to get much done.
This was augmented with an additional 96 kilobytes of magnetic drum memory,
spread across four separate drums.
Now, magnetic drum memory is a bit of a strange beast.
It works by spinning a magnetizable drum under a set of pickups. If you want all the fine details,
then you should check out my recent episode on magnetic core and why it's just so much better.
The summary is that drum-based memory was slow, not as reliable, and had to be accessed
somewhat sequentially. There were at
least restrictions. But it was cheap. So the question becomes, how do you make a single
computer that uses multiple types of memory? Drum and core memory each have their own specific
characteristics. They each have to be used in a specific way. As I will remind you from IBM's history of memory management,
quote, over the past quarter century, the internal organization of computers has become increasingly
sophisticated. To make this all work, Atlas had to become a more complicated kind of computer.
It couldn't just have the processor wired directly into its memory. Atlas needed something to sit
between the two and deal with all this nasty stuff. The solution was called the One Level
Store by Atlas's designers. We know it today as virtual memory. In the most basic sense,
virtual memory is an abstraction on top of physical memory. So far, we've been discussing the physical level,
the actual wires and ferrite beads that make up memory and its associated buses.
While that's an important level, it's a bad place to be if you're a programmer,
or, by some strange twist of fate, if you find yourself as a program.
You really just want to say,
hey, give me some memory and let some deeper
machinations take care of the specifics. Atlas implemented these deeper machinations using a
combination of hardware and software. The actual implementation is relatively simple. Here I'm
pulling from a 1962 paper on Atlas called, fittingly enough, One Level Storage System. The trick here had to do
with shuffling around resources. Memory on Atlas was broken up into 5 12-word chunks called pages.
During normal operation, a program could request data from any given page. So you had access to
the full address range. But there was an intermediate layer between you and the physical memory.
In the most simple terms, we could say that Atlas used its faster core memory as a cache
to speed up performance of its slower drum memory.
But it's a little bit more complex than that.
Each page and core had an associated PAR, or Page Address Register.
These were just entries in a table that listed which page of drum memory was stored in which
page of Core memory. When a user tried to access a physical memory location, Atlas first checked
the PAR to see if the requested location was loaded into core.
If so, then Atlas redirected the program to the proper address in core.
So you might ask Atlas to return a value at address 0x100, but actually be served the
value at address 0x005.
That first number, the address you asked for, is often called the logical address,
while the actual location of the data is called the physical address.
The shuffle occurs if you try to access data from a page that's not loaded into core. In that case,
Atlas fires off a routine to grab that page off the drum, find a free PAR, and then load
the page into core memory.
There's some record-keeping kind of stuff that goes on in the background, but that's
the basic rundown.
In Atlas, for the first time, we see a processor that isn't wired directly into memory.
There's this intermediary agent.
into memory. There's this intermediary agent. So why does this virtual memory or one-level store or whatever you want to call it matter for time sharing? Well, to put it simply, it lets you pull
some sick tricks. Here I've been highlighting virtual memory because it was the earliest tool
in play. It helped Atlas reduce overall costs, but it was just as easily applied elsewhere. You see,
you aren't limited to switching between two different types of memory. You can use any
kind of storage as long as it can hold data and is at least reasonably fast to access.
As hard drives entered the fray, mainframes started using them as secondary storage for
virtual memory systems.
With this tweak to the technique, you could now have a computer with theoretically limitless
memory. I mean, there are limits, but it was one way to break past the address bus barrier.
If you needed more memory but didn't have any space left on the bus, all you had to do was swap.
but didn't have any space left on the bus, all you had to do was swap.
Just take a chunk of RAM that's not being used, throw it on disk, and presto,
you've just made more RAM for free.
On its own, virtual memory is fancy.
I mean, who doesn't love the ability to just lie your way into having more memory?
That's pretty sweet.
When used in conjunction with timesharing,
things get really slick. If you set up everything right, then virtual memory and paging allow two programs to operate on the same computer in total isolation. This is all possible thanks to the
split between logical and physical addresses. It's usually accomplished on the
operating system level with a little help from specialized hardware. Since logical and physical
addresses don't have to line up, a program can request one address, but the operating system
can decide where to route that request. Just tie that routing into the process and you're good to
go. Each process can have its own set of pages that don't overlap with any other process.
Thus, two programs could read from the same address at the same time,
and thanks to the wonder of logical addresses, they could be given different values.
This is really safety at its finest.
To the program, it seems like it's all alone, but in reality, it could be running on a very crowded machine.
But it can never be exactly that easy.
Introducing this extra layer between memory access and physical memory introduces a new issue.
Performance.
Any added operation here eats into CPU cycles.
You'll never be able to get back to that baseline speed of one access equaling one operation.
The worst case scenario is that you have to do all of this purely in software.
You have to write some custom code that intercepts every time a program tries to access or really do anything with memory,
and then does this shuffle-translate boogie.
But luckily, we can do better. One solution, and the one I'm most familiar with, is the so-called paging box developed at MIT for use with the Incompatible Time Sharing System, or ITS. This was
quite literally a physical box that sat between the lab's PDP-10's processor and their memory.
A bus allowed a program on the PDP-10 to configure how data would be paged,
basically a fancier descendant of the PAR tables used in Atlas.
Then the box would handle the rest.
This has the advantage of freeing up the processor.
In theory, at least, this could bring these mediated memory operations closer to raw access performance.
The paging box had one other ability.
It implemented memory protection.
This is that timesharing trick I mentioned, just in physical form.
The paging table included options for restricting access to specific pages.
So when setting up a table, ITS was able to instruct the paging box
to keep certain chunks of memory isolated from other programs.
Thus, timesharing was not only made easier to implement,
it was also made more performant.
This whole realm of physical devices that sit outside the CPU and handle memory operations
are called Memory Management Units, or MMUs.
They're really nice, especially for timesharing, and they've become standard on computers.
But back in the day, when home computers were just starting out, MMUs were
mainly a big iron thing. I mean, why would you need all that power down in wafer world?
There's always been a certain drive to create microprocessors more in the mold of large
computers. We could look at this in a number of ways. Mainframes and minis are just more
capable machines, there's no better way to put that.
You can do a lot more in a big system thanks to all the features available and thanks to their larger size.
More memory means more code and data.
Faster processors means you can save time crunching numbers.
For Motorola, these features also meant costs could be cut.
In the 70s, Motorola got in on the processor game.
If you want a larger roundup, then go check out my episode on the 6502.
The whole first half is all about Motorola.
Anyway, the big M's first offering was the 6800.
Part of the 6800 team split from Motorola,
and they went over to another company called MOS Technology. There,
they built the 6502. This second chip is essentially a cost-reduced and feature-reduced
version of the 6800, designed thanks to client complaints. Perhaps unsurprisingly, the 6502 would
become the more popular of the two processors. At this point, Motorola, as well as the defectors over at MOS,
were targeting the embedded systems market. That is, the 6800 and 6502 were planned as ways to
give machines a little bit of smarts. Motorola's initial vision was that the 6800 could be one of
a number of building blocks, prefabricated chips that could be thrown together to quickly build
an embedded computer. Why go with this approach? Well, once again, it all came down to cost-cutting.
I'll cut you in on a little bit of career advice. It's something of a secret, so don't go sharing it.
Software costs are almost always higher than the cost of hardware. Just think about it.
For every computer, you end up using multiple different programs.
Each of those programs had to be written by human hands.
We usually count the amount of effort spent developing software in human hours,
and some programs are easily in the human year range.
Humans tend to be paid by the hour, and since
software development takes a lot of time, that means that software isn't cheap to develop.
As I've said before, high-level programming languages were invented to make programming
faster. Even with those tools, programming good software still takes huge amounts of time.
So software is always a pretty secure
field to work in. Motorola was attempting to offset their clients' software development costs
by providing a box of relatively standardized and reusable parts. The idea here was that if,
say, you wrote an interface program that used one of Motorola's compatible I.O. chips,
then that program could be reused on later projects that used the same chips.
It's all in the same family, but here's the thing.
The 6800 was a pretty primitive chip.
I haven't programmed for the 6800 before,
but I have spent some time dabbling with 6502 assembly.
These processors just don't have
a whole lot of support for fancy software. By that, I mean you end up writing out a lot of
assembly by hand without shortcuts. For instance, neither the 6502 nor the 6800 have a multiply
instruction. That's left as an exercise to the programmer. That's annoying,
but there's something worse. When programming the 6502, my biggest hangup was the chip's
lacking register set. Both these 6xxx chips are 8-bit, meaning that internally they like to
operate on 8-bit numbers, and they use an 8-bit data bus externally.
However, they also support a 16-bit address bus.
This leads to some, well, awkwardness, to put it lightly.
The 6502 deals with this mismatch by providing two 8-bit registers, X and Y. They can be combined to
describe a full 16-bit address. The 6800 provides a single special 16-bit index register that serves
the same purpose. Either way, these chips have to be specially designed to take advantage of
their larger address bus. That leads to a fun issue. How do you
write a program for these chips that compares two strings? Let's assume that
each string is held in memory at some address. You have to have some way to
tell the program where these two strings are. But you can't use registers for that,
since, well, you only have one register that can hold a full address.
There are a number of ways around this. One is to use a stack for passing arguments, but
these chips don't really have very sophisticated stack support. Luckily, we have now.
The 6800's designers knew this kind of problem would arise, so they gave us a special direct
addressing mode. Now, addressing modes in
general can get a little confusing, and the name sounds a little more grandiose than it actually is.
A computer usually has multiple ways to talk about memory. The most simple is explicit or
constant addressing, where you give a literal address as a number. You can also address by the value in a register,
often called indirect addressing.
The direct addressing mode of the 6800 is a little different.
It takes an 8-bit address, either as a constant or from a register,
then assumes that the upper 8 bits of that address are 0.
In practice, no one really calls this the direct
addressing mode. They call it the zero page, since, you know, it only works for direct access
to the lowest chunk of memory. You know, zero. The 6800 as well as the 6502 implement this feature.
Zero page access is also a little faster than other memory modes.
And in programming practice, the zero page is often used for passing parameters to functions
and holding temporary values. So that string compare function that I mentioned? Well,
you can just put both addresses somewhere in the zero page. Easy peasy. But, dear listener,
page. Easy peasy. But, dear listener, life can never be that easy, nor peasy. Aggressive use of the zero page leads to a very specific problem. It can create unsafe code. Now,
the setup for this is a bit complicated, so you're going to have to bear with me for a minute as I
paint a word picture. To get to why the zero page can sometimes lead
to problems, we need to talk about interrupts. Interrupts offer a fun little wrinkle to normal
computer operation. An interrupt is a mechanism by which normal execution can be, well, interrupted.
When an interrupt fires, the processor stops, stores its current state,
and then starts running what's called an interrupt handling routine. Once the routine is done,
the processor grabs its saved state and then restarts from where it was prior to being
interrupted. This mechanism is most often used for input and output devices. When you hit a key on a
keyboard, that tells a controller to
fire off an interrupt. The processor then stops what it's doing, runs a keyboard handler,
and then gets back on task. It's a really nice mechanism that helps us write better
code. Instead of having some main loop that's always running and checking for keyboard inputs,
we just need an interrupt handler. The keyboard handling code is
only executed right when it's needed, so we save a lot of processor time. However, not all chips
are equally well suited to interrupt handling. Here we get back to the 6800 and 6502 and to the
dangerous zero page. Let's say you have a keyboard interrupt handler set up
and your code is currently running that fancy string compare
that I keep talking about.
You've decided to use locations 002 and 004 in the zero page
as arguments to your string compare function.
Then someone presses a key.
Your currently running program is put off to the side for a minute, and the processor
starts dealing with the key press.
What would happen if the keyboard handler also used address 002 in the zero page?
What if that was where it stored the value of that key press?
The result would quite obviously be disaster, or at least your nice program would get a little trashed.
So to make everything play nice,
you have to have some pretty fine-grained control over your code.
You have to make sure that any interrupt handlers
aren't clobbering any locations in the zero page.
An interrupt can happen at any time,
so you have to always write around that possibility.
In legal terms, you could say that interrupts have, at least on these chips, a chilling effect.
They impose limits on how you can program, and what resources you can use in that program.
This gets a lot more dangerous if you're using an interrupt handler that you didn't write.
How do you handle that level of coordination?
What if the code, unbeknownst to you,
is using the same chunk of zero page that you need for the rest of your program?
For the keyboard example, this might be a little annoying,
but we can do worse.
Preemptive multitasking is the most common kind of multitasking nowadays.
It was also the trick that originally facilitated timesharing.
In this model, programs are preempted, as in the operating system switches to a new
program at some interval of time.
The current process gets stopped, no matter what it's doing, and another process starts
execution.
stopped, no matter what it's doing, and another process starts execution. This is commonly handled using a programmable interrupt timer, a circuit that repeatedly fires interrupts at some predetermined
time. Preemptive multitasking functions something like this. The interrupt timer fires off an
interrupt. The processor stops what it's doing to handle that interruption. From there, the current
program is suspended and a new program is chosen. The chosen program is then unsuspended and resumes
execution. This continues until, eventually, the suspended program is back in the foreground,
and then the loop just keeps cycling. To this initial program, the one that gets suspended,
it looks like an interrupt just fired and then returned. It may have been suspended for
milliseconds, or it might as well have been suspended for days. It can't really tell,
and crucially, it could have been suspended in the middle of some delicate operation.
suspended in the middle of some delicate operation. Now think about this in the context of the tiny 6800 and 6502.
Think of it in the context of the zero page.
Maybe the first process was setting up a string compare.
It just put a few addresses into the zero page.
Then before it actually called that function, it was preempted.
Another process starts running. This second
process also does a string compare. Hey, it's a popular function. When the first process resumes,
it finishes up making its own string compare call. The addresses that it just set up in the zero page
have been changed. Suddenly, it's comparing entirely different strings, and so all your careful
planning has just fallen to pieces. That's what happens without some type of memory protection.
This is exactly why memory protection is so important. It stops a dangerous form of
cross-pollination in your address space. That's also why many early microprocessors were poorly suited to multitasking. The 6800 and
6502 are a fun example because they lack any kind of memory management unit, and the ZeroPage
promotes a dangerous type of programming. At least, it's dangerous in this very specific case.
This was one issue facing Motorola as the MOS-6502 started to outsell their own 6800.
Motorola needed to make a new chip to try and reclaim some market share. One solution was to
take the successful part of the 6800, the hardware building block approach, and expand that into the
software domain. Their new chip, dubbed the 6809, would be designed to better
support complicated software while retaining some compatibility with the 6800. A big part of this
facelift was low-level support for multitasking. At least, that was the core idea. So on to the
brass tacks. How did the 6809 actually help with multitasking?
It's more accurate to say that it didn't really support it, but it gave programmers tools that made multitasking easier to implement.
The first tool in this utility belt was the ability to change the zero page.
In early 1979, Byte ran a series of articles discussing the development of this new chip.
It outlined the rationale behind the development of the new toolkit.
The chip had a very software-centric approach that I haven't seen all that often. It's kind
of refreshing. During the early stages of development, Motorola actually surveyed existing
6800 software to see what programmers
were actually doing with their hardware. I can't stress this enough, that's just a darn good idea.
Motorola also took a page from the 6502 defectors and gathered client feedback.
The 6809 would be molded by these two big inputs, analysis of existing code and complaints from existing
programmers. The first tool to be slotted into this new utility belt was comprised of changes
to the zero page. I think I've made a good case that the zero page, while being interesting and
useful, was a little bit unsuited for truly complex software. The 6809 replaced this inflexible storage with something
better. From the Byte article, quote, the direct page register can also be used effectively and
safely in a multitasking environment, where the real-time operating system allocates a different
base page for each task. End quote. This, I think, is an interesting step towards small memory management
hardware. The zero page, called the base page here, was replaced with a movable chunk of memory.
The direct page register was used to specify where the base page would be located. Access to the base
page would be accomplished the same way. A program would just ask for a location between 0 and 255.
But the upper 8 bits of that address would be pulled from the direct page register.
A direct read of 001 could be pulled from 001, or it could be pulled from something like 123-001.
The 6809 translated from a logical address to a physical address using a configurable value.
It's less flexible than something like the paging box used by ITS, but it has a similar flavor. It's
a similar solution. This also introduces an interesting possibility. In 1976, Texas Instruments
debuted their TMS-9900 processor.
This chip had a somewhat similar mechanism of action.
It used a chunk of memory for its general-purpose registers.
So when you interacted with a register, you were really interacting with some location in memory.
Where those registers were stored could be changed on the fly.
This was also useful for multitasking.
In turn, this design stemmed from earlier TI computers.
The side point here is that the idea of a chip mimicking a larger machine didn't start with
Motorola. This was an older lineage. So is there a connection here? I'm not entirely sure.
Is there a connection here?
I'm not entirely sure.
The 6809 was primarily designed by two people, Terry Ritter and Joel Boney.
I haven't seen any connection between these two and Texas anything prior to their work at Motorola.
That doesn't preclude the possibility, but it takes away a smoking gun.
I'm bringing this up to point out that the idea of moving around some special block in
memory isn't unique to Motorola's new chip.
It was a technique that was already out there.
Anyway, the revamped zero page, or the base page now, was the first big step towards multitasking.
This is also the big flashy part.
Interrupts could now use a special zero page.
Each process could, in theory, have its own zero page. The method of action here meant that a
program wouldn't have to have some hard-set direct page register. That could be configured when the
program was loaded. The other crucial tool that I need to address is a lot less flashy, and it's not exactly a single new feature.
The 6809 had improved support for relative addressing.
This is another type of addressing mode.
The 6800 already had some support for relative addressing, but the newer chip expanded that support.
Relative addressing just means that instead of giving some absolute address
to a location in memory, you can just give a relative distance. So instead of saying jump
to location 01534, you could just tell the processor to jump forward by five bytes.
At first, that may sound a little underwhelming. I told you it's not as flashy. But relative addressing has one huge
advantage. If you only use relative addressing, then you can create position-independent code.
In other words, your program can be loaded into any location in memory. For small machines,
this doesn't super matter, but it's crucial for timesharing. A program won't always be loaded into the same
exact address, so code has to be position-independent. The revamped moving zero page plus
improved relative addressing made the 6809 a great host for this kind of software.
So what about that software? What was actually running on this thing? This finally brings us to the long-promised topic, OS 9.
The history of OS 9 is a little, well, undocumented.
This is one of those stories that we have to piece together from manuals and magazine clippings.
It all goes back to a company called Microware.
The company was founded in 1977.
back to a company called Microware. The company was founded in 1977. As far as I can tell, their first product connected to Motorola was even more obscure. It was called RT68. We've already hit a
side note that I need to address. The RT here stands for real time. As a 1978 article on RT68 in Kilobod puts it,
Executive here is just a reference to the fact that RT68 is an operating system-like environment.
What bugs me here is this. The meaning of real-time
isn't the same as the more modern use of the term. More recently, real-time operating systems
have been described as systems that perform procedures within strict or guaranteed timing.
A real-time operating system might, say, always respond to signals on the serial line within 3 milliseconds.
Nothing I've read about RT68 implies that it had any timing guarantees.
When Microware calls it real-time, they mean that you can type into it and get feedback in, you know, real-time.
But hey, that's not what was going to be impressive about the offering.
RT68 ran on the 6800, and it supported multitasking.
The tradeoff was that RT68 was a pretty bare-bones operating system,
and all its multitasking was handled in software.
We get some hints at how this worked in the 78K article.
and software. We get some hints at how this worked in the 78K article. It goes into pretty fine-grained detail on how multitasking was actually accomplished on the limited Motorola
chip. RT68 kept a table of processes in memory. The table actually allowed for some pretty neat
controls. Each task had a priority level. You could also set how long it could run before a swap occurred.
This table also hides the slick trick in it, so to speak.
Each entry had an address for the process's stack.
RT68 set up a separate stack for each process.
That is, each process had its own pushdown stack that could be used for state data and
function arguments.
Once again, the zero page was just too dangerous to use in a multitasking environment,
so Microware had to provide some software-defined replacement.
Anyway, I think if anything, RT68 gave Microware a good introduction to pushing small chips to
their limit. You shouldn't really be
able to multitask on a 6800, at least not in a very meaningful or nice way. But here we have
Microware accomplishing just that feat. As we know, the 6800 wouldn't be long for the world.
Soon the replacement chip, the sporty new 6809, came in to steal the spotlight.
With the new chip came the need for new software,
hopefully software that could show off the better side of the 6809.
Motorola, as a hardware house, reached out to existing software vendors to have some code thrown together.
Microware was one such vendor.
As explained in the OS9 System Programmer's Manual,
was one such vendor. As explained in the OS9 System Programmer's Manual,
OS9 Level 1 is one of the products of the BASIC-09 Advanced 6809 Programming Language Development effort undertaken by Microware and Motorola from 1978 to 1980. During the course
of the project, it became evident that a fairly sophisticated operating system would be required to support BASIC-09 and similar high-performance 6809 software.
End quote.
The logic here, at least in my twisty head, is easy to follow.
BASIC-09 was a pretty fancy dialect of BASIC.
The package included a compiler as well as a more standard environment.
package included a compiler as well as a more standard environment. Just as an aside, Basic09 was advertised as being so well-structured that you didn't need line numbers, so it's
kind of a neat little dialect to see popping up.
To support a big fancy language, you end up needing a lot of smaller components. For Compiler,
you need some way to handle files, since you have to pass in source code and then store
compiled binaries somehow. You also need some way to manage memory. Every time your code calls out
to create a new list, for instance, you have to find somewhere free in memory to place that list.
You also need some kind of user interface, since, you know, a human is expected to use the thing. At some point in
the Basic 09 project, Microware realized it would make a lot more sense to just create an entire
operating system to support Basic 09 and any later projects for the 6809. Why reinvent the wheel when
you can do it right the first time? Now, jokes aside, this was probably the right way to go.
An operating system, once you shed all the artifice and whistles, is really just a toolkit
for allocating resources and providing some kind of useful programming interface. Many of the
resource management problems you need to solve when writing a compiler or a programming environment, like BASIC for instance, are already solved by an operating system. You could write up a bunch
of libraries to reuse later, or you could go one step further and just make a full OS.
We don't have very good information on the actual development cycle of OS 9, beyond the whole
BASIC to OS pipeline. So instead of more speculation, I'm jumping us right into OS 9, beyond the whole basic-to-OS pipeline. So instead of more speculation,
I'm jumping us right into OS 9 itself. Hey, this is a pretty technical episode,
so we're going to be sticking to that feel. The best resource I found is the aforementioned
System Programmers Manual. I think it makes for some riveting reading.
A good observation to start with is that OS 9 compares itself favorably to Unix.
Microware's own literature talks about their small operating system in terms of a larger
timesharing system. I mean, right there, we're on the trail of how big iron was getting scaled down.
Maybe call it small aluminum. So what exactly was being cut out to reduce the overall scale?
You can't have Unix on a micro, at least not quite yet. So what did early 6809 adopters get?
The main connections here are that OS 9 offered full preemptive multitasking, just like Unix.
It supported multiple users, just like Unix. It supported multiple users, just like Unix.
It supported a hierarchical file system, just like Unix. And OS 9 also exposed devices as files,
just like classic Unix. That last point may sound like the most mundane, but I'd argue it's the coolest. You'll often hear that in Unix
land, everything is a file. When I was first entering the Nix fold, I know I kept hearing that,
and it actually took me a little bit too long to fully grasp the meaning. To put this a less
catchy way, both Unix and OS 9 offer a unified device interface. You don't have special functions
for reading from the serial port, writing a file, and printing text. Those are all handled the same
way. The common denomination that Unix went with was the file. To talk to a terminal, you just
access a special file, something like devtty. That uses the same
functions as reading and writing any other file. That makes programming for Unix really nice.
It also means you can redirect data around freely. You can throw the outputs of a program into a file
or out to the printer or anywhere you want. OS 9 follows that same prescription. It's a pretty neat trick,
but it's also a wonderful way to handle hardware. OS 9 goes about this unification in, once again,
a smart way. Can you tell I'm a little bit impressed by the design here?
Anyway, this brings us to what OS 9 calls memory modules. Just like the whole real-time versus real-time thing,
there's a little bit of confusing phrasing here. To be fair, OS 9 would eventually become a real-time
system in the modern sense, but not for a while. Anyway, in OS 9, a memory module isn't a physical
module of memory. It's a loadable chunk of code wrapped with some information about
that code. Basically, it's a specialized executable format. I'm not going to get into
great detail about the code metadata, but there are two fields that we need to touch on.
First, each module had a name. I know, groundbreaking stuff. Second, each module specified how much space in memory it needed for storing data.
You see, in OS 9, code and data are kept separate for a really slick reason.
A memory module is different than a process.
This is some subtle and pretty highbrow stuff. The best way to make sense is
to go through how OS 9 fires off a new process. Let's say you're starting up a text editor. Call
it edit. All you do is type edit at the command line and hit enter. OS 9 then searches its table
of loaded memory modules for a module named Edit.
If it can't find one, then it goes to search some paths on disk.
Once found, the module is loaded.
So far, all normal stuff.
Once the module is found and settled in memory, OS9 allocates some space for the module's data.
If Edit says it needs 10 kilobytes, then OS9 goes and finds 10 kilobytes of memory for it. The first chunk of that memory is set up as a direct page, so the running process has its own
isolated zero-page equivalent. The memory module and this data chunk could be anywhere in memory.
They could be right next to each other, or they could be separated by kilobytes. Then, OS9 passes execution to edit's memory module. To the user, it just looks like
the text editor has finally loaded. So what happens if a second user tries to run edit?
Or if, for some weird reason, the initial user wants to run a second version of edit. OS9 finds the
module in memory, then allocates some new space for this new data. Then it passes off execution
to the module. Each instance of edit is using the same module, the same chunk of code, but they have
separate chunks of memory for data storage. Each process has its
own direct page for passing around arguments. All is isolated and safe, just as it should be.
I think this is a really cool way to adapt multitasking to a smaller world.
Normally, in a system like Unix, an invocation, that is, a call to load a program, does just that.
It loads a program.
That's fine, but it can waste memory.
On a mini-computer, you might have memory to burn.
But OS 9 was targeting machines that had something like 40 kilobytes of memory.
To run at that size, you have to keep things lean.
So it's better to reuse what you
already have loaded. Microcomputers also didn't have fantastic storage options yet. The Penumbra
here was small, so to speak. You might have some tape storage, maybe a floppy drive or two if you
were lucky. Neither of those devices are actually very fast, so once again, it's better to reuse what you've
already loaded. To accomplish this thrifty feat, OS9 software had to follow some guidelines.
The first was that memory modules had to provide information about what they were and how much
space they needed. The code also had to be kept separate from its variable space. You can't have variables tucked away in reusable code.
And finally, all your code had to be position independent.
That is, you could only use relative addressing.
The 6809 provided all the features needed to make that easy.
And just remember, with the direct page,
that relative addressing could be used in the same
way as the old zero page.
That definitely made things easier to adapt to.
The upside here is that if we say that Unix could guzzle RAM, then OS 9 practically sipped
on it.
This is a kind of efficiency born of necessity.
The other upshot is that OS 9 could do some really cool stuff. The core of OS9,
called the kernel, was relatively small. All it had to do was provide a way to allocate memory,
manage tasks, and some mechanism for handling memory modules. Sure, those are complex tasks,
but that's not a whole lot of tasks. We have a short list here. Device drivers were also loaded as modules, just a special case
of the memory modules used for normal software. And this was done dynamically at runtime.
So in theory, let's say sometime after you boot up, you decide you need to plug in a hard drive
to the parallel port. You could just tell OS 9 that it was time to load up the appropriate driver and
you're done. No need to reboot, no need to recompile the operating system. You just load a new module.
You could even write your own drivers for the device with relative ease. You could develop,
debug, and build that driver inside OS9 itself while it was running. For home computers of the era, that was a whole lot of power.
This was close to something you'd do on a mainframe.
So hey, maybe the restriction here bred something really useful.
In the current day, multitasking at home is normal.
I don't think anyone would buy a new computer that could only do one thing.
But it would take us a while to get there.
OS 9 launched in 1979. Just two years later, we get the IBM PC powered by MS-DOS, a very simple
single-tasking operating system. OS 9 lived largely outside the usual spotlight, despite
packing all the advanced features
that home users would have loved to have. Depending on your viewpoint, we wouldn't
get mainstream multitasking at home until the middle of the 80s, maybe later. Even then,
it wasn't as slick as Microware's Little Marvel.
Alright, we've reached the end of this episode.
Maybe the best way to characterize this is an exploration of timesharing through the lens of OS9.
So, what have we learned here? What's the takeaway?
Really, we're looking at this odd liminal period here.
Timesharing was technology with pretty deep and pretty old roots.
It had been developed and shaped by what was happening to larger computers. In turn,
large computers were shaped by the need for timesharing. Memory management units and devices
like the page box used by ITS were built for timesharing. Once microcomputers start to appear, they have a lot of catching up to do.
The initial attempts at timesharing on micros had to be different than timesharing on their
larger counterparts. The hardware was just more restrictive, it was different. OS9 is a great
example of the compromises and tricks needed to bring timesharing to the home. We've seen how this
smaller version of timesharing was
different, how it adapted to the restrictive environment. It's another step on the path to
our current desktop paradigm, and I think it is a fascinating step to look at.
Thanks for listening to Advent of Computing. I'll be back in two weeks time with another piece of
computing's past, and hey, if you like the show, there are now a few ways you can support it.
If you know anyone else who'd be interested in the show,
then why not take a minute to share it with them?
You can also rate and review on Apple Podcasts.
And if you want to be a super fan,
you can support the show directly
through Advent of Computing merch
or signing up as a patron on Patreon.
Patrons get early access to episodes,
polls for the direction of the show,
and bonus episodes.
You can find links to everything on my website, adventofcomputing.com.
If you have any comments or suggestions for a future episode, then go ahead and shoot
me a tweet.
I'm at Advent of Comp on Twitter.
And as always, have a great rest of your day.