summaryrefslogtreecommitdiff
path: root/web/l14.txt
blob: d121dffe57c361acc1195f82860816d293d5617e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
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).