How Software Learned to Serve Programs: The Operating System

Planted 02026-08-01

Who fulfills the promises every program assumes?

How can software learn to serve every program?

The compiler has completed its work.

It can translate a Jack program into VM commands. The VM translator can turn those commands into Hack assembly. The assembler can recover binary instructions. The CPU can execute them.

Yet the generated program repeatedly asks for abilities no instruction provides:

call Math.multiply
call Memory.alloc
call String.new
call Screen.drawRectangle
call Keyboard.readChar
call Output.printInt

The names look like capabilities.

At first, they are only requests.

The hardware does not know how to allocate an object. The screen memory does not know how to draw a rectangle. The keyboard register does not know how to collect a line of text. The ALU does not contain a square-root operation.

Some software must fulfill each promise.

If every application supplied its own version, every programmer would have to rebuild the same small world before solving a new problem inside it.

Instead, the machine can provide shared services.

That collection of services is the final layer in our computer.

It is the operating system.

1. The program that would have to build its own world

Imagine writing a game without any shared software beneath it.

Before drawing the first object, the program would need to know how screen coordinates become memory addresses and which bit within a sixteen-bit word controls each pixel.

Before displaying a score, it would need a bitmap for every digit and a rule for advancing a cursor.

Before reading a player’s name, it would need to detect key presses, wait for releases, handle backspace, recognize the Enter key, and store an unknown number of characters.

Before creating a ball or paddle, it would need to find unused memory without overwriting another object.

Before multiplying two numbers, it would need to recreate multiplication from operations the Hack platform possesses.

None of those tasks is unique to the game.

Another program will need them tomorrow.

Without a shared layer, the machine’s apparent generality collapses into repeated preparation:

Application idea

Rebuild common machinery

Finally begin the application

The problem resembles one that appeared around the earliest electronic computers.

The machines were fast once a calculation began.

Getting them from one useful calculation to the next was another matter.

2. The people operating the computer

The first computer operator was not software.

It was a person.

Programmers prepared instructions and data on cards or paper tape. Operators brought jobs into the machine room, loaded decks or mounted tapes, configured equipment, started execution, watched for failure, collected output, and prepared the system for the next job.

The job could finish in moments.

The transition surrounding it could take much longer.

An expensive computer might wait while someone:

removed one tape;
mounted another;
loaded a program;
selected an input device;
started the job;
or decided what should run next.

The human operator supplied sequence and coordination that the computer did not yet supply for itself.

As demand increased, computer centers began organizing work into batches. Programmers surrendered their jobs to professional operators. The operators arranged many jobs together so the machine could process them in sequence.

This improved the workflow.

It did not eliminate the repeated human decisions between programs.

Could software take over part of the operating?

3. The production line beside the IBM 704

Robert Patrick had encountered professional operators and batches of work while programming early machines.

At General Motors Research, he helped prepare for the arrival of an IBM 704. The machine was capable of performing calculations at electronic speed, but its useful output still depended on the larger production process around it.

Patrick looked at that process through an unexpected lens.

General Motors already knew how to study the flow of work.

Automobile production required many operations to occur in a coordinated order. Industrial planners used diagrams associated with Henry Gantt to make dependencies and timing visible: which activity must precede another, which could overlap, and where delay would leave expensive capacity unused.

Patrick adapted those production-planning ideas to computing.

Instead of asking only how instructions flowed through a processor, he asked how complete jobs flowed through the installation.

Programmer prepares work

Cards become tape

Machine receives a batch

Programs run in sequence

Results leave for printing

An idea used to coordinate automobiles was becoming a way to coordinate programs.

4. The stranger at SHARE

IBM’s scientific-computing customers had formed a user organization called SHARE.

Its members exchanged programs, experience, and proposals. A problem solved at one installation might matter to many others using similar machines.

At a SHARE meeting in Boston, Patrick presented his operating plan.

Afterward, Owen Mock of North American Aviation approached him.

Mock’s group had been thinking along the same lines.

The two organizations decided to collaborate. Patrick and Mock worked on what would now be called the architecture, while programmers at General Motors and North American Aviation implemented different portions and exchanged the completed work.

The result became known as the GM-NAA I/O System for the IBM 704.

It was one of the earliest batch operating systems and is often identified as the first operating system for that machine.

