I started my sw career on mainframes in the 70s.
The pictures I have included are snapshots of that era, by pure coincidence they seem to be the same system. Note the excellently fashionable clothing worn by the operators. I can confirm this was the required uniform at the time.
The machines I worked on were medium sized systems , but they could run a whole companies computing needs. They were not ‘online’ like today but they were interactive systems supporting call centers, inventory management, order processing, customer service, payroll, etc.
These were tiny systems by today’s standards, (my raspberry pi has 5000 times the compute power, 1000 times the RAM , 20,000 times the disk space, the mainframe cost roughly $500,000!) so how did they do it?
A quick tour

The system I will describe is a Honeywell / Bull Level 64. Not an IBM system , but they all worked roughly the same. The Level 64 is the same power as a mid size IBM 370.
The processor

That cabinet holds the CPU and its RAM. There is
- CPU running at 2mhz implemented in TTL chips. This was the first generation to use ICs as opposed to transistors, but these were tiny ICs. One chip was, say, 4 and gates, maybe 20 transistors.

- Memory was up to 1mb of RAM (that’s *megabytes* not gigabytes). First generation not to use core memory. Used 1k static ram chips
- Microcoded high level cisc.
- EBCDIC (not ASCII)
Also in the picture

The wedge shaped control panel, this has a bunch of buttons, lights and dials for powering up the system etc. Also a screen that usually echoed the last 20 lines of the ..
Operator console. A teletype device that therefore had a record of all operator commands and responses.
Storage

Multiple 200mb disc drives. One held the operating system, the others held code and data.
In the picture I have highlighted the 3 drives I can see. I assume there is one between those 2 labelled ‘1’ and ‘3’
The one in the foreground is presumably drive 4. It is a later model (slimmer) but hold ths same capacity. So there is 800mb of disk space available.
These have removable disk packs , like this:

This is about 18 inched across, it weighed maybe 15 lb.
When not in use they lived in big cases. You can see a case on top of drive 4 (ignore the big box between the drive and the case, it for sure should not be there!). You can see the top of this pack inside drive 4. They were big and heavy. One of an operator’s regular tasks was to swap them in and out as the OS asked for them
Tape drives. Usually at least 2, for backup and journalling. One can be seen on the far right on one of these pictures.
Terminals
The primary way of interacting with the system for end users is via synchronous terminals. This is quite different from the async terminals connected to minis. Synchronous means
- when you type you are just interacting with the local terminal, it is buffering up what you enter and displaying it
- When you press ‘enter’ you are saying to the terminal ‘send this to the computer’
- The computers communication front end is polling all the attached terminals in sequence saying ‘if you got something you can send it now’. This means multiple terminals can be hooked to the same line
Async means
- when you type, the characters are sent to the computer immediately
- ‘enter’ just tells the sw running on the computer something
- The computer is always listening for characters arriving at any time from the terminal. This means a dedicated connection for each terminal
In one picture there is a VIP7700 terminal; Honeywell’s first generation sync terminal

As you can see this is a huge device. I had this exact model on my desk
Later they got smaller

These terminals also offered ‘forms mode’. This meant that the host could send a form to the terminal, describing where and what a user could type into it. A user would fill in the necessary fields (using tab to navigate between fields) and hit ‘enter’ when done. Could not find a Honeywell picture of a form but here is an IBM 3270 equivalent (the way they work is almost identical)

These features, synchronous operations and forms mode, all served to offload work from the main system. As you will see later this is very important.
Punch Cards!
The other form of ‘communication’ was via punch cards.

When I started the *only* way to enter programs and edit them was via cards.
The software release that came out after 2 years of the Level64 being available changed that, you could enter code via terminals (woohoo). Hence the arrival of a terminal on my desk.
Architecture.
The ISA (Instruction Set Architecture) was very advanced for its time (and even for now)
- 32 bit adddress and data
- 8 base address registers. 16 general purpose registers, 8 of which also worked as index registers. 4 64-bit floating point registers.
- Fully virtualized memory
- Privileged ring protection
Notable was the very high level instruction set (all microcoded), not only the usual CISC instructions but also all the process and thread management was in the hardware. For example to start a thread you set up a block like this

