Monitors An Operating System Structuring Concept Paper by
Monitors: An Operating System Structuring Concept Paper by: C. A. R. Hoare Presented by: Sabrina Brick
Why monitors? n Concurrency has always been an OS issue q q n Resource allocation is necessary among competing processes Timer interrupts Existing synchronization mechanisms (semaphores, locks) are subject to hard-tofind, subtle bugs.
What is a monitor? n n A collection of data and procedures Mutual exclusion q q n allows controlled acquisition and release of critical resources single anonymous lock automatically acquired and released at entry and exit Data encapsulation q monitor procedures are “entry points” for accessing data
Monitors: a language construct n n Monitors are a programming language construct anonymous lock issues handled by compiler and OS detection of invalid accesses to critical sections happens at compile time process of detection can be automated by compiler by scanning the text of the program
An Abstract Monitor name: monitor …local declarations …initialize local data proc 1 (…parameters) …statement list proc 2 (…parameters) …statement list proc 3 (…parameters) …statement list
Rules to Follow with Monitors n n n Any process can call a monitor procedure at any time But only one process can be inside a monitor at any time (mutual exclusion) No process can directly access a monitor’s local variables (data encapsulation) A monitor may only access its local variables Why? Race conditions could occur! “Chaos will ensue”!
Enforcing the Rules n “wait” operation q n “signal” operation q n current process is put to sleep wakes up a sleeping process condition variables q q May have different reasons for “waiting” or “signaling” Processes waiting on a particular condition enter its queue
Sabrina’s Car: a running example car: monitor occupied: Boolean; occupied : = false; non. Occupied: condition; procedure enter. Car() if occupied then non. Occupied. wait; occupied = true; procedure exit. Car() occupied = false; non. Occupied. signal; What possible problem exists? monitor declaration local variables / initializations procedure
The Possible Problem n As discussed in Birrell’s paper (last class): q First process: n n n q Second process: n n Completes it’s work with critical section Signals a process waiting on “non. Occupied” Starts to release the monitor when… Is awakened by first process Tries an “entry point” into the monitor First process isn’t finished releasing it so it blocks again! This can’t happen with Hoare’s approach: q q Signal must be the last operation in a monitor. Signal and monitor exit are a combined operation.
Multiple Conditions n n n Sometimes it is necessary to be able to wait on multiple things Can be implemented with multiple conditions Example: 2 reasons to enter car q q n drive (empties tank) fill up car Two reasons to wait: q q Going to gas station but tank is already full Going to drive but tank is (almost) empty
Sabrina’s Car: a running example car: monitor empty. Tank, full. Tank: condition; tank: stack; tank : = stack of 10 gallons; //10 gallon tank //filled in increments of a gallon procedure drive. Car() if tank. is. Almost. Empty then full. Tank. wait; tank. pop(); //consume gas in increments of a gallon empty. Tank. signal; procedure fill. Car() if tank. is. Full then empty. Tank. wait; tank. push(gallons. pop); full. Tank. signal; //assume gas station “stack” has infinite supply
Condition Queue n Might want to check if any process is waiting on a condition q n The “condition queue” returns true if a process is waiting on a condition Example: filling the tank only if someone is waiting to drive the car.
Sabrina’s Car: a running example car: monitor emtpy. Tank, full. Tank: condition; tank: stack; tank : = stack of 10 gallons; //10 gallon tank //filled in increments of a gallon procedure drive. Car() if tank. is. Almost. Empty then full. Tank. wait; tank. pop(); //consume gas in increments of a gallon empty. Tank. signal; procedure fill. Car() if tank. is. Full then empty. Tank. wait; if full. Tank. queue tank. push(gallons. pop); full. Tank. signal; //returns true if a process is waiting on full. Tank //assume gas station “stack” has infinite supply
Priority: Scheduled Waits n n n A waiting process is given a number The process with the lowest number gets woken up first Example: People who want to drive my car are prioritized: q q q Highest: Me! Medium: Family Lowest: Friends/Others
Sabrina’s Car: a running example car: monitor emtpy. Tank, full. Tank: condition; tank: stack; tank : = stack of 10 gallons; //10 gallon tank //filled in increments of a gallon procedure drive. Car(p: person) if tank. is. Almost. Empty then if p. is. Sabrina then full. Tank. wait(1); if p. is. Family then full. Tank. wait(2); if p. is. Friendor. Other then full. Tank. wait(3); tank. pop(); //consume gas in increments of a gallon empty. Tank. signal; procedure fill. Car() if tank. is. Full then empty. Tank. wait; if full. Tank. queue tank. push(gallons. pop); full. Tank. signal; //assume gas station has infinite supply
Other issues n Power of Monitors q n Monitor->semaphore and vice versa Proof rules q q I {b. wait} I&B {b. signal} I I : the invariant B : the condition that a process waiting on b wants to be true before its woken up b : the condition variable
Lots of Great Examples n OS Examples: q q q Bounded Buffer Alarm clock Disk head scheduler Reader/writer Buffer Allocation
Monitors and Granularity n Monitors are designed for course-grained locking q n n n Example: locking an entire data structure rather than its individual components. Is this an appropriate solution for all cases? Why or why not? What problems arise when the granularity is too coarse? What about the opposite case?
Monitors: implementation issues Disabling Interrupts Using a lock car: monitor occupied: Boolean; occupied : = false; non. Occupied: condition; //lock 1: lock; procedure enter. Car() //disable. Interrupts(); if occupied then non. Occupied. wait; occupied = true; //enable. Interrupts(); procedure enter. Car() //lock 1. acquire(); if occupied then non. Occupied. wait; occupied = true; //lock 1. release(); procedure exit. Car() //disable. Interrupts(); occupied = false; non. Occupied. signal; //enable. Interrupts(); procedure exit. Car() //lock 1. acquire(); occupied = false; non. Occupied. signal; //lock 1. release(); “Yellow code” inserted into binary by compiler Which is the better approach? Why? //lock 1. acquire() //disable. Interrupts(); //lock. Var = 1; //enable. Interrupts();
What problems do monitors solve? n n n Mutual exclusion Encapsulation of data Compiler can automatically scan program text for some types of synchronization bugs Synchronization of shared data access simplified vs. semaphores and locks Good for problems that require course granularity Invariants are guaranteed after waits q Theoretically, a process that waits on a condition doesn’t have to retest the condition when it is awakened.
What remains problematic? n n n No way to check dynamically allocated shared data Signals are as error prone as with other synchronization mechanisms Deadlock can still happen in monitor code Programmer can still screw up Monitors are available in very few languages
Conclusions n n Monitors are a synchronization mechanism A higher level, easier to use abstraction, better encapsulation than semaphores, etc. Monitors still suffer from various problems Let’s take a look at working model that addresses some of those issues! (Suzanne’s presentation)
References n n n Jon Walpole “Monitors: An Operating Systems Structuring Concept” Hoare. Modern Operating Systems, Second Edition. Tannenbaum, pp. 115 -119. Emerson Murphy-Hill presentation from CS 533, Winter 2005 http: //en. wikipedia. org/wiki/C. _A. _R. _Hoare
- Slides: 23