The UNIX Time-Sharing System

Lecture Notes for CS 190
Winter 2019
John Ousterhout

  • 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
    • 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
      • Lengths often pre-declared
    • Unix:
      • Uninterpreted collection of bytes
      • Variable-length, can grow dynamically
  • File system APIs:
    • Before Unix:
      • Buffering at application level
      • No buffer cache in kernel
      • 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 to open each kind of device
      • Different control block structures for every device
    • Unix: device-independent I/O:
      • Open just like files
      • 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
  • Mountable file systems:
    • Combine multiple trees into one tree
    • Extremely simple mechanism
  • 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 stand-alone 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
  • One final example: what happens when a file is deleted while open?
    • Windows: don't allow deletion
    • Or, even worse, the file can be deleted, but name is still locked down
    • Unix: name is deleted and can be reused; file contents persist as long as file is open.