Its purpose was modest compared with a modern operating system.

It could move from one program to the next automatically and provide shared input/output routines. Jobs arrived according to agreed conventions. Software read the control information and carried the batch forward.

The machine no longer needed a person to reconstruct every transition.

Patrick later recalled that their installation completed roughly four or five times as much work each day. Programmers could remain at their desks instead of repeatedly carrying decks into the machine room.

Software had absorbed part of the operator’s role.

5. When a repeated action becomes a service

The GM-NAA system did not arise because someone began with a timeless definition of an operating system.

It arose because a repeated human activity had become a bottleneck.

The pattern is broader than batch processing:

Many programs need the same capability.

One shared implementation supplies it.

Programs rely on a stable way to request it.

A service changes the division of labor.

Without one:

Every application must know the mechanism.

With one:

Applications know the request.
Shared software knows the mechanism.

Operating systems would grow far beyond early job sequencing. They would manage processors, memory, devices, files, users, protection, networks, and many programs at once.

Our Hack computer does not need that entire history compressed into one project.

It needs a smaller service boundary.

6. The operating system this machine actually has

The Jack operating system is not a miniature Windows, macOS, or Linux.

It has no file system.

It has no process scheduler.

It does not isolate several users or protect one application from another.

It has no network, shell, desktop, or collection of windows.

It is a set of eight Jack classes:

Math
Memory
Array
String
Screen
Output
Keyboard
Sys

Together they close gaps between the Hack hardware and the Jack language.

The distinction matters.

Calling these classes an operating system does not mean every operating system is merely a library. It means that, for this deliberately simple machine, the essential operating-system lesson is the creation of shared software services above hardware.

The Jack language appears able to multiply, create objects, manipulate strings, draw shapes, display characters, and receive input because the OS fulfills those apparent abilities.

The OS is therefore also an extension of the language.

7. An API is a promise written in advance

Each OS class exposes an application programming interface, or API.

The API describes what a program may request without requiring the program to know how the request will be fulfilled.

For example:

function int multiply(int x, int y)

says, in effect:

Provide two integers.
Receive their product.

It does not require the caller to know whether multiplication uses repeated addition, shifting, a table, or a hardware instruction.

Similarly:

function Array new(int size)

promises a usable array of the requested size without exposing the allocator’s search through memory.

An API is another abstraction boundary.

Above it, applications trust names and contracts.

Below it, implementations manage addresses, loops, bit patterns, and hardware conventions.

The operating system is not made from a different substance than the application.

Both are Jack programs translated into VM commands.

Their difference is the role they agree to play.

8. Math supplies operations the hardware forgot

The Hack ALU can add and subtract.

It cannot directly multiply, divide, or calculate a square root.

Yet Jack permits:

let area = width * height;

The compiler translated * into:

call Math.multiply 2

Now Math.multiply must earn the language feature.

A simple implementation could add width repeatedly. A better one can examine the bits of a multiplier, double partial values, and accumulate only the contributions that matter.

If this bit of y is 1,
include the corresponding doubled value of x.

Division can repeatedly determine which doubled portions of the divisor fit inside the remaining dividend. Square root can search for the largest value whose square does not exceed the input.

The precise algorithm may change.

The API promise remains:

Ask for the mathematical result.
The service will reconstruct it from simpler operations.

The OS can make the language more capable than any single processor instruction.

9. Memory keeps promises from overlapping

A constructor asks:

call Memory.alloc 1

The argument specifies how many words the new object requires.

The request sounds simple.

The allocator must answer several questions:

Which region is unused?
Is it large enough?
If it is larger than needed, can it be divided?
How will the remaining free region be remembered?
What happens when the object is no longer needed?

Memory allocation is a promise about noninterference:

This region is yours until it is returned.
I will not promise the same words to another object at the same time.

The allocator can maintain a list of free regions in the heap. Each available block records its size and a connection to another free block. To satisfy a request, the allocator searches for a suitable region, removes or divides it, and returns the address available to the caller.

Memory.deAlloc returns a region so it may serve a later request.

The contents of RAM have not acquired ownership.

The OS maintains the agreements that make ownership appear real.

10. An array is memory with one added promise

The Array class appears to provide a new kind of object.

