Kinesia Online Course
Operating Systems
Kinesia LLC, 2003

  1. Introduction
  2. Processes
  3. Inter Process Communication
  4. Deadlocks
  5. Memory Management
 
  1. File Systems
  2. Protection and Security
  3. I/O Systems


We are what we repeatedly do.
Excellence, then, is not an act, but a habit.

					Aristotle
Interprocess Communication ( IPC )
  1. Race Conditions and Critical Sections

    Too much milk problem ( Adopted from Barton P. Miller's Lecture notes )

    Person A Person B
    3:00
    3:05
    3:10
    3:15
    3:20
    3:25
    3:30
    
    Look in fridge. Out of milk.
    Leave for store.
    Arrive at store.
    Leave store.
    Arrive home, put milk away.
     
     
    
    
    
    Look in fridge.  Out of milk.
    Leave for store.
    Arrive at store.
    Leave store.
    Arrive home.  OH NO!
    

    What does correct mean?

    Synchronization: the use of atomic operations to ensure the correct operation of cooperating processes

    race conditions

    shared resource -- critical resource

    Mutual exclusion: Mechanisms that ensure that only one person or process is doing certain things at one time (others are excluded). E.g. only one person goes shopping at a time.

    Critical section: A section of code, or collection of operations, in which only one process may be executing at a given time. E.g. shopping. It is a large operation that we want to make "sort of" atomic.

    There are many ways to achieve mutual exclusion. Most involve some sort of locking mechanism: prevent someone from doing something. For example, before shopping, leave a note on the refrigerator.

    Three elements of locking:
    1. Must lock before using. leave note
    2. Must unlock when done. remove note
    3. Must wait if locked. do not shop if note

    1st attempt at computerized milk buying:

    Processes A & B
    1
    2
    3
    4
    5
    6
    7
    
    if (NoMilk) {
        if (NoNote) {
            Leave Note;
            Buy Milk;
            Remove Note;
        }
    }
    

    Does this work?

    What happens if we leave the note at the very beginning: does this make everything work?

    2nd attempt: Change meaning of note.

    A buys if there is no note, B buys if there is a note. This gets rid of confusion.

    Process A Process B
    1
    2
    3
    4
    5
    6
    
    if (NoNote) {
        if (NoMilk) {
            Buy Milk;
        }
        Leave Note;
    }
    
    if (Note) {
        if (NoMilk) {
            Buy Milk;
        }
        Remove Note;
    }
    

    Suppose B goes on vacation. A will buy milk once and will not buy any more until B returns. Thus this really does not really do what we want; it is unfair, and leads to starvation.

    3rd attempt: Use 2 notes:

    Process A
    1
    2
    3
    4
    5
    6
    7
    
    Leave NoteA;
    if (NoNoteB) {
        if (NoMilk) {
            Buy Milk;
        }
    }
    Remove NoteA;
    
    Process B is the same except interchange NoteA and NoteB.

    What can we say about this solution?

    Solution is almost correct. We just need a way to decide who will buy milk when both leave notes (somebody has to hang around to make sure that the job gets done).

    4th attempt: In case of tie, A will buy milk:

    Process B stays the same as before.

    Process A
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    Leave NoteA;
    if (NoNoteB) {
        if (NoMilk) {
            Buy Milk;
        }
    } else {
        while (NoteB) {
            DoNothing;
        }
        if (NoMilk) {
            Buy Milk;
        }
    }
    Remove NoteA;
    

    How do we know this is correct?

    This solution works. But it still has two disadvantages:

    Good solution requirements of mutual exclusion

  2. No two processes may be simultaneously inside their critical section
  3. Independent of CPU speed and # of CPU
  4. No process running outside its critical section should block any process
  5. Bounded waiting
  6. Mutual Exclusions

  7. Disabling interrupts

      when a process enters a critical section, it disables all interrupts

      problem: if a process forgot to turn on interrupts, other processes can't be executed

      mulitprocessing -- time-consuming and difficult to disable all interrupts

  8. Two-process solutions
      only 2 processes: P0, P1

      strict alternation

      turn = 1 => P1
      turn = 0 => P0
      P0  P1
      while ( 1 ) {
        while ( turn != 0 )
        	;		//wait
        critical_section();
        turn = 1;		//P1's turn now
        non_critical_section();
      }
      	
       
      while ( 1 ) {
        while ( turn != 1 )
      	;		//wait
        critical_section();
        turn = 0;		//P0's turn now
        non_critical_section();
      }
      	

      The non_critical_section() of say P1 could be very long. This may lead to P0 blocking itself.

      Peterson's Solution

    P0  P1
    	//global variables
    	int wait;		//0 or 1
    	int interested[2];	//interested to enter C.S.
    	
    void enter_region0() {
      interested[0] = 1;	//P0 interested in C.S.
      wait = 0;		//P0, please wait first
      while ( wait == 0 && interested[1] )
        ;    //wait, if the other is interested
    }
    
    void leave_region0() {	//P0 leaving C.S.
        interested[0] = 0;
    }
    	
     
    
    void enter_region0() {
      interested[1] = 1;	//P1 interested in C.S.
      wait = 1;		//P1,please wait first
      while ( wait == 1 && interested[0] )
        ;    //wait, if the other is interested
    }
    
    void leave_region1() {	//P1 leaving C.S.
        interested[1] = 0;
    }
    	

      Before entering its critical seciton, each process calls enter_regionid() with its own process id. This call will cause it to wait, if need be, until it is safe to enter.

  9. Multiple-process solutions

    Lamport's Bakery Algorithm

    • get a ticket number to purchase baked goods
    • if two processes pick the number at the same time, it does not gaurantee two processes have different #
    • if number( Pi ) < number ( Pj ) serve Pi first
    • if number ( Pi ) = number ( Pj ), then check i, j ( unique process ids ); if i < j, then serve Pi first
    • The notation (a,b) < (c,d) is defined as (a < c) or (a = c and b < d)

      Process i
      
      boolean choosing[n];
      int number[n];
      while (true)
      {
           choosing[i] = true;	//picking a number
      				//max ( number[0], ..., number[n-1] ) + 1
           number[i] = 1 + getmax( number[], n );
           choosing[i] = false;	//finished picking a number
           for (int j=0; j < n; j++ )	//check with others
           {
                while ( choosing[j] )	//don't do anything while someone
                	;		//is choosing a #
                while (( number[j]!=0 ) && ( number[j],j) < ( number[i],i ))
                	;		//check if process j has higher priority 
      				//( lower # )
           }
           critical_section();
           number[i] = 0;		//reset
           non_critical_section();
      }
      	

  10. Use TSL Instruction

    • help from hardware
    • Test-and-Set Lock ( atomic action, i.e. indivisible )
    • Use flag ( lock ) to coordinate access to shared memory, set to 1 by TSL

      while ( 1 ) {
        test_and_set();	//lock ← 1; wait if necessary
        critical_section();
        reset();		//lock ← 0
      }
      
      
      test_and_set: TSL register, lock //register ← lock, lock ← 1 CMP register, #1 //is lock = 1 ? JE test_and_set //yes, C.S. forbiden, wait RET
      reset: MOV lock, #0 //lock ← 0 RET

  11. Semaphores

    o A semaphore S is an integer variable, apart from initialization is accessed through standard atomic operations:

  12. wait ( S ): waits for semaphore S to become positive, then decrements it by 1. ( or down( S ) )
  13. signal ( S ): increments semaphore S by 1.( or up ( S ) )

    	void wait ( int S ) {	//atomic
    	  while ( S <= 0 ) 
    	    	;		//wait
    	  S = S - 1;
    	}
    
    	
    void signal ( int S ) { //atomic S = S + 1; }

  14. o invented by Dijkstra in 1965

    o Semaphores are simple and elegant and allow the solution of many interesting problems. They do a lot more than just mutual exclusion.

    o Binary semaphores are those that have only two values, 0 and 1. They are implemented in the same way as regular semaphores except multiple signal()s will not increase the semaphore to anything greater than one.

    o Semaphores are not provided by hardware. But they have several attractive properties:

  15. Machine independent.
  16. Simple.
  17. Powerful. Embody both exclusion and waiting but do NOT need to be busy waiting
  18. Correctness is easy to determine.
  19. Work with many processes.
  20. Can have many different critical sections with different semaphores.
  21. Can acquire many resources simultaneously (multiple P's).
  22. Can permit multiple processes into the critical section at once, if that is desirable.
  23. o Here is an example of how semaphores are used to enforce mutual exclusion:

    o When a process executes the wait() operation and finds that the semaphore value is not positive, it must wait.

    Example

    In figure A, there are three semaphores: S1 has a value of zero and process P1 is blocked on S1; S2 has a value of three, meaning three more processes may execute a down operation on S2 without being put to sleep; S3 has a value of zero, and has two sleeping processes, P4 and P5, blocked on it.

    Now let us look at what happens when a process executes an UP on S1. In figure B, process P1 is activated by an UP operation. It executes a DOWN on S1 and enters its critical section. The state of the semaphores after these actions has now changed.

    o C implementation

    	typedef int	process;
    
    	typedef struct {
    	  int		count;
    	  process	Q[MAX_P];	//a queue of processes
    	} Semaphore S;
    
    	Semaphore S;
    	
    void wait ( Semaphore S ) //atomic { if ( S.count > 0 ) //no need to wait --S.count; else { add_process ( S.Q ); //add this process to S.Q sleep(); //block reschedule(); } }
    void signal ( Semaphore S ) //atomic { if ( isEmpty( S.Q ) ) { //no waiters to wake up ++S.count; } else { P = delete_process ( S.Q ); //delete a process P from S.Q wakeup ( P ); //e.g. put P in a ready list ++S.count; reschedule(); //depends on priority of P and //currently running process } }

    o A Java implementation

    Click here to see.

  24. Monitors high-level synchronization,
    consists of procedures, shared resources, and administrative data
  25. only one process can be active inside a monitor
  26. procedures of monitor can only access data inside monitor
  27. local data of monitor cannot be accessed from outside
  28. When a task attempts to access a monitor method, it is put in the monitor's entry queue.
  29. A condition variable is not a variable at all. In fact it is just a queue that is part of the monitor. Sometimes these are called event queues or variable queues. A condition variable queue can only be accessed with two monitor methods associated with this queue. These methods are typically called wait and signal. The signal method is called notify in Java.
  30. A task that holds the monitor lock may give it up and enter a condition variable queue by executing the corresponding wait method.
  31. A task that holds the monitor lock may revive a task waiting in a condition variable queue with the notify method of that queue.
  32. The notify method removes one task from the condition variable queue if the queue is not empty.
  33. Processes in the monitor queues are waiting to acquire the monitor lock.
  34. When a task is removed from one of the condition variable queues, it is put in the waiting queue.

    Weakness:

    1. one process active inside monitor => defeats concurrency purpose
    2. nested monitor calls --> deadlock
    Java Synchronization ~ monitor
    An Example : Producer-Consumer Problem

  35. Message Passing

    send ( P, &message );	//send a message to process P
    
    receive ( Q, &message );//receive a message from Q
    	

    A communication link has to satisfy the following properties:

    indirect communication

  36. A Classical IPC problem

    The dining philosophers problem ( Dijkstra, 1965 )

    Analysis

    	//philosopher i
    	
    	while ( true ) {
    	  think();
    	  wait ( mutex );	//first check if someone is eating
    	  take_chops( i );	//take left chopstick
    	  take_chops((i+1)%5);	//take right chopstick
    	  eat();
    	  put_chops( i );	//put down left chops
    	  put_chops( (i+1)%5);	//put down right chops
    	  signal( mutex );	//wake up anyone who's waiting
    	}
    	

    O.K. It works! But what's the drawback of the above algorithm?

    Improvement: