Computing History · Mainframe Era
Spooling: Turning Unit-Record Devices into Disk Files
How simultaneous peripheral operations on line decouple card reading from job execution, what a spool data set must contain, and why the queue becomes the scheduler.
A card reader reads a deck at a fixed rate determined by its mechanics, and a card is the unit of input. The resident monitor loop binds the reader to the running job: the job reads a card, processes it, reads the next. If the job spends a second computing per card, the reader is idle for a second per card, and the reader’s own capacity is wasted. If instead the reader could run ahead, filling storage with cards the job has not yet asked for, the reader could work continuously and the job would never wait.
The problem with running ahead is storage. A deck of two thousand cards at eighty bytes is 160 KB, which is more than a small machine has after the monitor and the job are loaded. The answer is to put the cards somewhere cheaper than main storage and slower than main storage but faster than the reader: disk. Spooling is that move, and it changes the structure of the system in ways that go well beyond input.
The three-way decoupling
Simultaneous peripheral operations on line is the expansion of the acronym, and the phrase names the essential property: the peripheral operates at the same time as the job, not between jobs.
Input spooling works as follows. A separate program, running as a system task, reads decks from the card reader into disk data sets, one data set per job, and records for each job a description: who submitted it, what resources it needs, what priority it has, and where its cards begin. The reader never stops, because it is never waiting for a job. It reads whatever deck is mounted next.
When the scheduler selects a job, the monitor does not read cards. The reads it issues instead are channel operations of the kind described in channel programs and CCWs, and that is what allows them to overlap with computation. It opens the job’s spool data set and reads from disk, which is an order of magnitude or two faster than a card reader and, more importantly, is a channel operation that can proceed while the job computes. The job’s own reads of its input are disk reads from a data set that was created before the job started.
Output spooling is the mirror image. The job writes its printed lines and punched cards to a disk data set rather than to a printer or punch. When the job ends, that data set becomes an entry in an output queue, and a separate writer task drains the queue to the actual printer at the printer’s own pace. The job does not wait for the printer; it ends as soon as its output is on disk.
The third decoupling is the one that matters most for throughput. Because input is on disk before the job runs, and output goes to disk after, the job’s entire life is disk-bound rather than unit-record-bound. The mechanical devices run in parallel with the processor, each at its own rate, and the queue of spooled work absorbs the difference between their rates.
What a spool data set must record
A spool file is not a plain concatenation of decks. It has to carry enough structure that a job can be reconstructed from it, and the structure has to survive a system restart.
Each spooled job needs an identifier that is unique within the system and stable across a restart. It needs the accounting identity of the submitter, since the charge for the job’s resources attaches to that identity. It needs the resource requirements that the scheduler will use: a class, a priority, a region size, a limit on CPU time, and a count of output lines. And it needs the data itself, plus enough markers that the boundary between the job’s control statements and its data is recoverable, because the monitor reads the control statements from the spool file the same way it read them from the reader.
The output side needs a different set of fields. A printed line is not a record in the job’s data set; it is a record with attributes, because the printer needs to know about carriage control characters, about page ejects, and about the forms mounted. A spooled output record therefore carries a carriage control byte alongside the text, and the writer task interprets that byte when it drives the printer. A print line of 132 characters plus one control byte is 133 bytes; the carriage control is what makes the first character of a line a command rather than text, and a writer that treats it as text prints a stray character at the start of every line.
The queue becomes the scheduler
The structural consequence of spooling is that the system now has a persistent queue of pending work, and a queue can be inspected, ordered, and held. This is the point at which the batch system acquires the features that operators actually used.
A job can be held before execution, which lets an operator examine the queue and decide what runs next. A job can be released, cancelled, or requeued. A job’s output can be held while its execution completes, so that output is printed only after a person has reviewed it, or after a specified time, or after the operator releases it. Priority is a field in the queue entry rather than a property of the deck’s position in a physical hopper.
The queue also makes job classes possible, which is the subject of job classes and initiator selection: a class is a partition of the queue, and an initiator is a consumer that takes work from one or more classes.
None of this is possible without a durable queue. A queue in main storage dies with the system, and a queue on cards cannot be reordered without physically moving cards. A queue on disk survives, and the operator’s console becomes a window onto it.
The cost: a second copy of everything
Spooling is not free, and the costs are specific.
Disk space is the obvious one. A job’s input exists on disk in full before it runs, and its output exists on disk in full before it prints. Both copies are transient but both are large. A system spooling output for a hundred jobs with a thousand lines each holds a hundred thousand lines, at 133 bytes, which is over thirteen megabytes of disk, and disk of the period is measured in hundreds of megabytes at most. The spool data sets therefore need management: a policy for how much space they may occupy, and a policy for what happens when the space fills. The usual answer is back pressure, in which the spool writer refuses new work until the printer drains, which converts an output bottleneck into an input bottleneck and is the correct behaviour but is not obvious.
Channel and CPU time are the second cost. Every card is now written to disk and later read back, so the data moves twice as many times as it did. The processor executes the spool writer’s instructions as well as the job’s. The saving is entirely in wall-clock overlap, not in total work, and the trade is favourable only because the unit-record devices are so much slower than the disk that the extra copy is invisible beside the wait it eliminates.
The third cost is complexity and a new class of failure. The spool data set is a shared file that several tasks write and read concurrently, so it needs allocation, locking, and recovery. A job that is cancelled mid-execution leaves a partial output data set that must be discarded without disturbing the rest of the queue. A system that fails while a job’s input is being spooled must be able to determine, at restart, which entries are complete and which are fragments. The usual design is a two-phase discipline: an entry is marked incomplete when created and marked complete only when the writer has finished with it, and the restart procedure discards everything not marked complete. Without that, a restart produces jobs made of the first half of one deck and the second half of another.
Precedents outside the mainframe
The same idea appears wherever a slow device meets a fast producer. A print daemon that accepts a file, stores it, and prints it later is a spooler, and the name of the standard printing system on Unix descends directly from the concept. The line printer daemon protocol defined for Unix systems in 1990 has the same three parts: a receiver that copies the file into a queue directory, a control file that records the owner and a sequence number, and a daemon that takes the oldest entry and drives the printer. The control file format differs, the failure handling is simpler, and the structure is identical.
The interesting difference is what happens to the queue’s ordering. On a batch mainframe, the queue is ordered by a scheduler that weighs class and priority, so the order is a policy decision made by the system. In a simple print queue, the order is first-in-first-out, and the policy question is reduced to whether a user may jump ahead. Both are answers to the same problem, and the choice between them is a statement about who owns the machine’s time.
A second descendant is the queue a mail system keeps between accepting a message and delivering it, which spools the message to disk before the first delivery attempt so that a failure does not lose it, and retries from the queue. The durability requirement is stronger there, since the queue must survive both a crash and a network outage, but the mechanism is the same: accept quickly, persist, and drain asynchronously.
What all of these share is a decision about where the boundary of a transaction lies. In a direct-output design, the transaction is complete when the device accepts the data. In a spooled design, the transaction is complete when the data is durable on disk, and the device’s acceptance is a separate, retryable event. That boundary is what allows a slow device to be removed from the critical path, and it is the reason spooling survived the disappearance of the card reader entirely.
References
- IBM System/360 Operating System: Job Management — IBM, GC28-6539
- IBM System/360 Operating System: Introduction — IBM, GC28-6534
- Operating System Concepts — Silberschatz, Galvin, Gagne; Wiley
- RFC 1179: Line Printer Daemon Protocol — IETF