Subversion Repositories HelenOS-doc

Rev

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

Rev Author Line No. Line
9 bondari 1
<?xml version="1.0" encoding="UTF-8"?>
57 jermar 2
<chapter id="scheduling">
3
  <?dbhtml filename="scheduling.html"?>
9 bondari 4
 
57 jermar 5
  <title>Scheduling</title>
9 bondari 6
 
57 jermar 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
58 jermar 45
    context switches.</para>
57 jermar 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
 
9 bondari 73
    <section>
57 jermar 74
      <title>Synchronous context switches</title>
9 bondari 75
 
57 jermar 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>
9 bondari 102
    </section>
57 jermar 103
  </section>
9 bondari 104
 
57 jermar 105
  <section>
58 jermar 106
    <title>Threads</title>
9 bondari 107
 
58 jermar 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
 
61 jermar 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
 
58 jermar 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>
59 jermar 161
  </section>
58 jermar 162
 
59 jermar 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>
57 jermar 249
  </section>
250
</chapter>