xv6-cs450/web/l14.txt
2008-09-03 04:50:04 +00:00

247 lines
9.8 KiB
Text

Why am I lecturing about Multics?
Origin of many ideas in today's OSes
Motivated UNIX design (often in opposition)
Motivated x86 VM design
This lecture is really "how Intel intended x86 segments to be used"
Multics background
design started in 1965
very few interactive time-shared systems then: CTSS
design first, then implementation
system stable by 1969
so pre-dates UNIX, which started in 1969
ambitious, many years, many programmers, MIT+GE+BTL
Multics high-level goals
many users on same machine: "time sharing"
perhaps commercial services sharing the machine too
remote terminal access (but no recognizable data networks: wired or phone)
persistent reliable file system
encourage interaction between users
support joint projects that share data &c
control access to data that should not be shared
Most interesting aspect of design: memory system
idea: eliminate memory / file distinction
file i/o uses LD / ST instructions
no difference between memory and disk files
just jump to start of file to run program
enhances sharing: no more copying files to private memory
this seems like a really neat simplification!
GE 645 physical memory system
24-bit phys addresses
36-bit words
so up to 75 megabytes of physical memory!!!
but no-one could afford more than about a megabyte
[per-process state]
DBR
DS, SDW (== address space)
KST
stack segment
per-segment linkage segments
[global state]
segment content pages
per-segment page tables
per-segment branch in directory segment
AST
645 segments (simplified for now, no paging or rings)
descriptor base register (DBR) holds phy addr of descriptor segment (DS)
DS is an array of segment descriptor words (SDW)
SDW: phys addr, length, r/w/x, present
CPU has pairs of registers: 18 bit offset, 18 bit segment #
five pairs (PC, arguments, base, linkage, stack)
early Multics limited each segment to 2^16 words
thus there are lots of them, intended to correspond to program modules
note: cannot directly address phys mem (18 vs 24)
645 segments are a lot like the x86!
645 paging
DBR and SDW actually contain phy addr of 64-entry page table
each page is 1024 words
PTE holds phys addr and present flag
no permission bits, so you really need to use the segments, not like JOS
no per-process page table, only per-segment
so all processes using a segment share its page table and phys storage
makes sense assuming segments tend to be shared
paging environment doesn't change on process switch
Multics processes
each process has its own DS
Multics switches DBR on context switch
different processes typically have different number for same segment
how to use segments to unify memory and file system?
don't want to have to use 18-bit seg numbers as file names
we want to write programs using symbolic names
names should be hierarchical (for users)
so users can have directories and sub-directories
and path names
Multics file system
tree structure, directories and files
each file and directory is a segment
dir seg holds array of "branches"
name, length, ACL, array of block #s, "active"
unique ROOT directory
path names: ROOT > A > B
note there are no inodes, thus no i-numbers
so "real name" for a file is the complete path name
o/s tables have path name where unix would have i-number
presumably makes renaming and removing active files awkward
no hard links
how does a program refer to a different segment?
inter-segment variables contain symbolic segment name
A$E refers to segment A, variable/function E
what happens when segment B calls function A$E(1, 2, 3)?
when compiling B:
compiler actually generates *two* segments
one holds B's instructions
one holds B's linkage information
initial linkage entry:
name of segment e.g. "A"
name of symbol e.g. "E"
valid flag
CALL instruction is indirect through entry i of linkage segment
compiler marks entry i invalid
[storage for strings "A" and "E" really in segment B, not linkage seg]
when a process is executing B:
two segments in DS: B and a *copy* of B's linkage segment
CPU linkage register always points to current segment's linkage segment
call A$E is really call indirect via linkage[i]
faults because linkage[i] is invalid
o/s fault handler
looks up segment name for i ("A")
search path in file system for segment "A" (cwd, library dirs)
if not already in use by some process (branch active flag and AST knows):
allocate page table and pages
read segment A into memory
if not already in use by *this* process (KST knows):
find free SDW j in process DS, make it refer to A's page table
set up r/w/x based on process's user and file ACL
also set up copy of A's linkage segment
search A's symbol table for "E"
linkage[i] := j / address(E)
restart B
now the CALL works via linkage[i]
and subsequent calls are fast
how does A get the correct linkage register?
the right value cannot be embedded in A, since shared among processes
so CALL actually goes to instructions in A's linkage segment
load current seg# into linkage register, jump into A
one set of these per procedure in A
all memory / file references work this way
as if pointers were really symbolic names
segment # is really a transparent optimization
linking is "dynamic"
programs contain symbolic references
resolved only as needed -- if/when executed
code is shared among processes
was program data shared?
probably most variables not shared (on stack, in private segments)
maybe a DB would share a data segment, w/ synchronization
file data:
probably one at a time (locks) for read/write
read-only is easy to share
filesystem / segment implications
programs start slowly due to dynamic linking
creat(), unlink(), &c are outside of this model
store beyond end extends a segment (== appends to a file)
no need for buffer cache! no need to copy into user space!
but no buffer cache => ad-hoc caches e.g. active segment table
when are dirty segments written back to disk?
only in page eviction algorithm, when free pages are low
database careful ordered writes? e.g. log before data blocks?
I don't know, probably separate flush system calls
how does shell work?
you type a program name
the shell just CALLs that program, as a segment!
dynamic linking finds program segment and any library segments it needs
the program eventually returns, e.g. with RET
all this happened inside the shell process's address space
no fork, no exec
buggy program can crash the shell! e.g. scribble on stack
process creation was too slow to give each program its own process
how valuable is the sharing provided by segment machinery?
is it critical to users sharing information?
or is it just there to save memory and copying?
how does the kernel fit into all this?
kernel is a bunch of code modules in segments (in file system)
a process dynamically loads in the kernel segments that it uses
so kernel segments have different numbers in different processes
a little different from separate kernel "program" in JOS or xv6
kernel shares process's segment# address space
thus easy to interpret seg #s in system call arguments
kernel segment ACLs in file system restrict write
so mapped non-writeable into processes
how to call the kernel?
very similar to the Intel x86
8 rings. users at 4. core kernel at 0.
CPU knows current execution level
SDW has max read/write/execute levels
call gate: lowers ring level, but only at designated entry
stack per ring, incoming call switches stacks
inner ring can always read arguments, write results
problem: checking validity of arguments to system calls
don't want user to trick kernel into reading/writing the wrong segment
you have this problem in JOS too
later Multics CPUs had hardware to check argument references
are Multics rings a general-purpose protected subsystem facility?
example: protected game implementation
protected so that users cannot cheat
put game's code and data in ring 3
BUT what if I don't trust the author?
or if i've already put some other subsystem in ring 3?
a ring has full power over itself and outer rings: you must trust
today: user/kernel, server processes and IPC
pro: protection among mutually suspicious subsystems
con: no convenient sharing of address spaces
UNIX vs Multics
UNIX was less ambitious (e.g. no unified mem/FS)
UNIX hardware was small
just a few programmers, all in the same room
evolved rather than pre-planned
quickly self-hosted, so they got experience earlier
What did UNIX inherit from MULTICS?
a shell at user level (not built into kernel)
a single hierarchical file system, with subdirectories
controlled sharing of files
written in high level language, self-hosted development
What did UNIX reject from MULTICS?
files look like memory
instead, unifying idea is file descriptor and read()/write()
memory is a totally separate resource
dynamic linking
instead, static linking at compile time, every binary had copy of libraries
segments and sharing
instead, single linear address space per process, like xv6
(but shared libraries brought these back, just for efficiency, in 1980s)
Hierarchical rings of protection
simpler user/kernel
for subsystems, setuid, then client/server and IPC
The most useful sources I found for late-1960s Multics VM:
1. Bensoussan, Clingen, Daley, "The Multics Virtual Memory: Concepts
and Design," CACM 1972 (segments, paging, naming segments, dynamic
linking).
2. Daley and Dennis, "Virtual Memory, Processes, and Sharing in Multics,"
SOSP 1967 (more details about dynamic linking and CPU).
3. Graham, "Protection in an Information Processing Utility,"
CACM 1968 (brief account of rings and gates).