Multi-programming in THE Landon Cox February 3, 2016

advertisement
Multi-programming in THE
Landon Cox
February 3, 2016
THE (First SOSP ’67)
SIGOPS Hall of Fame summary
“The first paper to suggest that an operating
system be built in a structured way. That
structure was a series of layers, each a virtual
machine that introduced abstractions built using
the functionality of lower layers. The paper
stimulated a great deal of subsequent work in
building operating systems as structured
systems.”
Across systems categories
• Many common, important problems
•
•
•
•
•
•
Fault tolerance
Coordination of concurrent activities
Geo. separated but linked data
Large-scale data sets
Protection from mistakes and attacks
Interactions with many people
• All of these problems lead to
complexity
Complexity
• How do we control complexity?
• Build abstractions that hide unimportant details
• Establish conventional interfaces
• Enable composition of simple, well-defined components
• None of these ideas a specific to computer
systems
• These are just principles of good engineering
• Civil engineering, urban planning, mechanical engineering,
aviation and space flight, electrical engineering,
ecology and political science
Dealing with complexity
• How do you impose order on complex programs?
•
•
•
•
Break functionality into modules
Goal is to “decouple” unrelated functions
Narrow the set of interactions between modules
Hope to make whole system easier to reason about
• How do we specify interactions between code
modules?
• Procedure calls
• int foo(char *buf)
• Why do procedure calls reduce complexity?
• They make interface between modules explicit
• They hide implementation details
Dealing with complexity
P(a){…}
C(){… y=P(x); …}
• Via what data structure do C and P communicate?
• They communicate via a shared, in-memory stack
• What is on the stack?
• Stack: regs, args, RA, P's vars, maybe args ...
• C: push regs, push args, push PC+1, jmp P, pop args, pop regs, ... R0
• P: push vars, ..., pop vars, mov xx -> R0, pop PC
• The call contract
•
•
•
•
•
P returns
P returns to where C said
P restores stack pointer
P doesn't modify stack (or C's other memory)
P doesn't wedge machine (e.g., use up all heap memory)
Dealing with complexity
P(a){…}
C(){… y=P(x); …}
• Is the call contract enforced?
• At a low level none of it is enforced
• P can violate all terms of the contract
• Violations due to bugs and attacks
• What about P’s functionality (what it does)? Enforced?
• No. E.g., no guarantee that sqrt() returns the right answer
• Would like a specification (spec) that P is correct
• Can we enforce either the spec or contract?
• We cannot enforce a procedure’s spec (i.e., that it is correct)
• Can only apply testing to convince ourselves that code adheres to spec
• We can enforce the call contract (= enforced modularity)
Dealing with complexity
P(a){…}
C(){… y=P(x); …}
• Why can we enforce the call contract?
• Purely mechanical interaction
• Programmer’s intention is clear
• No semantic gap to cross
• Ways that call contracts are enforced?
• Put P and C in different processes (RPC)
• Trusted compiler/runtime transitions between C, P (Java)
• Hardware regulates transitions (kernel, user process)
Dealing with complexity
P(a){…}
C(){… y=P(x); …}
• Functions are programming language constructs
• Can you think of a PL construct that shatters modularity?
• goto
• Inspired famous rant by Dijkstra in CACM in 1968
• “Go To Statement Considered Harmful”
"The unbridled use of the goto statement has as an immediate consequence
that it becomes terribly hard to find a meaningful set of coordinates in which to
describe the process progress. ... The goto statement as it stands is just too
primitive, it is too much an invitation to make a mess of one's program.”
•
Several months earlier … “Structure of THE multi-programming
system”
THE-multiprogramming system
• An operating system is a very complex
program
•
•
•
•
Has to manage other processes
Has to handle interrupts
Has to handle I/O devices
Has to handle synchronization primitives
• How THE handles all of this complexity
• Breaks functionality into stacked layers
• Higher layers depend on lower layers
THE-multiprogramming system
• What is a layer?
• A layer is a set of related procedure calls or functionality
• Represents a level in a hierarchy
• How do layers differ from procedure calls w/in a
program?
• Code can only call within layer and into layers below
• Can build a layer without worrying about what happens above
• Why is this additional structure appealing?
•
•
•
•
Further limits how modules interact
Reduces dependencies between modules
Hopefully reduces complexity and bugs
Do not have to worry about code in Layer 1 calling into Layer 4
THE-multiprogramming system
• Batch system
• Users submit programs
• OS decides what to run
• 5 user processes at a time
• Multi-tasking
• Manage multiple programs concurrently
• Need to switch between programs
Multi-programming
• Want more than 1 process in phys memory
• Processes can have same virtual addrs
• Cannot share physical addrs
• Addresses must be translated
• Programs cannot manipulate phys addrs anymore
• Statically: occurs before execution
• Dynamically: occurs during execution
• Protection becomes harder
Static address translation
• Want two processes in memory
• With address independence
• Without translating on-the-fly
• Using static translation
• How to do this?
Static address translation
• Adjust loads/stores when put in memory
• This is called a linker-loader
•
•
•
fffff
.
Programming
80000
• Assume memory starts at 0
.
3ffff
load $1, $base + offset
.
.
Linker-loader
.
20000
• Adds 0x20000 to offsets for P2
1ffff
Similar to linking phase of compile
.
.
.
00000
(high memory)
OS kernel
User process 2
User process 1
(low memory)
Layer 4
User programs
Layer 3
I/O devices
Layer 2
Console
Layer 1
Pager
Layer 0
Scheduler
Layer 0
• Primary responsibilities of Layer 0
• Ensure efficient, fair allocation of processor (government)
• Hide number of processors (abstraction layer)
• How did the scheduler ensure fair allocation?
• Handled all transitions between processes
• Some were voluntary transitions (synchronization calls)
• Some were involuntary transitions (timer interrupts)
• Could Layer 0 code be swapped out?
• No, timer-interrupt handler always has to be in memory
Layer 1
• Primary responsibilities of Layer 1
• Ensure efficient, fair allocation of memory (government)
• Hide drum addresses (abstraction layer)
• What were a segment, core page, and drum page?
• Segment = coarse-grained unit of data, visible to program
• Core page = in-memory container for a segment
• Drum page = on-disk container for a segment
Layer 1
• How were processes isolated?
•
•
•
•
No hardware support for virtual memory
Relied on language and compiler
Programs written in Algol used private segment identifiers
Like the static address translation discussed last time
• Could Layer 1 code be swapped out?
• No, memory manager always has to be in memory
• Otherwise have a serious bootstrapping problem
Layer 1
• How did paging work without hardware support?
• Similar to way compiler handles procedure calls
• Compiler needed to know which segments to be accessed
• Compiler could insert calls into layer 1 to page-in segments
• Was any of this enforced?
• No, just like procedure-call contract is not enforced
• At the lowest-level, could easily corrupt other processes
• Bugs and attacks could crash the whole system
Coordination of processes
• “A society of harmonious sequential
processes”
• Each layer implemented as a process
• To move between layers, need way to transition
safely
• What primitives provided safe switching?
• Semaphores: Up (V) and Down (P)
• Down (may) block the calling process
• Up (may) allow another process to be scheduled
Semaphores
• First defined by Dijkstra for THE
• Two operations: up and down
// aka “V” (“verhogen”)
up () {
// begin atomic
value++
// end atomic
}
What is going on
here?
Can value ever be < 0?
// aka “P” (“proberen”)
down () {
do {
// begin atomic
if (value > 0) {
value-break
}
// end atomic
} while (1)
}
More semaphores
• Key state of a semaphore is its value
• Initial value determines semaphore’s behavior
• Value cannot be accessed outside semaphore
• (i.e., there is no semaphore.getValue() call)
• Semaphores can be both synchronization
types
• Mutual exclusion (like locks)
• Ordering constraints (like monitors)
Semaphore mutual exclusion
• Ensure that 1 (or < N) thread is in critical section
s.down ();
// critical section
s.up ();
• How do we make a semaphore act like a lock?
•
•
•
•
Set initial value to 1 (or N)
Like lock/unlock, but more general
(could allow 2 threads in critical section if initial value = 2)
Lock is equivalent to a binary semaphore
Semaphore ordering constraints
• Thread A waits for B before proceeding
// Thread A
s.down ();
// continue
// Thread B
// do task
s.up ();
• How to make a semaphore behave like a monitor?
• Set initial value of semaphore to 0
• A is guaranteed to wait for B to finish
• Doesn’t matter if A or B run first
• Like a CV in which condition is “sem.value==0”
• Can think of as a “prefab” condition variable
Prod.-cons. with semaphores
• Same before-after constraints
• If buffer empty, consumer waits for producer
• If buffer full, producer waits for consumer
• Semaphore assignments
• mutex (binary semaphore)
• fullBuffers (counts number of full slots)
• emptyBuffers (counts number of empty slots)
Prod.-cons. with semaphores
• Initial semaphore values?
• Mutual exclusion
• sem mutex (?)
• Machine is initially empty
• sem fullBuffers (?)
• sem emptyBuffers (?)
Prod.-cons. with semaphores
• Initial semaphore values?
• Mutual exclusion
• sem mutex (1)
• Machine is initially empty
• sem fullBuffers (0)
• sem emptyBuffers (MaxSodas)
Prod.-cons. with semaphores
Semaphore mutex(1),fullBuffers(0),emptyBuffers(MaxSodas)
consumer () {
down (fullBuffers)
}
producer () {
down (emptyBuffers)
down (mutex)
down (mutex)
take soda out
put soda in
up (mutex)
up (mutex)
up (emptyBuffers)
up (fullBuffers)
}
Use one semaphore for fullBuffers and emptyBuffers?
Remember semaphores are like prefab CVs.
Prod.-cons. with semaphores
Semaphore mutex(1),fullBuffers(0),emptyBuffers(MaxSodas)
consumer () {
down (mutex)
}
1
producer () {
down (mutex)
2
down (fullBuffers)
down (emptyBuffers)
take soda out
put soda in
up (emptyBuffers)
up (fullBuffers)
up (mutex)
up (mutex)
}
Does the order of the down calls matter?
Yes. Can cause “deadlock.”
Prod.-cons. with semaphores
Semaphore mutex(1),fullBuffers(0),emptyBuffers(MaxSodas)
consumer () {
down (fullBuffers)
}
producer () {
down (emptyBuffers)
down (mutex)
down (mutex)
take soda out
put soda in
up (emptyBuffers)
up (fullBuffers)
up (mutex)
up (mutex)
}
Does the order of the up calls matter?
Not for correctness (possible efficiency issues).
Prod.-cons. with semaphores
Semaphore mutex(1),fullBuffers(0),emptyBuffers(MaxSodas)
consumer () {
down (fullBuffers)
}
producer () {
down (emptyBuffers)
down (mutex)
down (mutex)
take soda out
put soda in
up (mutex)
up (mutex)
up (emptyBuffers)
up (fullBuffers)
}
What about multiple consumers and/or producers?
Doesn’t matter; solution stands.
Prod.-cons. with semaphores
Semaphore mtx(1),fullBuffers(1),emptyBuffers(MaxSodas-1)
consumer () {
down (fullBuffers)
}
producer () {
down (emptyBuffers)
down (mutex)
down (mutex)
take soda out
put soda in
up (mutex)
up (mutex)
up (emptyBuffers)
up (fullBuffers)
}
What if 1 full buffer and multiple consumers call down?
Only one will see semaphore at 1, rest see at 0.
Monitors vs. semaphores
• Monitors
• Separate mutual exclusion and ordering
• Semaphores
• Provide both with same mechanism
• Semaphores are more “elegant”
• Single mechanism
• Can be harder to program
Monitors vs. semaphores
// Monitors
lock (mutex)
// Semaphores
down (semaphore)
while (condition) {
wait (CV, mutex)
}
unlock (mutex)
• Where are the conditions in both?
• Which is more flexible?
• Why do monitors need a lock, but not semaphores?
Monitors vs. semaphores
// Monitors
lock (mutex)
// Semaphores
down (semaphore)
while (condition) {
wait (CV, mutex)
}
unlock (mutex)
• When are semaphores appropriate?
• When shared integer maps naturally to problem at hand
• (i.e., when the condition involves a count of one thing)
Classic synchronization problem
Each barber/stylist is
a thread
Each customer is
a thread
Sofa capacity = 4
Standing room
Customer
room capacity
=9
Only 9 customers
in room
at a time.
Stylist(int id) {
Customer () {
}
Break up into groups of 2 or 3
Try to come up with a solution
We’ll talk about it in 10 minutes…
}
Salon with semaphores
Semaphore room = 9,
sofa = 4, chair = 3,
customer = 0, cut = 0;
Stylist () {
while (1) {
customer.down()
// cut hair
cut.up ()
}
}
Is anything weird
here?
Customer () {
room.down ()
// enter room
sofa.down ()
// sit on sofa
chair.down ()
// sit on chair
sofa.up ()
customer.up ()
cut.down ()
// leave chair
chair.up ()
// leave room
room.up ()
}
lock;
roomCV;
numInRoom=0;
sofaCV;
numOnSofa=0;
chairCV;
freeChairs[3];
customerCVs[3];
cutCVs[3];
Enter room
Customer () {
lock.acquire ();
while (numInRoom == 9)
roomCV.wait (lock);
numInRoom++;
while (numOnSofa == 4)
sofaCV.wait (lock);
numOnSofa++;
Sit on sofa
while (findFreeChair () == NONE)
chairCV.wait (lock);
stylist = findFreeChair ();
freeChairs[stylist] = 1;
numOnSofa--;
sofaCV.signal (lock);
Sit in chair
Wake up stylist
customerCVs[stylist].signal (lock);
Wait for cut to
finish
cutCVs[stylist].wait (lock);
freeChairs[stylist] = 0;
chairCV.signal (lock);
numInRoom--;
roomCV.signal (lock);
lock.release ();
Leave the shop
}
Stylist(int id) {
lock.acquire ();
while (1) {
while (!freeChairs[id])
customerCVs[id].wait (lock);
cutHair ();
cutCVs[id].signal ();
}
lock.release ();
}
Customer () {
lock.acquire ();
while (numInRoom == 9)
roomCV.wait (lock);
numInRoom++;
while (numOnSofa == 4)
sofaCV.wait (lock);
numOnSofa++;
while (findFreeChair () == NONE)
chairCV.wait (lock);
stylist = findFreeChair ();
freeChairs[stylist] = 1;
numOnSofa--;
sofaCV.signal (lock);
customerCVs[stylist].signal (lock);
cutCVs[stylist].wait (lock);
freeChairs[stylist] = 0;
chairCV.signal (lock);
numInRoom--;
roomCV.signal (lock);
lock.release ();
}
Next time
• More history
• Multics
• “Almost every important idea in OS can be found
somewhere in Multics.”
• So complicated and hard to use, it inspired UNIX
• Questions?
Download