You are expected to understand this. CS 111 Operating Systems Principles, Spring 2006
You are here: CS111: [[2006spring:notes:lec7]]
 
 
 
Table of Contents

CS 111

Scribe Notes for 4/25/06, Lecture 7

Scheduling

Real-Time Scheduling : SCHEDULE is a new type of preference. It is a process or thread that declares ahead how much of time a process or thread needs, and when OS meets these requirements.

  • "Soft" real-time : e.g. CD skips, multimedia
  • "Hard" real-time : e.g. Plane crashes (absolute guarantees!)

Reservations : Process requests τ units of time every T units. For A, every 10 units of time, it needs 1 unit of time to prepare.

τ T
A 1 10
B 4 5
C 2 10

.:fig1.jpg

Therefore we can not run process C.

Generally, real-time system use Admission Control. Once we schedule time for A and B, although B doesn't really use the time, C still can not get in. This is because it can finish 4 units of time of jobs within 3 units of time.

  • ADMISSION CONTROL : OS allows process to run only depending on schedule.
  • BEST EFFORT : OS runs process giving it left over time.

Deadline Scheduling : For this kind of scheduling, each process should be run before hitting the deadline.

Process requests t units of time by the deadline

τ Deadline
A 5 10
B 1 9
C 4 15
D 2 3

.:fig2.jpg?448x149

If there exists E which also requests 2 units of time with deadline 3, obviously we cannot finish D and E in 3 units' time.

  • EDF (Earliest Deadline First) : Always finds a schedule if one exists

We will have problem if we have the case below:

τ Deadline
A 5 10
B 1 9
C 4 15
D 2 3
N 1 8

.:fig3.jpg

Synchronization

Example of Bank of America ATM system

Bank of America:

.:t_fig_3.jpg

While ( 1 )
{
  Accept new connection ;
  Pthread_create( conn, new_connection);
}
Conn( )
{
  Look up balance; // global var
  if ( command is deposit )
  {
    balance + = amount;
  }
  else if (command is withdraw && balance >= amount )
  {
    balance - = amount;
  }
}

The possible results are:

.:t_fig_21.jpg

We have to take a look of how the code can be compiled.

Example:

  • Initially, my balance is $10. I withdraw $5 at LA and my Mom deposits $5 at MA.
  • The Possible outcomes: $5, $10, $15 ($10 is the answer we should have)

The critical statements are:

Deposit Withdraw
balance += amount; balance -= amount;
LOAD movl balance, %eax (1) movl balance, %eax (4)
MODIFY addl amount, %eax (2) subl amount, %eax (5)
STORE movl %eax, balance (3) movl %eax, balance (6)
Instruction Sequence
To get $10: (1) (2) (3) p (4) (5) (6)
To get $ 5: (1) p (4) p (2) (3) p (5) (6)
To get $15: (1) p (4) (5) (6) p (2) (3)

where p stands for preemption

.:t_fig_1.jpg

Synchronization : Two or more threads ( ) need to cooperate must selectively avoid preemption.

Critical Section : A piece of code that must be executed ATOMICALLY with respect to other code in the same contexts, for the program to work correctly.

Atomic Indivisible : Execute as one uninterruptible unit.

Problem part:

Conn( )
{
  Look up balance; // global var
  if ( command is deposit )
  {
    balance + = amount;
  }
  else if (command is withdraw && balance >= amount )
  {
    balance - = amount;
  }
}

Good critical sections avoid RACE CONDITIONS (order dependent bugs)

Shared State

  • Synchronization
  • Context

The Requirements of Critical Section are that they should be mutual exclusive (atomically). Also, there should be no two critical secion in the same context run simultaneously. Second, it should be only one thread entering the critical section. Moreover, no single thread waits forever on a critical section becasue of bounded writing which means fairness. All the threads should enter critical section eventually.

Building a critical section

Now, our goal is to prevent concurrency / preemption. In other words, we have the following questions. First, what kinds of concurrency matter? Also, how big is the critical section? And, what is the type of the critical section? For concurrency, we can use hardware interrupts, or threads. But how can we build on hardware assistance? First we can make instructions to execute atomically. Using the ATM example, we can put the movl code atomically:

Balance T1 T2
$10 movl $5 blance movel $5, %eax

In addition, we can first turn off interrupts, and then run the critical section and turn the interrupts on again. This technique is called Uniprocessor. Note: However, turning off interrupts is kind of violation of isolation idea in OS. For multiprocessor, we can use inter-processor interrupts. The last but not least, we can apply user-level primitive for synchronization which is LOCK. The lock we use here is mutual exclusive lock, mutex.

Data object:

1. states

  • a. LOCKED
  • b. UNLOCKED

2. operations

  • a. lock(&1) => UNLOCKED -> LOCKED
  • b. unlock(&1) => LOCKED -> UNLOCKED

How to fix the problem by using lock? Here is the code:

While(1)
{
  Accept new connection;
  Pthread_create(conn, new connection);
}
Conn( )
{
  Location & userlock;
  Look up balance; // global var
  Lock(&userlock);
  if ( command is deposit )
  {
    balance + = amount;
  }
  else if (command is withdraw && balance >= amount )
  {
    balance - = amount;
  }
  Unlock (&userlock);
}

Code between lock & unlock executes atomically. And it is a context per-account. The advantage is that multiple accounts can execute simultaneously. But the disadvantage is that there are too many locks which are space overhead. This characteristic is defined as FINE GRAINED.

Conn()
{
  Location & banklock;
  Look up balance; // global var
  Lock(&banklock);
  if ( command is deposit )
  {
    balance + = amount;
  }
  else if (command is withdraw && balance >= amount )
  {
    balance - = amount;
  }
  Unlock (&banklock);
}

This is COARSE GRAINED. This version takes lesser space, but concurrency is also lesser.

The following is a more fine grained. We put lock and unlock before and after each statement that is going to change the value of the balance variable.

if (dep)
{
  Lock(&userlock);
  Bal + = amt;
  Unlock(&userlock);
}
else if(withdraw)
{
  Lock (&userlock);
  if (bal >= amt)
    Bal - = amt;
  Unlock(&userlock);
}

If we change the above code with two contexts, the code is like this:

if (deposit)
{
  Lock(&userlock_dep);
  Bal + = amt;
  Unlock(&userlock_dep);
}
else if(withdraw)
{
  Lock (&userlock_with);
  if (bal >= amt)
    Bal - = amt;
  Unlock(&userlock_with);
}

This one is not working.

For creating the lock and unlock, we have the System calls:

Sys_lock (&l)
While (*l == 1) //while(someone has lock)
  Schedule( );  //  busy wait();
*l = 1;         //take lock
Run(&current);  //run
Sys_unlock(&l)
  *l = 0;       //unlock
Run(&currnt);   //run

What happen if it runs on Linux system?

It works because the kernel never preemptible.

What happen if it runs on multi-processor system?

It will not work because:

P1 P2 P3
While (*l= = 1) While (*l = = 1) Has lock; *l = 0;

this violates mutual exclusive.

How to solve it?

ATOMIC INSTRUCTIONS

=>Test_and_set (int* p, int value)

int old = *p;
*p = value;
return old;

=>Alternative:

Sys_lock(&l);
While (test_and_set(&l,1) == 1)
  Schedule( );
run(&current);
 
2006spring/notes/lec7.txt · Last modified: 2006/09/26 11:42 (external edit)
 
Recent changes RSS feed Driven by DokuWiki