aboutsummaryrefslogtreecommitdiffstats
path: root/litmus/sched_global_plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'litmus/sched_global_plugin.c')
-rw-r--r--litmus/sched_global_plugin.c675
1 files changed, 675 insertions, 0 deletions
diff --git a/litmus/sched_global_plugin.c b/litmus/sched_global_plugin.c
new file mode 100644
index 000000000000..22dffa7d62fc
--- /dev/null
+++ b/litmus/sched_global_plugin.c
@@ -0,0 +1,675 @@
1/*
2 * litmus/sched_global_plugin.c
3 *
4 * Implementation of the basic operations and architecture needed by
5 * G-EDF/G-FIFO/EDZL/AEDZL global schedulers.
6 *
7 * This version uses the simple approach and serializes all scheduling
8 * decisions by the use of a queue lock. This is probably not the
9 * best way to do it, but it should suffice for now.
10 */
11
12#include <linux/spinlock.h>
13#include <linux/percpu.h>
14#include <linux/sched.h>
15
16#include <litmus/litmus.h>
17#include <litmus/jobs.h>
18#include <litmus/sched_global_plugin.h>
19#include <litmus/sched_trace.h>
20
21#include <litmus/preempt.h>
22
23#include <linux/module.h>
24
25
26/* Overview of Global operations.
27 *
28 * gbl_link_task_to_cpu(T, cpu) - Low-level operation to update the linkage
29 * structure (NOT the actually scheduled
30 * task). If there is another linked task To
31 * already it will set To->linked_on = NO_CPU
32 * (thereby removing its association with this
33 * CPU). However, it will not requeue the
34 * previously linked task (if any). It will set
35 * T's state to RT_F_RUNNING and check whether
36 * it is already running somewhere else. If T
37 * is scheduled somewhere else it will link
38 * it to that CPU instead (and pull the linked
39 * task to cpu). T may be NULL.
40 *
41 * gbl_unlink(T) - Unlink removes T from all scheduler data
42 * structures. If it is linked to some CPU it
43 * will link NULL to that CPU. If it is
44 * currently queued in the gsnedf queue it will
45 * be removed from the rt_domain. It is safe to
46 * call gbl_unlink(T) if T is not linked. T may not
47 * be NULL.
48 *
49 * gbl_requeue(T) - Requeue will insert T into the appropriate
50 * queue. If the system is in real-time mode and
51 * the T is released already, it will go into the
52 * ready queue. If the system is not in
53 * real-time mode is T, then T will go into the
54 * release queue. If T's release time is in the
55 * future, it will go into the release
56 * queue. That means that T's release time/job
57 * no/etc. has to be updated before requeu(T) is
58 * called. It is not safe to call gbl_requeue(T)
59 * when T is already queued. T may not be NULL.
60 *
61 * job_arrival(T) - This is the catch all function when T enters
62 * the system after either a suspension or at a
63 * job release. It will queue T (which means it
64 * is not safe to call job_arrival(T) if
65 * T is already queued) and then check whether a
66 * preemption is necessary. If a preemption is
67 * necessary it will update the linkage
68 * accordingly and cause scheduled to be called
69 * (either with an IPI or need_resched). It is
70 * safe to call job_arrival(T) if T's
71 * next job has not been actually released yet
72 * (releast time in the future). T will be put
73 * on the release queue in that case.
74 *
75 * job_completion(T) - Take care of everything that needs to be done
76 * to prepare T for its next release and place
77 * it in the right queue with
78 * job_arrival().
79 *
80 *
81 * When we now that T is linked to CPU then gbl_link_task_to_cpu(NULL, CPU) is
82 * equivalent to gbl_unlink(T). Note that if you unlink a task from a CPU none of
83 * the functions will automatically propagate pending task from the ready queue
84 * to a linked task. This is the job of the calling function (by means of
85 * __take_ready).
86 */
87
88/* Uncomment this if you want to see all scheduling decisions in the
89 * TRACE() log.
90 #define WANT_ALL_SCHED_EVENTS
91 */
92
93
94/* Macros to access the current active global plugin. These are
95 * a lot like C++'s 'this' pointer.
96 */
97struct sched_global_plugin* active_gbl_plugin;
98#define active_gbl_domain (active_gbl_plugin->domain)
99#define active_gbl_domain_lock (active_gbl_domain.ready_lock)
100
101
102/*********************************************************************/
103/* "Member" functions for both sched_plugin and sched_global_plugin. */
104/* NOTE: These will automatically call down into "virtual" functions.*/
105/*********************************************************************/
106
107/* Priority-related functions */
108int gbl_preemption_needed(struct task_struct *t)
109{
110 /* we need the read lock for active_gbl_domain's ready_queue */
111 /* no need to preempt if there is nothing pending */
112 if (!__jobs_pending(&active_gbl_domain))
113 return 0;
114 /* we need to reschedule if t doesn't exist */
115 if (!t)
116 return 1;
117
118 /* NOTE: We cannot check for non-preemptibility since we
119 * don't know what address space we're currently in.
120 */
121
122 /* make sure to get non-rt stuff out of the way */
123 return !is_realtime(t) || active_gbl_plugin->prio_order(__next_ready(&active_gbl_domain), t);
124}
125
126int gbl_ready_order(struct bheap_node* a, struct bheap_node* b)
127{
128 return active_gbl_plugin->prio_order(bheap2task(a), bheap2task(b));
129}
130
131
132
133int gbl_cpu_lower_prio(struct bheap_node *_a, struct bheap_node *_b)
134{
135 cpu_entry_t *a, *b;
136 a = _a->value;
137 b = _b->value;
138
139 /* Note that a and b are inverted: we want the lowest-priority CPU at
140 * the top of the heap.
141 */
142 return active_gbl_plugin->prio_order(b->linked, a->linked);
143}
144
145/* gbl_update_cpu_position - Move the cpu entry to the correct place to maintain
146 * order in the cpu queue. Caller must hold gbl_domain_lock.
147 */
148void gbl_update_cpu_position(cpu_entry_t *entry)
149{
150 if (likely(bheap_node_in_heap(entry->hn)))
151 bheap_delete(gbl_cpu_lower_prio, &active_gbl_plugin->cpu_heap, entry->hn);
152 bheap_insert(gbl_cpu_lower_prio, &active_gbl_plugin->cpu_heap, entry->hn);
153}
154
155/* caller must hold gsnedf lock */
156cpu_entry_t* lowest_prio_cpu(void)
157{
158 struct bheap_node* hn;
159 hn = bheap_peek(gbl_cpu_lower_prio, &active_gbl_plugin->cpu_heap);
160 return hn->value;
161}
162
163
164/* link_task_to_cpu - Update the link of a CPU.
165 * Handles the case where the to-be-linked task is already
166 * scheduled on a different CPU.
167 */
168void gbl_link_task_to_cpu(struct task_struct* linked,
169 cpu_entry_t *entry)
170{
171 cpu_entry_t *sched;
172 struct task_struct* tmp;
173 int on_cpu;
174
175 BUG_ON(linked && !is_realtime(linked));
176
177 /* Currently linked task is set to be unlinked. */
178 if (entry->linked) {
179 entry->linked->rt_param.linked_on = NO_CPU;
180 }
181
182 /* Link new task to CPU. */
183 if (linked) {
184 set_rt_flags(linked, RT_F_RUNNING);
185 /* handle task is already scheduled somewhere! */
186 on_cpu = linked->rt_param.scheduled_on;
187 if (on_cpu != NO_CPU) {
188 sched = active_gbl_plugin->cpus[on_cpu];
189 /* this should only happen if not linked already */
190 BUG_ON(sched->linked == linked);
191
192 /* If we are already scheduled on the CPU to which we
193 * wanted to link, we don't need to do the swap --
194 * we just link ourselves to the CPU and depend on
195 * the caller to get things right.
196 */
197 if (entry != sched) {
198 TRACE_TASK(linked,
199 "already scheduled on %d, updating link.\n",
200 sched->cpu);
201 tmp = sched->linked;
202 linked->rt_param.linked_on = sched->cpu;
203 sched->linked = linked;
204 gbl_update_cpu_position(sched);
205 linked = tmp;
206 }
207 }
208 if (linked) /* might be NULL due to swap */
209 linked->rt_param.linked_on = entry->cpu;
210 }
211 entry->linked = linked;
212#ifdef WANT_ALL_SCHED_EVENTS
213 if (linked)
214 TRACE_TASK(linked, "linked to %d.\n", entry->cpu);
215 else
216 TRACE("NULL linked to %d.\n", entry->cpu);
217#endif
218 gbl_update_cpu_position(entry);
219}
220
221/* unlink - Make sure a task is not linked any longer to an entry
222 * where it was linked before. Must hold
223 * active_gbl_domain_lock.
224 */
225void gbl_unlink(struct task_struct* t)
226{
227 cpu_entry_t *entry;
228
229 if (t->rt_param.linked_on != NO_CPU) {
230 /* unlink */
231 entry = active_gbl_plugin->cpus[t->rt_param.linked_on];
232 t->rt_param.linked_on = NO_CPU;
233 gbl_link_task_to_cpu(NULL, entry);
234 } else if (is_queued(t)) {
235 /* This is an interesting situation: t is scheduled,
236 * but was just recently unlinked. It cannot be
237 * linked anywhere else (because then it would have
238 * been relinked to this CPU), thus it must be in some
239 * queue. We must remove it from the list in this
240 * case.
241 */
242 remove(&active_gbl_domain, t);
243 }
244}
245
246
247/* preempt - force a CPU to reschedule
248 */
249void gbl_preempt(cpu_entry_t *entry)
250{
251 preempt_if_preemptable(entry->scheduled, entry->cpu);
252}
253
254/* requeue - Put an unlinked task into global domain.
255 * Caller must hold active_gbl_domain.
256 */
257void gbl_requeue(struct task_struct* task)
258{
259 BUG_ON(!task);
260 /* sanity check before insertion */
261 BUG_ON(is_queued(task));
262
263 if (is_released(task, litmus_clock()))
264 active_gbl_plugin->add_ready(&active_gbl_domain, task);
265 else {
266 /* it has got to wait */
267 add_release(&active_gbl_domain, task);
268 }
269}
270
271
272/* check for any necessary preemptions */
273void gbl_check_for_preemptions(void)
274{
275 struct task_struct *task;
276 cpu_entry_t* last;
277
278 for(last = lowest_prio_cpu();
279 gbl_preemption_needed(last->linked);
280 last = lowest_prio_cpu())
281 {
282 /* preemption necessary */
283 task = active_gbl_plugin->take_ready(&active_gbl_domain);
284 TRACE("check_for_preemptions: attempting to link task %d to %d\n",
285 task->pid, last->cpu);
286 if (last->linked)
287 gbl_requeue(last->linked);
288 gbl_link_task_to_cpu(task, last);
289 gbl_preempt(last);
290 }
291}
292
293
294void gbl_release_jobs(rt_domain_t* rt, struct bheap* tasks)
295{
296 unsigned long flags;
297
298 raw_spin_lock_irqsave(&active_gbl_domain_lock, flags);
299
300 __merge_ready(rt, tasks);
301 gbl_check_for_preemptions();
302
303 raw_spin_unlock_irqrestore(&active_gbl_domain_lock, flags);
304}
305
306/* caller holds active_gbl_domain_lock */
307void gbl_job_completion(struct task_struct *t, int forced)
308{
309 BUG_ON(!t);
310
311 sched_trace_task_completion(t, forced);
312
313 TRACE_TASK(t, "job_completion().\n");
314
315 /* set flags */
316 set_rt_flags(t, RT_F_SLEEP);
317 /* prepare for next period */
318 prepare_for_next_period(t);
319 if (is_released(t, litmus_clock()))
320 sched_trace_task_release(t);
321 /* unlink */
322 gbl_unlink(t);
323 /* requeue
324 * But don't requeue a blocking task. */
325 if (is_running(t))
326 active_gbl_plugin->job_arrival(t);
327}
328
329
330/*********************************************************************/
331/* These two functions can't use active_* defines since the 'litmus' */
332/* pointer is undefined/invalid when these are called. Think of them */
333/* as static member functions. */
334/*********************************************************************/
335
336void gbl_domain_init(struct sched_global_plugin* gbl_plugin,
337 check_resched_needed_t resched,
338 release_jobs_t release)
339{
340 rt_domain_init(&gbl_plugin->domain, gbl_ready_order, resched, release);
341}
342
343
344long gbl_activate_plugin(void* plg)
345{
346 struct sched_plugin* plugin = (struct sched_plugin*)plg;
347 int cpu;
348 cpu_entry_t *entry;
349
350 /* set the active global plugin */
351 active_gbl_plugin =
352 container_of(plugin,
353 struct sched_global_plugin,
354 plugin);
355
356 bheap_init(&active_gbl_plugin->cpu_heap);
357#ifdef CONFIG_RELEASE_MASTER
358 active_gbl_domain.release_master = atomic_read(&release_master_cpu);
359#endif
360
361 for_each_online_cpu(cpu) {
362 entry = active_gbl_plugin->cpus[cpu];
363 bheap_node_init(&entry->hn, entry);
364 entry->linked = NULL;
365 entry->scheduled = NULL;
366#ifdef CONFIG_RELEASE_MASTER
367 if (cpu != active_gbl_domain.release_master) {
368#endif
369 TRACE("Global Plugin: Initializing CPU #%d.\n", cpu);
370 gbl_update_cpu_position(entry);
371#ifdef CONFIG_RELEASE_MASTER
372 } else {
373 TRACE("Global Plugin: CPU %d is release master.\n", cpu);
374 }
375#endif
376 }
377 return 0;
378}
379
380
381/********************************************************************/
382/* "Virtual" functions in both sched_plugin and sched_global_plugin */
383/********************************************************************/
384
385
386/* gbl_job_arrival: task is either resumed or released */
387void gblv_job_arrival(struct task_struct* task)
388{
389 BUG_ON(!task);
390
391 gbl_requeue(task);
392 gbl_check_for_preemptions();
393}
394
395/* gbl_tick - this function is called for every local timer interrupt.
396 *
397 * checks whether the current task has expired and checks
398 * whether we need to preempt it if it has not expired
399 */
400void gblv_tick(struct task_struct* t)
401{
402 if (is_realtime(t) && budget_enforced(t) && budget_exhausted(t)) {
403 if (!is_np(t)) {
404 /* np tasks will be preempted when they become
405 * preemptable again
406 */
407 litmus_reschedule_local();
408 TRACE("gbl_scheduler_tick: "
409 "%d is preemptable "
410 " => FORCE_RESCHED\n", t->pid);
411 } else if (is_user_np(t)) {
412 TRACE("gbl_scheduler_tick: "
413 "%d is non-preemptable, "
414 "preemption delayed.\n", t->pid);
415 request_exit_np(t);
416 }
417 }
418}
419
420/* Getting schedule() right is a bit tricky. schedule() may not make any
421 * assumptions on the state of the current task since it may be called for a
422 * number of reasons. The reasons include a scheduler_tick() determined that it
423 * was necessary, because sys_exit_np() was called, because some Linux
424 * subsystem determined so, or even (in the worst case) because there is a bug
425 * hidden somewhere. Thus, we must take extreme care to determine what the
426 * current state is.
427 *
428 * The CPU could currently be scheduling a task (or not), be linked (or not).
429 *
430 * The following assertions for the scheduled task could hold:
431 *
432 * - !is_running(scheduled) // the job blocks
433 * - scheduled->timeslice == 0 // the job completed (forcefully)
434 * - get_rt_flag() == RT_F_SLEEP // the job completed (by syscall)
435 * - linked != scheduled // we need to reschedule (for any reason)
436 * - is_np(scheduled) // rescheduling must be delayed,
437 * sys_exit_np must be requested
438 *
439 * Any of these can occur together.
440 */
441struct task_struct* gblv_schedule(struct task_struct * prev)
442{
443 cpu_entry_t* entry = active_gbl_plugin->cpus[smp_processor_id()];
444 int out_of_time, sleep, preempt, np, exists, blocks;
445 struct task_struct* next = NULL;
446
447#ifdef CONFIG_RELEASE_MASTER
448 /* Bail out early if we are the release master.
449 * The release master never schedules any real-time tasks.
450 */
451 if (active_gbl_domain.release_master == entry->cpu)
452 return NULL;
453#endif
454
455 raw_spin_lock(&active_gbl_domain_lock);
456
457 /* sanity checking */
458 BUG_ON(entry->scheduled && entry->scheduled != prev);
459 BUG_ON(entry->scheduled && !is_realtime(prev));
460 BUG_ON(is_realtime(prev) && !entry->scheduled);
461
462 /* (0) Determine state */
463 exists = entry->scheduled != NULL;
464 blocks = exists && !is_running(entry->scheduled);
465 out_of_time = exists &&
466 budget_enforced(entry->scheduled) &&
467 budget_exhausted(entry->scheduled);
468 np = exists && is_np(entry->scheduled);
469 sleep = exists && get_rt_flags(entry->scheduled) == RT_F_SLEEP;
470 preempt = entry->scheduled != entry->linked;
471
472#ifdef WANT_ALL_SCHED_EVENTS
473 TRACE_TASK(prev, "invoked gsnedf_schedule.\n");
474#endif
475
476 if (exists)
477 TRACE_TASK(prev,
478 "blocks:%d out_of_time:%d np:%d sleep:%d preempt:%d "
479 "state:%d sig:%d\n",
480 blocks, out_of_time, np, sleep, preempt,
481 prev->state, signal_pending(prev));
482 if (entry->linked && preempt)
483 TRACE_TASK(prev, "will be preempted by %s/%d\n",
484 entry->linked->comm, entry->linked->pid);
485
486
487 /* If a task blocks we have no choice but to reschedule.
488 */
489 if (blocks)
490 gbl_unlink(entry->scheduled);
491
492 /* Request a sys_exit_np() call if we would like to preempt but cannot.
493 * We need to make sure to update the link structure anyway in case
494 * that we are still linked. Multiple calls to request_exit_np() don't
495 * hurt.
496 */
497 if (np && (out_of_time || preempt || sleep)) {
498 gbl_unlink(entry->scheduled);
499 request_exit_np(entry->scheduled);
500 }
501
502 /* Any task that is preemptable and either exhausts its execution
503 * budget or wants to sleep completes. We may have to reschedule after
504 * this. Don't do a job completion if we block (can't have timers running
505 * for blocked jobs). Preemption go first for the same reason.
506 */
507 if (!np && (out_of_time || sleep) && !blocks && !preempt)
508 active_gbl_plugin->job_completion(entry->scheduled, !sleep);
509
510 /* Link pending task if we became unlinked.
511 */
512 if (!entry->linked)
513 gbl_link_task_to_cpu(active_gbl_plugin->take_ready(&active_gbl_domain), entry);
514
515 /* The final scheduling decision. Do we need to switch for some reason?
516 * If linked is different from scheduled, then select linked as next.
517 */
518 if ((!np || blocks) &&
519 entry->linked != entry->scheduled) {
520 /* Schedule a linked job? */
521 if (entry->linked) {
522 entry->linked->rt_param.scheduled_on = entry->cpu;
523 next = entry->linked;
524 }
525 if (entry->scheduled) {
526 /* not gonna be scheduled soon */
527 entry->scheduled->rt_param.scheduled_on = NO_CPU;
528 TRACE_TASK(entry->scheduled, "scheduled_on = NO_CPU\n");
529 }
530 } else
531 /* Only override Linux scheduler if we have a real-time task
532 * scheduled that needs to continue.
533 */
534 if (exists)
535 next = prev;
536
537 sched_state_task_picked();
538
539 raw_spin_unlock(&active_gbl_domain_lock);
540
541#ifdef WANT_ALL_SCHED_EVENTS
542 TRACE("active_gbl_domain_lock released, next=0x%p\n", next);
543
544 if (next)
545 TRACE_TASK(next, "scheduled at %llu\n", litmus_clock());
546 else if (exists && !next)
547 TRACE("becomes idle at %llu.\n", litmus_clock());
548#endif
549
550
551 return next;
552}
553
554
555/* _finish_switch - we just finished the switch away from prev
556 */
557void gblv_finish_switch(struct task_struct *prev)
558{
559 cpu_entry_t* entry = active_gbl_plugin->cpus[smp_processor_id()];
560
561 entry->scheduled = is_realtime(current) ? current : NULL;
562#ifdef WANT_ALL_SCHED_EVENTS
563 TRACE_TASK(prev, "switched away from\n");
564#endif
565}
566
567
568/* Prepare a task for running in RT mode
569 */
570void gblv_task_new(struct task_struct * t, int on_rq, int running)
571{
572 unsigned long flags;
573 cpu_entry_t* entry;
574
575 TRACE("global plugin: task new %d\n", t->pid);
576
577 raw_spin_lock_irqsave(&active_gbl_domain_lock, flags);
578
579 /* setup job params */
580 release_at(t, litmus_clock());
581
582 if (running) {
583 entry = active_gbl_plugin->cpus[task_cpu(t)];
584 BUG_ON(entry->scheduled);
585
586#ifdef CONFIG_RELEASE_MASTER
587 if (entry->cpu != active_gbl_domain.release_master) {
588#endif
589 entry->scheduled = t;
590 tsk_rt(t)->scheduled_on = task_cpu(t);
591#ifdef CONFIG_RELEASE_MASTER
592 } else {
593 /* do not schedule on release master */
594 gbl_preempt(entry); /* force resched */
595 tsk_rt(t)->scheduled_on = NO_CPU;
596 }
597#endif
598 } else {
599 t->rt_param.scheduled_on = NO_CPU;
600 }
601 t->rt_param.linked_on = NO_CPU;
602
603 active_gbl_plugin->job_arrival(t);
604 raw_spin_unlock_irqrestore(&active_gbl_domain_lock, flags);
605}
606
607void gblv_task_wake_up(struct task_struct *task)
608{
609 unsigned long flags;
610 lt_t now;
611
612 TRACE_TASK(task, "wake_up at %llu\n", litmus_clock());
613
614 raw_spin_lock_irqsave(&active_gbl_domain_lock, flags);
615 /* We need to take suspensions because of semaphores into
616 * account! If a job resumes after being suspended due to acquiring
617 * a semaphore, it should never be treated as a new job release.
618 */
619 if (get_rt_flags(task) == RT_F_EXIT_SEM) {
620 set_rt_flags(task, RT_F_RUNNING);
621 } else {
622 now = litmus_clock();
623 if (is_tardy(task, now)) {
624 /* new sporadic release */
625 release_at(task, now);
626 sched_trace_task_release(task);
627 }
628 else {
629 if (task->rt.time_slice) {
630 /* came back in time before deadline
631 */
632 set_rt_flags(task, RT_F_RUNNING);
633 }
634 }
635 }
636 active_gbl_plugin->job_arrival(task);
637 raw_spin_unlock_irqrestore(&active_gbl_domain_lock, flags);
638}
639
640void gblv_task_block(struct task_struct *t)
641{
642 unsigned long flags;
643
644 TRACE_TASK(t, "block at %llu\n", litmus_clock());
645
646 /* unlink if necessary */
647 raw_spin_lock_irqsave(&active_gbl_domain_lock, flags);
648 gbl_unlink(t);
649 raw_spin_unlock_irqrestore(&active_gbl_domain_lock, flags);
650
651 BUG_ON(!is_realtime(t));
652}
653
654
655void gblv_task_exit(struct task_struct * t)
656{
657 unsigned long flags;
658
659 /* unlink if necessary */
660 raw_spin_lock_irqsave(&active_gbl_domain_lock, flags);
661 gbl_unlink(t);
662 if (tsk_rt(t)->scheduled_on != NO_CPU) {
663 active_gbl_plugin->cpus[tsk_rt(t)->scheduled_on]->scheduled = NULL;
664 tsk_rt(t)->scheduled_on = NO_CPU;
665 }
666 raw_spin_unlock_irqrestore(&active_gbl_domain_lock, flags);
667
668 BUG_ON(!is_realtime(t));
669 TRACE_TASK(t, "RIP\n");
670}
671
672long gblv_admit_task(struct task_struct* tsk)
673{
674 return 0;
675}