Subversion Repositories HelenOS-doc

Rev

Rev 59 | Rev 62 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <chapter id="scheduling">
  3.   <?dbhtml filename="scheduling.html"?>
  4.  
  5.   <title>Scheduling</title>
  6.  
  7.   <para>One of the key aims of the operating system is to create and support
  8.   the impression that several activities are executing contemporarily. This is
  9.   true for both uniprocessor as well as multiprocessor systems. In the case of
  10.   multiprocessor systems, the activities are trully happening in parallel. The
  11.   scheduler helps to materialize this impression by planning threads on as
  12.   many processors as possible and, where this means reaches its limits, by
  13.   quickly switching among threads executing on a single processor.</para>
  14.  
  15.   <section>
  16.     <title>Contexts</title>
  17.  
  18.     <para>The term context refers to the set of processor resources that
  19.     define the current state of the computation or the environment and the
  20.     kernel understands it in several more or less narrow sences:</para>
  21.  
  22.     <itemizedlist>
  23.       <listitem>
  24.         <para>synchronous register context,</para>
  25.       </listitem>
  26.  
  27.       <listitem>
  28.         <para>asynchronous register context,</para>
  29.       </listitem>
  30.  
  31.       <listitem>
  32.         <para>FPU context and</para>
  33.       </listitem>
  34.  
  35.       <listitem>
  36.         <para>memory management context.</para>
  37.       </listitem>
  38.     </itemizedlist>
  39.  
  40.     <para>The most narrow sence refers to the the synchronous register
  41.     context. It includes all the preserved registers as defined by the
  42.     architecture. To highlight some, the program counter and stack pointer
  43.     take part in the synchronous register context. These are the registers
  44.     that must be preserved across a procedure call and during synchronous
  45.     context switches.</para>
  46.  
  47.     <para>The next type of the context understood by the kernel is the
  48.     asynchronous register context. On an interrupt, the interrupted execution
  49.     flow's state must be guaranteed to be eventually completely restored.
  50.    Therefore the interrupt context includes, among other things, the scratch
  51.    registers as defined by the architecture. As a special optimization and if
  52.    certain conditions are met, it need not include the architecture's
  53.     preserved registers. The condition mentioned in the previous sentence is
  54.     that the low-level assembly language interrupt routines don't modify the
  55.    preserved registers. The handlers usually call a higher-level C routine.
  56.    The preserved registers are then saved on the stack by the compiler
  57.    generated code of the higher-level function. In HelenOS, several
  58.    architectures can be compiled with this optimization.</para>
  59.  
  60.    <para>Although the kernel does not do any floating point
  61.    arithmetics<footnote>
  62.        <para>Some architectures (e.g. ia64) inevitably use a fixed set of
  63.        floating point registers to carry out its normal operations.</para>
  64.      </footnote>, it must protect FPU context of userspace threads against
  65.    destruction by other threads. Moreover, only a fraction of userspace
  66.    programs use the floating point unit. HelenOS contains a generic framework
  67.    for switching FPU context only when the switch is forced.</para>
  68.  
  69.    <para>The last member of the context family is the memory management
  70.    context. It includes memory management registers that identify address
  71.    spaces on hardware level (i.e. ASIDs and page tables pointers).</para>
  72.  
  73.    <section>
  74.      <title>Synchronous context switches</title>
  75.  
  76.      <para>The scheduler, but also other pieces of the kernel, make heavy use
  77.      of synchronous context switches, because it is a natural vehicle not
  78.      only for changes in control flow, but also for switching between two
  79.      kernel stacks. Two functions figure in a synchronous context switch
  80.      implementation: <code>context_save</code> and
  81.      <code>context_restore</code>. Note that these two functions break the
  82.      natural perception of the linear C code execution flow starting at
  83.      function's entry point and ending on one of the function's exit
  84.      points.</para>
  85.  
  86.      <para>When the <code>context_save</code> function is called, the
  87.      synchronous context is saved in a memory structure passed to it. After
  88.      executing <code>context_save</code>, the caller is returned 1 as a
  89.      return value. The execution of instructions continues as normally until
  90.      <code>context_restore</code> is called. For the caller, it seems like
  91.      the call never returns<footnote>
  92.          <para>Which might be a source of problems with variable liveliness
  93.          after <code>context_restore</code>.</para>
  94.        </footnote>. Nevertheless, a synchronous register context, which is
  95.      saved in a memory structure passed to <code>context_restore,</code> is
  96.      restored, thus transfering the control flow to the place of occurrence
  97.      of the corresponding call to <code>context_save</code>. From the
  98.      perspective of the caller of the corresponding
  99.      <code>context_save</code>, it looks as though a return from
  100.      <code>context_save</code>. However, this time a return value of 0 is
  101.      returned.</para>
  102.    </section>
  103.  </section>
  104.  
  105.  <section>
  106.    <title>Threads</title>
  107.  
  108.    <para>A thread is the basic executable entity with some code and stack.
  109.    While the code, implemented by a C language function, can be shared by
  110.    several threads, the stack is always private to each instance of the
  111.    thread. Each thread belongs to exactly one task through which it shares
  112.    address space with its sibling threads. Threads that execute purely in the
  113.    kernel don't have any userspace memory allocated. However, when a thread
  114.     has ambitions to run in userspace, it must be allocated a userspace stack.
  115.     The distinction between the purely kernel threads and threads running also
  116.     in userspace is made by refering to the former group as to kernel threads
  117.     and to the latter group as to userspace threads. Both kernel and userspace
  118.     threads are visible to the scheduler and can become a subject of kernel
  119.     preemption and thread migration during times when preemption is
  120.     possible.</para>
  121.  
  122.     <para>
  123.     <mediaobject id="thread_states" xreflabel="">
  124.         <imageobject role="html">
  125.            <imagedata fileref="images/thread_states.png" format="PNG" />
  126.     </imageobject>
  127.            
  128.     <imageobject role="fop">
  129.             <imagedata fileref="images.vector/thread_states.svg" format="SVG" />
  130.     </imageobject>
  131.                    
  132.     <caption>Transitions among thread states.</caption>
  133.     </mediaobject>
  134.      </para>
  135.                        
  136.  
  137.     <para>HelenOS userspace layer knows even smaller units of execution. Each
  138.     userspace thread can make use of an arbitrary number of pseudo threads.
  139.     These pseudo threads have their own synchronous register context,
  140.     userspace code and stack. They live their own life within the userspace
  141.     thread and the scheduler does not have any idea about them because they
  142.     are completely implemented by the userspace library. This implies several
  143.     things:</para>
  144.  
  145.     <itemizedlist>
  146.       <listitem>
  147.         <para>pseudothreads schedule themselves cooperatively within the time
  148.         slice given to their userspace thread,</para>
  149.       </listitem>
  150.  
  151.       <listitem>
  152.         <para>pseudothreads share FPU context of their containing thread
  153.         and</para>
  154.       </listitem>
  155.  
  156.       <listitem>
  157.         <para>all pseudothreads of one userspace thread block when one of them
  158.         goes to sleep.</para>
  159.       </listitem>
  160.     </itemizedlist>
  161.   </section>
  162.  
  163.   <section>
  164.     <title>Scheduler</title>
  165.  
  166.     <section>
  167.       <title>Run queues</title>
  168.  
  169.       <para>There is an array of several run queues on each processor. The
  170.       current version of HelenOS uses 16 run queues implemented by 16 doubly
  171.       linked lists. Each of the run queues is associated with thread priority.
  172.       The lower the run queue index in the array is, the higher is the
  173.       priority of threads linked in that run queue and the shorter is the time
  174.       in which those threads will execute. When kernel code wants to access
  175.       the run queue, it must first acquire its lock.</para>
  176.     </section>
  177.  
  178.     <section>
  179.       <title>Scheduler operation</title>
  180.  
  181.       <para>The scheduler is invoked either explicitly when a thread calls the
  182.       <code>scheduler</code> function (e.g. goes to sleep or merely wants to
  183.       relinquish the processor for a while) or implicitly on a periodic basis
  184.       when the generic clock interrupt preempts the current thread. After its
  185.       invocation, the scheduler saves the synchronous register context of the
  186.       current thread and switches to its private stack. Afterwards, a new
  187.       thread is selected according to the scheduling policy. If there is no
  188.       suitable thread, the processor is idle and no thread executes on it.
  189.       Note that the act of switching to the private scheduler stack is
  190.       essential. If the processor kept running using the stack of the
  191.       preempted thread it could damage it because the old thread can be
  192.       migrated to another processor and scheduled there. In the worst case
  193.       scenario, two execution flows would be using the same stack. </para>
  194.  
  195.       <para>The scheduling policy is implemented in function
  196.       <code>find_best_thread</code>. This function walks the processor run
  197.       queues from lower towards higher indices and looks for a thread. If the
  198.       visited run queue is empty, it simply searches the next run queue. If it
  199.       is known in advance that there are no ready threads waiting for
  200.       execution, <code>find_best_thread</code> interruptibly halts the
  201.       processor or busy waits until some threads arrive. This process repeats
  202.       until <code>find_best_thread</code> succeeds.</para>
  203.  
  204.       <para>After the best thread is chosen, the scheduler switches to the
  205.       thread's task and memory management context. Finally, the saved
  206.      synchronous register context is restored and the thread runs. Each
  207.      scheduled thread is given a time slice depending on its priority (i.e.
  208.      run queue). The higher priority, the shorter timeslice. To summarize,
  209.      this policy schedules threads with high priorities more frequently but
  210.      gives them smaller time slices. On the other hand, lower priority
  211.      threads are scheduled less frequently, but run for longer periods of
  212.      time.</para>
  213.  
  214.      <para>When a thread uses its entire time slice, it is preempted and put
  215.      back into the run queue that immediately follows the previous run queue
  216.      from which the thread ran. Threads that are woken up from a sleep are
  217.      put into the biggest priority run queue. Low priority threads are
  218.      therefore those that don't go to sleep so often and just occupy the
  219.       processor.</para>
  220.  
  221.       <para>In order to avoid complete starvation of the low priority threads,
  222.       from time to time, the scheduler will provide them with a bonus of one
  223.       point priority increase. In other words, the scheduler will now and then
  224.       move the entire run queues one level up.</para>
  225.     </section>
  226.  
  227.     <section>
  228.       <title>Processor load balancing</title>
  229.  
  230.       <para>Normally, for the sake of cache locality, threads are scheduled on
  231.       one of the processors and don't leave it. Nevertheless, a situation in
  232.      which one processor is heavily overloaded while others sit idle can
  233.      occur. HelenOS deploys special kernel threads to help to mitigate this
  234.      problem. Each processor is associated with one load balancing thread
  235.      called <code>kcpulb</code> that wakes up regularily to see whether its
  236.      processor is underbalanced or not. If yes, the thread attempts to
  237.      migrate threads from other overloaded processors to its own processor's
  238.       run queues. When the job is done or there is no need for load balancing,
  239.       the thread goes to sleep.</para>
  240.  
  241.       <para>The balancing threads operate very gently and try to migrate low
  242.       priority threads first; one <code>kcpulb</code> never takes from one
  243.       processor twice in a row. The load balancing threads as well as threads
  244.       that were just stolen cannot be migrated. The <code>kcpulb</code>
  245.       threads are wired to their processors and cannot be migrated whatsoever.
  246.       The ordinary threads are protected only until they are
  247.       rescheduled.</para>
  248.     </section>
  249.   </section>
  250. </chapter>