Computing History · Lessons from Failures

The Floppy Sector Interface as a Lossy Abstraction

How a disk controller invented to address one track at a time became the universal disk interface, and why the geometry, translation and sector-numbering decisions made for it still surface in firmware and partition tables.

The first hard disk controller used in personal computers did not address sectors. It addressed cylinders, heads and sectors in a form the controller’s own chip understood, and the BIOS that wrapped it exposed three 8-bit fields through a software interrupt. That interface was adequate for a ten-megabyte drive. It was still the only way to reach the boot device four orders of magnitude later, and the accumulated translation layers between the register values and the physical media are a compact record of what happens when a convenience abstraction is promoted to a standard.

One chip, one track, one command

The NEC µPD765 floppy disk controller, used in the original IBM PC and in many machines that copied the architecture, executes commands addressed by physical location. A read command carries four parameters: the cylinder number (0 to 79 in the PC’s 40-track double-sided drives, with higher values on later drives), the head number (0 or 1), the sector number as recorded in the sector’s own address field (1 to 9 for a 360 KB format, 1 to 18 for a 1.2 MB format), and the sector size code. The controller seeks, waits for the index hole, reads each sector’s header, and transfers the matching sectors into memory.

The chip knows nothing about any other disk. Its state is a command register, a status register, a data register and a set of result bytes. It can read, write, format and seek, and it drives the drive’s step and direction lines itself. This is a design for a single physical device on a single cable with no notion of multiple logical volumes, and it is why the sector number is a physical fact: the format command writes that number into the gap before each sector’s data, and reading a sector number that was never formatted returns an error, not a remap.

A 360 KB floppy holds 40 cylinders times two heads times nine sectors of 512 bytes, giving 360 KB when the 1 KB of the boot sector is counted separately from the 368,640 bytes of the data area. These numbers are not arbitrary; they are set by the format, and every piece of software that hard-coded them was recording a property of one medium.

What the BIOS added

The PC BIOS wrapped the controller in interrupt 13h, which took the cylinder in one register, and the sector and head packed into another. Two 8-bit registers suffice for cylinders up to 1023 and sector numbers up to 63, and in the floppy era the free bits were used for the head count and the upper cylinder bits as the drive sizes grew. The interrupt was intended for boot code and for callers that could not afford a driver, and it is specified as a software interface rather than a hardware one, which is exactly why it survived: a BIOS implementation could change the hardware underneath without any caller noticing.

That property cut both ways. Because the BIOS interface was stable, no other interface got written. Application software called interrupt 13h to read and write disks, DOS provided no driver model that would have let it do otherwise, and the interface’s limits therefore became binding on every larger disk that followed.

The 1024-cylinder wall

The register layout permits 1024 cylinders, which at the addressing granularity of the time was about 8 GB. In the mid-1990s disks crossed 8 GB and the interface could not name blocks beyond that point. The response, present in the BIOS’s extended interrupt 13h services, was to add a second style of call that takes a 64-bit logical block address in a parameter block. A drive that supports the extension can be addressed in blocks with no geometry at all.

The problem was the boot path. The initial program load reads the first sector of the boot device using the old call, because the extended services might not be present. That single sector, 512 bytes, has to decide whether the extension exists, load more of the boot loader, and, until it has done so, can only name locations within the first 1024 cylinders. The convention that grew up was to place the operating system kernel below that boundary, which is why partition tables from that period often show a small partition at the start and why some boot configurations still reserve a region near the beginning of the disk.

The extension also fixed the encoding of the old call rather than removing it. Where the old interface packed cylinder, head and sector into fields of specific widths, the extension’s device parameter packet describes the geometry with 16-bit cylinders, 8-bit heads and sectors. A drive reporting more than 1024 cylinders reports a value that cannot be used by the old call, and the BIOS’s response was to report a smaller geometry than the drive has, with the excess folded into head or sector counts. Every later reader of the partition table must know which convention the table’s author used.

Why integrity checking was not in the controller

The second lossy abstraction is the sector itself. Each sector on a floppy is preceded by an address mark, a cylinder/head/sector field, a size code and an 8-bit CRC over those fields, and followed by a data field with its own 16-bit CRC. The controller verifies both CRCs and reports a data error if either fails. A bad sector on a floppy is therefore visible to software, which is the origin of the read-retry machinery in DOS and of the widespread practice of writing a file twice.

Hard disks added error correction, and the controller’s interface returned a status code rather than the uncorrected data. But the ATA interface’s design goal was device transparency: the host sends a command and reads a sector’s worth of bytes from a FIFO, and the controller performs retries, remapping and error correction internally. A sector that could not be corrected produces an error status on the command, and the operating system’s response is generally to retry the command, not to inspect what was on the medium. The abstraction is honest about failure but opaque about cause, which means a drive that remaps a bad sector silently is indistinguishable from a drive that did not have one. Reading the SMART attribute set was the retrofit that made the failure record visible, and it is a separate protocol precisely because the data path had no room for it.