Its implementation can be strikingly small.

Creating an array means requesting a contiguous region from Memory.alloc.

Disposing of it means returning that region to Memory.deAlloc.

Indexing is already handled by compiler-generated address arithmetic:

base address + index

An array is therefore not a special material inside the machine.

It is ordinary memory plus two agreements:

These words are contiguous.
Interpret this address as their beginning.

One OS service can be built almost entirely from another.

11. A string becomes a maintained object

The source program writes:

"order complete"

The compiler responds by calling String.new and String.appendChar.

The String class must decide how characters occupy memory.

A string needs to preserve more than character values. It may need:

maximum capacity;
current length;
the ordered characters;
and rules for appending, erasing, reading, and replacing them.

appendChar must reject or report an attempt to exceed capacity. eraseLastChar must shorten the active content without confusing capacity with length. intValue must interpret digit characters as a number. setInt must perform the reverse transformation.

The quotation marks made the source look static.

The operating system maintains a living data structure.

The compiler created the calls.

The OS gives those calls memory, invariants, and behavior.

12. Screen turns geometry into addresses

The Hack screen is memory mapped.

Writing bits into a particular region of RAM changes pixels on the display.

That hardware agreement is powerful but inconvenient.

A program wants to say:

do Screen.drawRectangle(x1, y1, x2, y2);

The screen memory accepts only addresses, words, and bits.

The Screen class must bridge the descriptions.

For one pixel, it determines:

Which row contains the coordinate?
Which sixteen-pixel word contains the column?
Which bit inside that word controls this pixel?
Should the bit be set or cleared?

Lines, rectangles, and circles require further algorithms. A line crosses discrete pixels even when its mathematical slope does not align neatly with rows and columns. A circle must approximate a continuous curve using a finite grid.

Geometry becomes address arithmetic and bit masks.

The screen does not learn what a rectangle is.

The OS preserves the relationship between the geometric request and the memory pattern that makes the shape appear.

13. Output rebuilds symbols from pixels

Even with drawing services, the machine cannot display a letter.

Pixels know only whether they are on or off.

Output.printChar needs a shape for each supported character. The OS can store a small bitmap font: patterns indicating which pixels should be filled for A, B, 7, ?, and the rest of the character set.

Printing a character becomes:

Find the character’s bitmap.
Draw its rows at the cursor position.
Advance the cursor.

Printing a string repeats the process for every character.

Printing an integer first turns the number into decimal characters, then draws those characters.

Newline and backspace are agreements about cursor movement, not abilities supplied by the screen hardware.

The path runs in both directions:

Human symbol

Character code

Bitmap pattern

Screen-memory bits

Visible pixels

The OS rebuilds writing from light.

14. Keyboard turns a changing register into a conversation

The Hack keyboard is also memory mapped.

One location reports the currently pressed key. If no key is pressed, it reports zero.

That is enough to sense a physical event.

It is not yet enough to read a line.

Keyboard.keyPressed can return the current value directly.

Keyboard.readChar needs a protocol:

Wait until a key is pressed.
Remember its code.
Wait until the key is released.
Echo the character.
Return the code.

Waiting for release matters. Without it, one physical press observed over many processor cycles might appear to be many characters.

readLine repeats readChar, handles backspace, stops at newline, and accumulates the result in a string.

readInt asks the string service to interpret the characters numerically.

The input classes reveal another ladder:

Electrical key state

One key event

Character

Line of text

Number meaningful to a program

The operating system turns a register into interaction.

15. Sys begins and ends the software world

Every program needs a first call.

The VM translator created bootstrap code that calls Sys.init.

The Sys class receives that responsibility.

Its initialization routine prepares the OS services and calls the application’s entry point:

Initialize shared modules.
Call Main.main.
If Main.main returns, halt.

Sys.wait creates a delay by spending a calculated amount of time in a loop.

Sys.error reports an agreed error code and stops ordinary progress.

Sys.halt prevents execution from wandering into unrelated memory after the program is finished.

The class does not manage multiple processes or choose among competing users.

Its role is smaller and still fundamental.

It gives the software stack an official beginning, delay, failure, and end.

16. The operating system built from its own services

The Jack OS is written in Jack.

That can sound circular.

How can the language rely on an operating system that is itself written in the language?

