248 lines
9.8 KiB
Text
248 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).
|