and then execute the ‘start’ instruction. (Please excuse the terrible image, this is a pic from my ‘interior decor’ handbook from 1976. The last document I have from then)
All mutex, semaphore, IPC operations were done in instructions too.
It also could have alternate microcode sets to allow emulation of other systems. One process could become, for example, a Honeywell 2000 system; the machine knew to use that alternate instruction set for that process.
How did such a small system manage
Having set the scene and described the system; how did it manage to run a whole company? These systems were designed for medium sized businesses
- savings and loan
- utilities
- manufacturing
- …
A typical business would use the system online (online means ‘not batch’, ie using terminals. There was no Internet then) for
- Order management
- inventory management
- customer service
If you had ordered 100 widgets and need to change the order to 200 then you would call the manufacturer up and talk to the sales dept. They all would have sync terminals on their desks and they would call up your order and change it. This would then also update the inventory and / or factory pipeline.
The tricks to getting the most out limited resources were
- offload as much work as possible from the CPU and RAM to other processors.
- High level instruction set means less OS code (no semaphores, mutexes, thread switching..)
- be extremely efficient in moving data around
- OS and online terminal support SW engine designed specifically to optimize these use cases
Thread management
A lot of operations normally performed by the OS, and hence taking up RAM space, were implemented in microcode. For example, all thread synchronization and communication was done via semaphores, either with or without messages.
A no message semaphore, basically an atomic counter with a thread queue, was used as a mutex. There were 2 instructions
- v-op: effectively a lock. The normal state was the counter = 1. v-op decremented the counter, if it was >= 0 then the calling thread continued. If the count was < 0 then the thread waited.
- p-op: unlock, the count was incremented by 1, if the count was then < 1 that meant there were threads waiting, the thread at the head of the queue was set ready, awaiting execution time.
Semaphores with messages were used for communaction. Very similar to no message except that the count started at 0, and there was a queue of messages, each 16 bytes, associated with it.
- v-opm : the was like a wait on a condition variable. If the count was 0 it means that there were no messages, the thread waited. If the count was > 0 that meant there were waiting messages, the message was loaded into g0 to g3 (32 bit general purpose registers) and the thread continued, the message was removed from the queue and the count was decremented.
- p-opm : post a message. The contents of g0-g3 were copied into the message queue, the count was incremented. If ant threads were waiting the first one would be woken up, the message loaded into g0-g3, count decremented,
Offloading I/O
The main CPU did not deal with any IO
- For terminals two systems were involved. Firstly the terminal itself dealt with buffering, forms, echoing, etc, as described above. Second, the terminals were connected to a separate dedicated processor responsible purely for talking to terminals.
- For disk drives there was a dedicated mass storage processor.
From the CPU and OS point of view peripherals had a very uniform interface.
- launch a ‘channel program’, this was a sequence of fixed size blocks that the IO controller understood. (A disk one might say, “select drive 1”, “seek to track 4 cylinder 2”, “read 40 bytes into this memory block”)
- The channel program had physical memory addresses as opposed to virtual ones so that the IO controller could DMA directly into the buffer. (There was an instruction to convert virtual address to a physical one)
- The channel program also included a semaphore address. Once the channel program completed the IO controller would notify that semaphore, posting a message with completion information.
TDS
TDS (Transaction Driven System) is the heart of the online support. All mainframe manufacturers had similar software (CICS on IBM).
The heart of the system worked like this

