The Evolution of The UNIX Time-Sharing System — Dannis M. Ritchie

Dennis Ritchie looks back at the creation of UNIX at Bell Labs with Ken Thompson and others. Then he introduces some of the most important parts of UNIX, including:

The file system

  1. I-list: An array of i-nodes, each of which represents a file. The i-nodes contain file metadata such as protection mode, type and size, and the physical location of the contents.
  2. Directory: A special file containing a list of filenames and the associated i-number.
  3. Special files are used to describe the device. A particular i-number corresponds to a particular device.

Process control mechanism

The general steps of the shell’s command execution are as follows:

  1. The shell reads commands from the terminal
  2. Create a new process
  3. The child process executes the command
  4. Meanwhile, the shell starts to wait until the child process finishes executing
  5. The shell goes back to the first step

It was interesting, and I simulated it with Elixir:

defmodule MF.PC do
  def loop do
    cmd = IO.read(:line)
    pid = self()

    spawn(fn ->
      exec(cmd, pid)
    end)

    wait()
  end

  defp exec(cmd, pid) do
    send(pid, {:done, String.upcase(cmd)})
  end

  defp wait do
    receive do
      {:done, msg} ->
        IO.puts(msg)
        loop()
    end
  end
end

Whatever is entered returns uppercase input.

iex(2)> MF.PC.loop
hello
HELLO

how are u
HOW ARE U

fine
FINE

good bye
GOOD BYE

Okay, boring, just to get an idea of the UNIX pattern of process messaging.

One interesting bug mentioned in the paper is that the chdir (CD) command is also executed as shown above. As a result, only the working directories of the child processes have changed, which has no effect on the shell itself. So they turn CD into a special command that only runs in the current process.

An interesting bug mentioned in the paper is that if a script file” comfile” has the following command:

ls
who

Then we execute in the shell:

sh comfile >output

The expected result is that the execution results of ls and who are written to the output file in turn. But in fact, since UNIX originally kept the IO pointer to the file in the process that did the “open file”, the main shell process, and the subshell that we opened with the sh command did the writing, so the IO pointer to the file never changed. The WHO output overwrites the LS output. To fix this, they added a table to the system that holds IO Pointers for all open files.

As an aside, it occurred to me that Elixir does not specify where to write a file, but it does:

iex(4)> :file.read_file "comfile" {:ok, "ls\nwho\n"} iex(5)> {:ok, device} = :file.open "comfile", [:raw, :write] {:ok, {{: file_descriptor, : prim_file, % handle: # Reference < 0.1011150086.1394212882.243351 >, the owner: # PID < 0.110.0 >, r_ahead_size: 0, r_buffer: # Reference < 0.1011150086.1394212865.243949 >}}} iex (6) > : file. Pwrite (device, 1, 'hello') :ok iex(7)> :file.read_file "comfile" {:ok, <<0, 104, 101, 108, 108, 111>>} iex(8)> <<104, 101, 108, 108, 111>> "hello"

Finally is also mentioned about the pipe operator | and the origin of the C language.