The UNIX Time-Sharing System

Classic 1974 paper on UNIX by Ritchie and Thompson.

Perhaps it seems like no big deal?

  • We take all of this stuff for granted today.
  • Must consider the prevailing state-of-the-art when Unix appeared.

Historical context for Unix:

  • Late 1960's
  • IBM OS/360: most ambitious commercial OS of its time
    • Announced in 1964
    • Designed to run on a family of computers
    • Ambitious feature set
    • Large project
    • Very late, very buggy
  • Multics: visionary OS: combined many untested technical ideas
    • Started in 1964
    • Collaboration between MIT, GE, Bell Labs
    • Timesharing
    • Single level store
    • Flexible and powerful sharing
    • Dynamic linking
    • Security (protection rings)
    • Hierarchical file system
    • First OS written in a high-level language
    • Also very late, very buggy, ultimately not very successful
  • Ritchie and Thompson both worked on Multics
  • Unix was in many ways a reaction to Multics

Key overall ideas:

  • Decompose system into a collection of components
    • Each component individually simple and obvious
    • Components plug together in obvious ways that are very powerful.
  • Eliminate distinctions: power comes from making everything look the same; find the least common denominator
    • Makes everything interchangeable

File data model:

  • Before Unix:
    • Record-oriented, multiple formats
    • File sizes often pre-declared
  • Unix:
    • Uninterpreted collection of bytes
    • Variable-length, can grow dynamically

File system APIs:

  • Before Unix:
    • No buffer cache in kernel
    • Buffering at application level
    • DMA between device and user space
    • Complex control blocks
    • Separate APIs for sequential and random access
    • Asynchronous I/O interface (start I/O, etc.)
  • Unix:
    • Buffer cache in kernel, delayed writes
    • Simple read-write kernel calls:
      • write(fd, start, count)
    • Same APIs for sequential and random-access (implicit access position maintained by kernel)
    • Kernel copies to/from user buffers

File names and directories

  • Before Unix:
    • Often just one directory per user
    • Directories represented differently on disk than files
  • Unix:
    • Hierarchical names
    • Working directory
    • Directories stored just like other files

Devices:

  • Before UNIX:
    • Different kernel calls for each kind of device
    • Different control block structures for every device
  • Unix: device-independent I/O:
    • Open just like files (devices have file names)
    • Same kernel calls for reading and writing
    • Drivers in the kernel translate general-purpose commands (read, write) to device-specific control sequences
    • ioctl for device-specific functions

Access control:

  • Simple 6-bit scheme (subsequently extended to 9 bits)
  • Too restrictive?
  • Compare to complex ACLs with inheritance, etc.
  • Set-user-id
    • (vs. implement all system functionality in the kernel)
    • Examples: login program, sudo

Processes:

  • Before Unix:

    • One process per user
    • Reuse process for different programs
    • No shells: command mechanism built into kernel (TOPS-10 example)
  • Unix: multiple processes per user

Passing arguments to processes:

  • Before Unix:
    • Every command interprets the entire command line
  • Unix: standard argc/argv mechanism for passing textual arguments to programs

Inheriting open files:

  • Before Unix: none
    • Each command opens its own files; pipelines impossible
  • Unix:
    • Open files inherited from parent
    • Standard conventions: always read from descriptor 0, write to descriptor 1
    • Enables pipelines

Fork-exec-wait distinction:

  • Fork-exec distinction allows parent to tailor environment for child (I/O redirection)
  • Exec-wait distinction allows parent to spawn multiple children (pipelines)
  • Controversial because of cost of copying address space

Shell programs:

  • Before Unix:
    • Command interpreter built into kernel
    • Fixed built-in command set
    • Commands are different from programs (e.g. "run foo", not "foo")
  • Unix:
    • Shell is user-level program
      • Different users can have different shells
      • Allows new shells to develop
    • Commands are just names of files
      • Simple path mechanism
      • Personal commands; run my programs just like system programs
    • Processes as building blocks
      • One process for each command
    • Shell performs computations on arguments (wildcard expansion), independent of command
    • I/O redirection, pipelines
      • Lots of small filters can be combined into powerful pipelines
      • Allowed another entire level of (de)composition
      • Would have been impossible without several other ideas:
        • Multiple processes
        • Fork-exec-wait, file descriptor inheritance
        • Device-independent I/O
    • Shell as programming language: can automate program invocation!
    • Shell scripts:
      • Use redirection on the shell itself
      • Another entirely new level of programming
    • Background processing

Unix changed the boundary between the kernel and user processes.

Things Unix moved into the kernel:

  • Buffer cache
  • Control blocks
  • Device differences

Things Unix moved out of the kernel:

  • Command-line interpreter
  • Setuid programs
  • Knowledge of file contents

Some of the Unix features had been discussed for earlier systems but were thought to be too complex or expensive.

  • Unix did all of this in about 40KBytes of object code
  • One tenth the size of Multics