The layers do not all depend on one another in the same way.

The compiler can translate Jack source without executing the translated program on the Hack computer. The resulting VM files can contain calls that will be resolved only when the program runs.

Some OS classes build on other OS classes:

Array uses Memory.
String uses Memory and Math.
Output uses Screen.
Keyboard may use Output and String.
Sys initializes the collection.

At the bottom of the service layer are operations the VM and hardware already provide: arithmetic, branching, memory access, and memory-mapped input and output.

The OS grows upward from those foundations.

During development, one class can be tested beside supplied implementations of the others. A working service temporarily supports the service still being built.

Bootstrapping does not require every layer to spring into existence at once.

It requires one dependable foothold at a time.

17. The line that crosses the entire machine

Consider:

do Output.printString("Hello");

The single line activates the entire structure we have built.

The tokenizer finds its names and symbols.

The parser recognizes a string constant inside a subroutine call inside a do statement.

The compiler generates calls that construct the string and pass it to Output.printString.

The VM translator implements those calls using stack frames, arguments, return addresses, and Hack assembly.

The assembler converts symbols and mnemonics into binary instructions.

The CPU fetches, decodes, and executes each instruction.

The String service maintains the ordered characters.

The Output service finds the bitmap for each character.

The Screen service turns the bitmap into memory writes.

Screen hardware interprets those bits as pixels.

Logic gates preserve every low-level relationship needed along the way.

Human-facing statement

Language structure

Compiler-generated VM commands

Operating-system services

Assembly and machine instructions

CPU, memory, and gates

Five visible letters

No layer contains the whole event.

The result exists because the promises compose.

18. What an operating system really is

An operating system is often pictured as the software that appears when a computer starts: a desktop, login screen, taskbar, dock, or command prompt.

Those are possible interfaces to an operating system.

They are not its essence.

At the scale of the Hack computer, the operating system is shared software that gives stable names to abilities more complicated than the hardware directly provides.

Its promise is:

Ask for this service through the agreed interface.
I will absorb the mechanism required below.

GM-NAA I/O absorbed repeated work surrounding batches and input/output so operators and programmers did not have to coordinate every transition by hand.

The Jack OS absorbs repeated work surrounding mathematics, storage, data structures, graphics, text, input, and startup so applications do not have to reconstruct those mechanisms.

The systems differ enormously in purpose and scale.

They share a movement of responsibility:

Work repeated around every program

Software maintained for all programs

An operating system makes the computer available through promises larger than its instructions.

19. From NAND to Tetris

We began with a question:

How can a computer add numbers when none of its parts knows what a number is?

The answer was never one ingenious component hidden at the center.

It was a succession of dependable relationships.

A NAND gate preserves a truth rule.

Joined gates preserve arithmetic and selection.

Memory preserves a pattern across time.

The clock creates agreed moments of change.

The CPU turns stored instructions into coordinated motion.

The assembler preserves operations while removing human-facing names.

The virtual machine gives software a stable imagined computer.

The call stack preserves unfinished worlds.

A high-level language lets people omit machine bookkeeping.

The parser recovers structure from text.

The code generator turns structure into executable sequence.

The operating system fulfills the shared promises programs rely on.

At the bottom, the machine still distinguishes high voltage from low voltage.

At the top, a person presses a key and moves a paddle toward a falling ball.

No transistor knows the rules of Pong.

No gate recognizes a letter.

No register understands an object.

No stack frame knows that it preserves a game.

No operating-system function knows that the player is trying to win.

The meanings do not live inside the parts.

They emerge because each part keeps a smaller promise reliably enough for the next level to trust it.

This is what abstraction has been doing throughout the journey.

It does not erase the world below.

It creates a dependable boundary above it.

Below the boundary, detail remains.

Above it, a person can act as though a simpler story were true:

Draw this shape.
Read this key.
Call this method.
Move this paddle.
Play this game.

The computer did not become powerful because its smallest parts became intelligent.

It became powerful because people learned how to make simple parts dependable, how to compose their promises, and where to place each new burden so the layer above could think about something else.

From one NAND gate, they built a machine.

From the machine, they built a language.

From the language, they built a world someone could play.

That is how software learned to serve programs.

That is how NAND became Tetris.