(This ignores all the set up etc., this is once an active terminal session is running for a user sitting at a terminal).
- The user enters info into a form and hits transmit, this is picked up by the terminal front end processor, in turns this posts an IO completion message to the semaphore that all the TDS threads are waiting on.
- The thread at the front of the waiting list picks up this message (not the actual screen message, just info about the completed IO), it contains information that identifies the user session.
- The TDS thread reads all the state of that session from its state file. This is one fast IO, the state file is basically a disk array, the state blocks are fixed size, the session id is the index. The most important piece of information in this state block is which TPR, (Transaction Processing Routine) should handle this message.
- The TPR code (almost always COBOL code) is written with a specific entry point, the TDS thread calls it . (The code is in the equivalent of a DLL so that it can be hot swapped if needed)
- This code will then make a system call to read the actual contents of the message, perform any IO to the data files it needs, do all the business logic,… and creates the reply.
- The last thing it does is to set any state it needs , this includes which TPR should handle the reply, plus whether or not to commit any IO that was performed. Often no commit is done because the TPR sent back a message showing the update pending and saying ‘Are you sure?’
- The reply is sent and the thread loops round to wait on the semaphore again.
Note that the IO both to the terminal and to the disks is very close. The path to load the session state is
- change one entry in a semi hard-coded (its created at TDS boot time continaing the drive ID, the disk offset and the block size) channel program to specify the index,
- one instruction launches the channel program
- The disk controller reads the relevant data via DMA
- The thread is woken up via a hardware IO completion notification. (Note that this doesnt happen at the main TDS semaphore, its an inline wait; the thread is suspended waiting for the IO to complete)
The same happens for the TPR customer IO, except the IO is not hard coded because there is more variety.
The net effect is that this is fast
BTW, COBOL code is compiled to bare metal just like C. The source code is very verbose but it generates lean, mean machine code.
Data IO
This is pre SQL, pre networked databases etc. The data sat on disks literally right next to the CPU, with fast DMA access to its RAM.
There were two common ways of organizing data. By far the most common was indexed files, IBM called it ISAM. ISAM – Wikipedia
- A file consisted of fixed length records (think classic SQL table). Say the file was ‘customers’, it would have customer id, name, address,…. The file system was not aware of the fields, it would just read the record into memory, the program interpreted say, bytes 0-9 as ID, 10-20 as name etc.
- There was a primary key, that had to be unique (again think classic SQL table).
- There could be several secondary keys, these did not have to be unique
- Operations were position, read, write, update, delete. You could position the logical read position using the keys.
A customer sales management system would have multiple files, one each for what would now be called tables. The logical relationships were all handled by the COBOL code.
For example, say we want to look up all the orders for a customer. W e know the customer ID (say it can from a terminal request)
- position the customer file to customer record using primary key, just to make sure that customer exists and we want to put customer name at the top of the listing screen. Read customer record.
- Now position the orders file to the first order for that customer, the order file has a secondary key of customerID + OrderDate. Now read forward from that point accumulating orders until the customer ID key is no longer for the customer we are interested in
Very Fast, A very short path from the executing code to the actual disks.
The other main storage form was CODASYL IDS. This is a network database (network in the sense of a linked set of records, not a communications network). I wont go into details, here is a link Integrated Data Store – Wikipedia, but I will qote the opening passage of the article
Integrated Data Store (IDS) was an early network database management system largely used by industry, known for its high performance
It was not as simple to use as a modern SQL database, much of the burden was placed on user code, but it was fast.
Data Consistency
Locking
When a record was read it was locked for write. When written it was locked for all access. Released at commit time.
The lock system could detect deadlocks and would kill and rollback one of the deadlocked TPRs and restart it. If that didnt work (ie a second deadlock occurred), it would kill all active TPRs , roll them all back and restart them one at a time.
Persistence
To ensure the ACID properties needed by systems this system (and all mainframes had their equivalents) there was a journaling system.
Before journal.
A before journal was an image of all the records modified by a transaction, once the TPR signaled that the work could be committed the IO buffers, if any, were flushed to disk and the before images discarded.
If Something went wrong during the processing, including the users backing out, a terminal disconnection.. then all the work done so far was discarded by reapplying the before images. AKA rolling back (just like SQL rollback today)
After Journal
In this case nothing was written to disk (like a WAL, write ahead log), everything was stored in a journal up until the commit happened, once the commit occurred then the data was copied from the after journal to the real data files. This also meant that the after journal had a record of all updates. This meant if there was a catastrophic failure then the days transactions could effectively be replayed.
I hope you found this interesting. At the time we did not think it remarkable that we could get such good performance out of such small (by todays standards) systems. To us these systems were monsters; leading edge technology.
You also can see that many ‘modern’ concepts, have in fact been around for a long time.
Leave a comment