The UNIX Time-Sharing System

Lecture Notes for CS 190
Spring 2015
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 problem 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
  • Files:
    • Uninterpreted collections of bytes
      • (vs. record-oriented, or multiple formats)
    • Variable-length files, can grow dynamically
      • (vs. pre-declared lengths)
    • Buffer cache in kernel, delayed writes
      • (vs. buffering at application level)
    • Simple read-write kernel calls:
      • write(fd, buffer, count)
      • Same APIs for sequential and random-access (implicit access position maintained by kernel)
      • Kernel copies to/from user buffers
      • Prevailing approach at the time:
        • 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.)
    • Hierarchical name structure:
      • Directories stored just like other files
        • (vs. different on-disk structures)
      • Hierarchical names
      • Working directory
    • Device-independent I/O:
      • Open just like files
        • (vs. different kernel calls to open each kind of device)
      • Same kernel calls for reading and writing
        • (vs. different control block structures for every device)
    • 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)
  • Processes:
    • Multiple processes/user!
      • (vs. only 1; previously, processes were extremely heavyweight)
    • 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
    • Standard mechanism for passing textual arguments to programs.
      • (vs. every command interprets entire command line: limits shell capabilities)
    • Programs inherit open files from their parents
      • Standard I/O: always read from descriptor 0, write to descriptor 1
      • (vs. start with clean slate: makes pipelines impossible)
  • Commands and shells:
    • Processes as building blocks
    • Shell is stand-alone program
      • (vs. built into kernel)
      • Different users can have different shells
      • Allows new shells to develop
    • Commands are just names of files
      • (vs. fixed built-in command set)
      • Simple path mechanism
      • Personal commands; run my programs just like system programs
    • One process for each command
    • Perform 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.