CS452 - Real-Time Programming - Spring 2010
Lecture 24 - Pathologies
Public Interest Announcements
- Demos
- How to give a demo
Pathologies
1. Deadlock
One or more tasks will never run again. For example
- Task sends to itself (local: rest of system keeps running, task itself
will never run)
- Every task does Receive( ) (global: nothing is running)
- Cycle of tasks sending around the cycle (local: other tasks keep
running)
Kernel can detect such things
Potential deadlock can be detected at compile time
- cycle in the send graph of all sends that could happen
- doesn't necessarily occur at run-time
- that is, it's a necessary but not sufficient condition
- Changes in a critical race can make a potential deadlock reveal
itself.
Solutions
- Gating
- Most common example is initialization, where the send/receive
pattern may be different than FOREVER
- Gate the end of initialization
- Define two types of task
- Administaror (A): only receives
- Worker (W): only sends
- Two A tasks cannot communicate directly; two W tasks cannot
communicate directly.
Send
appears in two flavours
FOREVER {
Send( A, request, result )
...
}
FOREVER {
Send( A, result, work-order )
...
}
The corresponding Receive
s are also different.
FOREVER {
Send( A1, request, message )
Send( A2, message, result )
}
mixes the two.
2. Livelock (Deadly Embrace)
Usually occurs in the context of resource contention. For example
- client1 needs resource1 & resource2; obtains resource1 from
propietor1; asks propietor2 for resource2
- client2 needs resource1 & resource2; obtains resource2 from
propietor2; asks propietor1 for resource1
- Client
Send( prop1, getr1, ... );
Send( prop2, getr2, ... );
// Use the resources
Other order in other client
- Proprietor
FOREVER {
Receive( &clientTid, req, ... );
switch ( req-type ) {
case REQUEST:
if( available ) { Reply( clientTid, use-it, ... ); available = false; }
else enqueue( clientTid );
case RELEASE:
available = true;
Reply( clientTid, "thanks", ... );
if( !empty( Q ) ) Reply( dequeue( ), use-it, ... );
}
}
- state:
- client1, client2: REPLY-BLOCKED - can't release resources
- proprietor1, proprietor2: SEND-BLOCKED - waiting for release
- proprietor1 & proprietor2 fail the requests
- Proprietor
FOREVER {
Receive( &clientTid, req, ... );
switch ( req-type ) {
case REQUEST:
if( available ) { Reply( clientTid, use-it, ... ); available = false; }
else Reply( clientTid, "sorry", ...);
case RELEASE:
available = true;
Reply( clientTid, "thanks", ... );
}
}
- Polling is the most likely result.
- Client
while ( Send( prop1, getr1, ... ) ) ;
while ( Send( prop2, getr2, ... ) ) ;
// Use the resources
- Concrete example:
- Join the tracks together with one set of tasks managing one track,
another set managing another
- i.e. Two track reservation servers
- What happens when a train moves from one track to the other?
- This is a real-life, many dollar problem in the mobile phone
industry.
Kernel(s) cannot easily detect livelock
Possible solutions
- both resources in one proprietor
- global order on resource requests
- ethernet algorithm
- Release; wait a random time; try again
- Requires proprietor who says "sorry".
Could consider this a form of critical race.
3. Critical Races
Example
- Two tasks, A & B, at the same priority
- A is doing a lot of debugging IO
- B always reserves a section of track before A, and all is fine.
- Debugging IO is removed
- A reserves the section before B can get it, and execution
collapses.
- Lower priority of A to the same level as C.
- Now C executes more slowly, and D gets a different resource before C
.
- You shuffle priorities forever, eventually reverting to leave in the
debugging IO.
Theory of relativity and the event horizon.
Symptoms
- Small changes in priorities change execution unpredictably, and
drastically.
- Debugging output changes execution drastically.
- Changes in train speeds change execution drastically.
- Example from two terms ago
`Drastically' means chaos in both senses of the term
- Sense one: a small change in the initial conditions produces an
exponentially growing change in the system
- Sense two: exercise for the reader.
Solutions
- Explicit synchronization
- but you then have to know the orders in which things are permitted
to occur
- Gating is a technique of global synchronization
- which can be provided by a detective/coordinator
4. Performance
The hardest problem to solve
- You just don't know what is possible
- Ask a question like:
- Is my kernel code at the limit of what is possible in terms of
performance?
- We can compare the performance on message passing, etc., because
two kernels are pretty much the same.
- Compare a lot of kernels and you should be able to find a lower
limit
- Can't do the same thing for train applications
Priority
The hardest thing to get right
- Sizing stacks used to be harder, but now we have lots of memory
- NP-hard for the human brain
- Practical method starts with all priorities the same, then adjusts
- symptoms of good priority assignment
- The higher priority, the more likely the ready queue is to be
empty
- The shorter the run time in practice the higher the priority
Problems with priority
- Priority inversion
- One resource, many clients
- Tasks try to do too much
Congestion
- Too many tasks
- blocked tasks don't count,
- lowest priority tasks almost don't count
Layered abstraction are costly
e.g. Notifier -> SerialServer -> InputAccumulater -> Parser ->
TrackServer
Hardware
- Turn on optimization, but be careful
- There are places where you have done register allocation by
hand
- Turn on caches
Size & align calibration tables by size & alignment of cache
lines
- linker command script
- I think that this is stretching it.
Return to: