340 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			340 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<title>Scheduling</title>
 | 
						|
<html>
 | 
						|
<head>
 | 
						|
</head>
 | 
						|
<body>
 | 
						|
 | 
						|
<h1>Scheduling</h1>
 | 
						|
 | 
						|
<p>Required reading: Eliminating receive livelock
 | 
						|
 | 
						|
<p>Notes based on prof. Morris's lecture on scheduling (6.824, fall'02).
 | 
						|
 | 
						|
<h2>Overview</h2>
 | 
						|
 | 
						|
<ul>
 | 
						|
 | 
						|
<li>What is scheduling?  The OS policies and mechanisms to allocates
 | 
						|
resources to entities.  A good scheduling policy ensures that the most
 | 
						|
important entitity gets the resources it needs.  This topic was
 | 
						|
popular in the days of time sharing, when there was a shortage of
 | 
						|
resources.  It seemed irrelevant in era of PCs and workstations, when
 | 
						|
resources were plenty. Now the topic is back from the dead to handle
 | 
						|
massive Internet servers with paying customers.  The Internet exposes
 | 
						|
web sites to international abuse and overload, which can lead to
 | 
						|
resource shortages.  Furthermore, some customers are more important
 | 
						|
than others (e.g., the ones that buy a lot).
 | 
						|
 | 
						|
<li>Key problems:
 | 
						|
<ul>
 | 
						|
<li>Gap between desired policy and available mechanism.  The desired
 | 
						|
policies often include elements that not implementable with the
 | 
						|
mechanisms available to the operation system.  Furthermore, often
 | 
						|
there are many conflicting goals (low latency, high throughput, and
 | 
						|
fairness), and the scheduler must make a trade-off between the goals.
 | 
						|
 | 
						|
<li>Interaction between different schedulers.  One have to take a
 | 
						|
systems view.  Just optimizing the CPU scheduler may do little to for
 | 
						|
the overall desired policy.
 | 
						|
</ul>
 | 
						|
 | 
						|
<li>Resources you might want to schedule: CPU time, physical memory,
 | 
						|
disk and network I/O, and I/O bus bandwidth.
 | 
						|
 | 
						|
<li>Entities that you might want to give resources to: users,
 | 
						|
processes, threads, web requests, or MIT accounts.
 | 
						|
 | 
						|
<li>Many polices for resource to entity allocation are possible:
 | 
						|
strict priority, divide equally, shortest job first, minimum guarantee
 | 
						|
combined with admission control.
 | 
						|
 | 
						|
<li>General plan for scheduling mechanisms
 | 
						|
<ol>
 | 
						|
<li> Understand where scheduling is occuring.
 | 
						|
<li> Expose scheduling decisions, allow control.
 | 
						|
<li> Account for resource consumption, to allow intelligent control.
 | 
						|
</ol>
 | 
						|
 | 
						|
<li>Simple example from 6.828 kernel. The policy for scheduling
 | 
						|
environments is to give each one equal CPU time. The mechanism used to
 | 
						|
implement this policy is a clock interrupt every 10 msec and then
 | 
						|
selecting the next environment in a round-robin fashion.  
 | 
						|
 | 
						|
<p>But this only works if processes are compute-bound.  What if a
 | 
						|
process gives up some of its 10 ms to wait for input?  Do we have to
 | 
						|
keep track of that and give it back? 
 | 
						|
 | 
						|
<p>How long should the quantum be?  is 10 msec the right answer?
 | 
						|
Shorter quantum will lead to better interactive performance, but
 | 
						|
lowers overall system throughput because we will reschedule more,
 | 
						|
which has overhead.
 | 
						|
 | 
						|
<p>What if the environment computes for 1 msec and sends an IPC to
 | 
						|
the file server environment?  Shouldn't the file server get more CPU
 | 
						|
time because it operates on behalf of all other functions?
 | 
						|
 | 
						|
<p>Potential improvements for the 6.828 kernel: track "recent" CPU use
 | 
						|
(e.g., over the last second) and always run environment with least
 | 
						|
recent CPU use.  (Still, if you sleep long enough you lose.) Other
 | 
						|
solution: directed yield; specify on the yield to which environment
 | 
						|
you are donating the remainder of the quantuam (e.g., to the file
 | 
						|
server so that it can compute on the environment's behalf).
 | 
						|
 | 
						|
<li>Pitfall: Priority Inversion
 | 
						|
<pre>
 | 
						|
  Assume policy is strict priority.
 | 
						|
  Thread T1: low priority.
 | 
						|
  Thread T2: medium priority.
 | 
						|
  Thread T3: high priority.
 | 
						|
  T1: acquire(l)
 | 
						|
  context switch to T3
 | 
						|
  T3: acquire(l)... must wait for T1 to release(l)...
 | 
						|
  context switch to T2
 | 
						|
  T2 computes for a while
 | 
						|
  T3 is indefinitely delayed despite high priority.
 | 
						|
  Can solve if T3 lends its priority to holder of lock it is waiting for.
 | 
						|
    So T1 runs, not T2.
 | 
						|
  [this is really a multiple scheduler problem.]
 | 
						|
  [since locks schedule access to locked resource.]
 | 
						|
</pre>
 | 
						|
 | 
						|
<li>Pitfall: Efficiency.  Efficiency often conflicts with fairness (or
 | 
						|
any other policy).  Long time quantum for efficiency in CPU scheduling
 | 
						|
versus low delay.  Shortest seek versus FIFO disk scheduling.
 | 
						|
Contiguous read-ahead vs data needed now.  For example, scheduler
 | 
						|
swaps out my idle emacs to let gcc run faster with more phys mem.
 | 
						|
What happens when I type a key?  These don't fit well into a "who gets
 | 
						|
to go next" scheduler framework.  Inefficient scheduling may make
 | 
						|
<i>everybody</i> slower, including high priority users.
 | 
						|
 | 
						|
<li>Pitfall: Multiple Interacting Schedulers. Suppose you want your
 | 
						|
emacs to have priority over everything else.  Give it high CPU
 | 
						|
priority.  Does that mean nothing else will run if emacs wants to run?
 | 
						|
Disk scheduler might not know to favor emacs's disk I/Os.  Typical
 | 
						|
UNIX disk scheduler favors disk efficiency, not process prio.  Suppose
 | 
						|
emacs needs more memory.  Other processes have dirty pages; emacs must
 | 
						|
wait.  Does disk scheduler know these other processes' writes are high
 | 
						|
prio?
 | 
						|
 | 
						|
<li>Pitfall: Server Processes.  Suppose emacs uses X windows to
 | 
						|
display.  The X server must serve requests from many clients.  Does it
 | 
						|
know that emacs' requests should be given priority?  Does the OS know
 | 
						|
to raise X's priority when it is serving emacs?  Similarly for DNS,
 | 
						|
and NFS.  Does the network know to give emacs' NFS requests priority?
 | 
						|
 | 
						|
</ul>
 | 
						|
 | 
						|
<p>In short, scheduling is a system problem. There are many
 | 
						|
schedulers; they interact.  The CPU scheduler is usually the easy
 | 
						|
part.  The hardest part is system structure.  For example, the
 | 
						|
<i>existence</i> of interrupts is bad for scheduling.  Conflicting
 | 
						|
goals may limit effectiveness.
 | 
						|
 | 
						|
<h2>Case study: modern UNIX</h2>
 | 
						|
 | 
						|
<p>Goals: 
 | 
						|
<ul>
 | 
						|
<li>Simplicity (e.g. avoid complex locking regimes).  
 | 
						|
<li>Quick response to device interrupts.  
 | 
						|
<li> Favor interactive response.  
 | 
						|
</ul> 
 | 
						|
 | 
						|
<p>UNIX has a number of execution environments.  We care about
 | 
						|
scheduling transitions among them.  Some transitions aren't possible,
 | 
						|
some can't be be controlled. The execution environments are:
 | 
						|
 | 
						|
<ul>
 | 
						|
<li>Process, user half
 | 
						|
<li>Process, kernel half
 | 
						|
<li>Soft interrupts: timer, network
 | 
						|
<li>Device interrupts
 | 
						|
</ul>
 | 
						|
 | 
						|
<p>The rules are:
 | 
						|
<ul>
 | 
						|
<li>User is pre-emptible.
 | 
						|
<li>Kernel half and software interrupts are not pre-emptible.
 | 
						|
<li>Device handlers may not make blocking calls (e.g., sleep)
 | 
						|
<li>Effective priorities: intr > soft intr > kernel half > user
 | 
						|
</ul>  
 | 
						|
 | 
						|
</ul>
 | 
						|
 | 
						|
<p>Rules are implemented as follows:
 | 
						|
 | 
						|
<ul>
 | 
						|
 | 
						|
<li>UNIX: Process User Half.  Runs in process address space, on
 | 
						|
per-process stack.  Interruptible.  Pre-emptible: interrupt may cause
 | 
						|
context switch.  We don't trust user processes to yield CPU.
 | 
						|
Voluntarily enters kernel half via system calls and faults.
 | 
						|
 | 
						|
<li>UNIX: Process Kernel Half. Runs in kernel address space, on
 | 
						|
per-process kernel stack.  Executes system calls and faults for its
 | 
						|
process.  Interruptible (but can defer interrupts in critical
 | 
						|
sections).  Not pre-emptible.  Only yields voluntarily, when waiting
 | 
						|
for an event. E.g. disk I/O done.  This simplifies concurrency
 | 
						|
control; locks often not required.  No user process runs if any kernel
 | 
						|
half wants to run.  Many process' kernel halfs may be sleeping in the
 | 
						|
kernel.
 | 
						|
 | 
						|
<li>UNIX: Device Interrupts. Hardware asks CPU for an interrupt to ask
 | 
						|
for attention.  Disk read/write completed, or network packet received.
 | 
						|
Runs in kernel space, on special interrupt stack.  Interrupt routine
 | 
						|
cannot block; must return.  Interrupts are interruptible.  They nest
 | 
						|
on the one interrupt stack.  Interrupts are not pre-emptible, and
 | 
						|
cannot really yield.  The real-time clock is a device and interrupts
 | 
						|
every 10ms (or whatever).  Process scheduling decisions can be made
 | 
						|
when interrupt returns (e.g. wake up the process waiting for this
 | 
						|
event).  You want interrupt processing to be fast, since it has
 | 
						|
priority.  Don't do any more work than you have to.  You're blocking
 | 
						|
processes and other interrupts.  Typically, an interrupt does the
 | 
						|
minimal work necessary to keep the device happy, and then call wakeup
 | 
						|
on a thread.
 | 
						|
 | 
						|
<li>UNIX: Soft Interrupts.  (Didn't exist in xv6) Used when device
 | 
						|
handling is expensive.  But no obvious process context in which to
 | 
						|
run.  Examples include IP forwarding, TCP input processing.  Runs in
 | 
						|
kernel space, on interrupt stack.  Interruptable.  Not pre-emptable,
 | 
						|
can't really yield.  Triggered by hardware interrupt.  Called when
 | 
						|
outermost hardware interrupt returns.  Periodic scheduling decisions
 | 
						|
are made in timer s/w interrupt.  Scheduled by hardware timer
 | 
						|
interrupt (i.e., if current process has run long enough, switch).
 | 
						|
</ul>
 | 
						|
 | 
						|
<p>Is this good software structure?  Let's talk about receive
 | 
						|
livelock.
 | 
						|
 | 
						|
<h2>Paper discussion</h2>
 | 
						|
 | 
						|
<ul>
 | 
						|
 | 
						|
<li>What is application that the paper is addressing: IP forwarding.
 | 
						|
What functionality does a network interface offer to driver?
 | 
						|
<ul>
 | 
						|
<li> Read packets
 | 
						|
<li> Poke hardware to send packets
 | 
						|
<li> Interrupts when packet received/transmit complete
 | 
						|
<li> Buffer many input packets
 | 
						|
</ul>
 | 
						|
 | 
						|
<li>What devices in the 6.828 kernel are interrupt driven?  Which one
 | 
						|
are polling?  Is this ideal?
 | 
						|
 | 
						|
<li>Explain Figure 6-1.  Why does it go up?  What determines how high
 | 
						|
the peak is?  Why does it go down?  What determines how fast it goes
 | 
						|
does? Answer:
 | 
						|
<pre>
 | 
						|
(fraction of packets discarded)(work invested in discarded packets)
 | 
						|
           -------------------------------------------
 | 
						|
              (total work CPU is capable of)
 | 
						|
</pre>
 | 
						|
 | 
						|
<li>Suppose I wanted to test an NFS server for livelock.
 | 
						|
<pre>
 | 
						|
  Run client with this loop:
 | 
						|
    while(1){
 | 
						|
      send NFS READ RPC;
 | 
						|
      wait for response;
 | 
						|
    }
 | 
						|
</pre>
 | 
						|
What would I see?  Is the NFS server probably subject to livelock?
 | 
						|
(No--offered load subject to feedback).
 | 
						|
 | 
						|
<li>What other problems are we trying to address?
 | 
						|
<ul> 
 | 
						|
<li>Increased latency for packet delivery and forwarding (e.g., start
 | 
						|
disk head moving  when first NFS read request comes)
 | 
						|
<li>Transmit starvation 
 | 
						|
<li>User-level CPU starvation
 | 
						|
</ul>
 | 
						|
 | 
						|
<li>Why not tell the O/S scheduler to give interrupts lower priority?
 | 
						|
Non-preemptible.
 | 
						|
Could you fix this by making interrupts faster? (Maybe, if coupled
 | 
						|
with some limit on input rate.)  
 | 
						|
 | 
						|
<li>Why not completely process each packet in the interrupt handler?
 | 
						|
(I.e. forward it?) Other parts of kernel don't expect to run at high
 | 
						|
interrupt-level (e.g., some packet processing code might invoke a function
 | 
						|
that sleeps). Still might want an output queue
 | 
						|
 | 
						|
<li>What about using polling instead of interrupts?  Solves overload
 | 
						|
problem, but killer for latency.
 | 
						|
 | 
						|
<li>What's the paper's solution?
 | 
						|
<ul>
 | 
						|
<li>No IP input queue.
 | 
						|
<li>Input processing and device input polling in kernel thread.
 | 
						|
<li>Device receive interrupt just wakes up thread. And leaves
 | 
						|
interrupts *disabled* for that device.
 | 
						|
<li>Thread does all input processing, then re-enables interrupts.
 | 
						|
</ul>
 | 
						|
<p>Why does this work?  What happens when packets arrive too fast?
 | 
						|
What happens when packets arrive slowly?
 | 
						|
 | 
						|
<li>Explain Figure 6-3.
 | 
						|
<ul>
 | 
						|
<li>Why does "Polling (no quota)" work badly? (Input still starves
 | 
						|
xmit complete processing.)
 | 
						|
<li>Why does it immediately fall to zero, rather than gradually decreasing?
 | 
						|
(xmit complete processing must be very cheap compared to input.)
 | 
						|
</ul>
 | 
						|
 | 
						|
<li>Explain Figure 6-4.
 | 
						|
<ul>
 | 
						|
 | 
						|
<li>Why does "Polling, no feedback" behave badly? There's a queue in
 | 
						|
front of screend.  We can still give 100% to input thread, 0% to
 | 
						|
screend.
 | 
						|
 | 
						|
<li>Why does "Polling w/ feedback" behave well? Input thread yields
 | 
						|
when queue to screend fills.
 | 
						|
 | 
						|
<li>What if screend hangs, what about other consumers of packets?
 | 
						|
(e.g., can you ssh to machine to fix screend?)  Fortunately screend
 | 
						|
typically is only application. Also, re-enable input after timeout.
 | 
						|
 | 
						|
</ul>
 | 
						|
 | 
						|
<li>Why are the two solutions different?
 | 
						|
<ol>
 | 
						|
<li> Polling thread <i>with quotas</i>.
 | 
						|
<li> Feedback from full queue.
 | 
						|
</ol>
 | 
						|
(I believe they should have used #2 for both.)
 | 
						|
 | 
						|
<li>If we apply the proposed fixes, does the phenomemon totally go
 | 
						|
 away? (e.g. for web server, waits for disk, &c.)  
 | 
						|
<ul>
 | 
						|
<li>Can the net device throw away packets without slowing down host?
 | 
						|
<li>Problem: We want to drop packets for applications with big queues.
 | 
						|
But requires work to determine which application a packet belongs to
 | 
						|
Solution: NI-LRP (have network interface sort packets)
 | 
						|
</ul>
 | 
						|
 | 
						|
<li>What about latency question?  (Look at figure 14 p. 243.)
 | 
						|
<ul>
 | 
						|
<li>1st packet looks like an improvement over non-polling. But 2nd
 | 
						|
packet transmitted later with poling.  Why? (No new packets added to
 | 
						|
xmit buffer until xmit interrupt) 
 | 
						|
<li>Why?  In traditional BSD, to
 | 
						|
amortize cost of poking device. Maybe better to poke a second time
 | 
						|
anyway.
 | 
						|
</ul>
 | 
						|
 | 
						|
<li>What if processing has more complex structure?
 | 
						|
<ul>
 | 
						|
<li>Chain of processing stages with queues? Does feedback work?
 | 
						|
    What happens when a late stage is slow?
 | 
						|
<li>Split at some point, multiple parallel paths? No so great; one
 | 
						|
    slow path blocks all paths.
 | 
						|
</ul>
 | 
						|
 | 
						|
<li>Can we formulate any general principles from paper?
 | 
						|
<ul>
 | 
						|
<li>Don't spend time on new work before completing existing work.
 | 
						|
<li>Or give new work lower priority than partially-completed work.
 | 
						|
</ul>
 | 
						|
 | 
						|
</ul>
 |