Linear block addressing and the death of geometry

ATA eventually adopted logical block addressing as the native addressing mode, with 28-bit LBA on the older interface and 48-bit LBA on later revisions. Once the drive exposes a linear count of 512-byte blocks, the geometry it reports is a courtesy, and the host’s translation becomes an identity mapping. The 2 048-sector offset that begins the first partition on most modern disks is a product of that transition: aligning the first partition to a megabyte boundary aligns it to the erase block size of the flash-based devices that the interface now mostly speaks to, and the number is a multiple of the 63-sector tracks of the old geometry by construction.

The intermediate period, when BIOS translation, drive geometry and operating system assumptions all disagreed, produced the most destructive failure mode of the era. A drive whose true size exceeded what the BIOS’s translation reported appeared to the operating system to be smaller than it was. Repartitioning with a tool that knew the real size could write a table describing a disk larger than the BIOS’s view, which the boot code would then interpret with a different geometry, and on the next boot the table would appear to describe a disk with overlapping or out-of-range partitions. Every write that followed went to the wrong place.

The file allocation layer stacked above it

The abstraction cost was compounded by the layer directly above. The FAT file system, designed for floppies and extended to hard disks, records allocation as a linked chain of entries indexed by cluster number, with each entry holding the next cluster or an end-of-chain marker. Directory entries store the first cluster, the file’s byte length, and the modification time in a packed format that the 1981 PC’s clock resolution imposed.

The chain means a file’s allocation cannot be validated without walking it. A corrupted entry pointing backwards creates a loop that a naive integrity check never terminates; pointing forward into another file’s chain silently splices two files together and corrupts both on write. Later versions added a copy of the table, but the two copies were only checked at mount and were not ordered relative to each other, so a crash between writes could leave a divergence that no rule resolved. This is the failure class that journaling file systems were built to remove, and the two copies were the pre-journal answer to it. A duplicate of a mutable structure without an update ordering discipline gives the illusion of redundancy: both copies are individually plausible and jointly inconsistent.

A file system that records allocation as a chain, in a table that is itself a file system structure, also cannot be checked from its own data alone. The check program has to distinguish a chain owned by a directory entry from an orphan chain reachable only through the table, and that distinction is not recorded anywhere. Scanning the whole table for orphans is why the documentation insists the volume be unmounted first.

What the layer boundary teaches

The floppy controller’s command set was a correct design for a device that a human physically inserted and removed. Making it the boot path for every later device required no decision by anyone; it happened because the firmware interface was the one thing that could not change without invalidating every boot flow in the field. The firmware layer that inherited the obligation is the subject of The IBM PC BIOS Standard and the Cost of Freezing a Firmware Interface, and the equivalent freeze at the process level, where an interface also had to describe an activity it could not observe, is traced in Threads, Scheduler Activations and the Failed Promise of User-Level Scheduling.

The resulting stack has four descriptions of the same storage: the drive’s internal physical layout, the translated logical geometry the firmware reports, the linear block count the block layer sees, and the partition table’s own record of extents in whatever scheme its author used. The documented incidents in this area are all cases of one layer’s description being interpreted by another layer that assumed it held the authority. This is the same mechanism, at a different layer, as the version-token mismatch in Optimistic Concurrency and the Conflict Check That Arrives Too Late, where two parties each held a locally correct view and neither owned the union.

A frame format specified with a slot for flexibility that nothing fills is the same problem on a different bus; Ancillary Data in FDDI and the Unused Flexibility of a Link Layer works through that case. A storage interface survives by being the last thing anyone changes, and the cost of that survival is that every later device must pretend to be a floppy in at least the parts of itself that the boot path touches.

Reference to the standard, and what it settles

The ATA specifications and the T13’s enhanced BIOS service documents define the register layouts and the parameter structure, so the ambiguity about what a field means at the wire level is small. What they do not define is the mapping from the partition table’s cylinder/head/sector triples on a disk whose reported geometry is fiction. That mapping was never specified by anyone, and its absence is the reason a disk moved between two machines of the era could be unreadable without any component reporting an error. A specification can be exact about a register and silent about the layer above it, and the silence propagates.

References

  1. NEC µPD765A/µPD765B Programmable Floppy Disk Controller data sheet — NEC Electronics
  2. IBM PC Technical Reference Manual — IBM, 1981
  3. Enhanced BIOS Services for Disk Drives (INT 13h Extensions) — T13 Technical Committee, NCTITS 3.0