diff options
author | Joshua Bakita <jbakita@cs.unc.edu> | 2020-05-17 16:28:20 -0400 |
---|---|---|
committer | Joshua Bakita <jbakita@cs.unc.edu> | 2020-05-17 16:28:20 -0400 |
commit | 1394cfe730e1e5030decc9990b37011a4957a7c0 (patch) | |
tree | 135ac6edcecdbbe723a6f18eb23d6cf5d606d8fc | |
parent | 888ab3700f7d9e0b59795c6d8b0461b3ce0cdc81 (diff) |
Implement background scheduling
This turned out to be incredibly complicated. High-level changes are:
1. Container completion in g_job_completion() has been completely
reworked. We now check for and appropriately handle all possible
states of a background scheduled task.
2. edfsc_cschedule() has had its fixed task scheduling logic overhauled
to be easier to follow. This fixes the logic for when a fixed task
preempts a background scheduled task, but may also fix other bugs.
3. When a task blocks while being background scheduled, remove that
task, NOT the container scheduling that task.
4. Update `entry->scheduled` at the end of edfsc_gschedule(). We will
not run g_finish_switch() if we continue scheduling the same task,
but we still need entry->scheduled to be updated in the case when
we stop background scheduling and switch to normal scheduling. The
original code that sets `entry->scheduled` in g_finish_switch()
may no longer be needed and further investigation of its removal
is encouraged.
5. If a task exits while being background scheduled, remove it from
the container rather than `entry->scheduled`.
-rw-r--r-- | litmus/sched_edfsc.c | 95 |
1 files changed, 64 insertions, 31 deletions
diff --git a/litmus/sched_edfsc.c b/litmus/sched_edfsc.c index a91951480f33..8ae94f2dc1df 100644 --- a/litmus/sched_edfsc.c +++ b/litmus/sched_edfsc.c | |||
@@ -436,10 +436,6 @@ static void c_release(struct task_struct *t) { | |||
436 | if (!bheap_node_in_heap(entry->hn)) | 436 | if (!bheap_node_in_heap(entry->hn)) |
437 | add_cpu_to_global(entry); | 437 | add_cpu_to_global(entry); |
438 | // Note that container's aren't real tasks and thus can't block | 438 | // Note that container's aren't real tasks and thus can't block |
439 | if (tsk_rt(t)->edfsc_params.domain->scheduled) { | ||
440 | requeue(tsk_rt(t)->edfsc_params.domain->scheduled); | ||
441 | tsk_rt(t)->edfsc_params.domain->scheduled = NULL; | ||
442 | } | ||
443 | // Let g_preempt_check() decide what to run, don't impose | 439 | // Let g_preempt_check() decide what to run, don't impose |
444 | unlink(t); | 440 | unlink(t); |
445 | // Request to be scheduled globally again | 441 | // Request to be scheduled globally again |
@@ -482,12 +478,44 @@ static noinline void g_job_completion(struct task_struct* t, int forced) | |||
482 | requeue(t); | 478 | requeue(t); |
483 | g_preempt_check(); | 479 | g_preempt_check(); |
484 | } | 480 | } |
485 | // When a container job finishes late | 481 | /* A container may be in several different states when it finishes. It may: |
486 | } else if (is_container(t) && tsk_rt(t)->edfsc_params.can_release) { | 482 | * - Be scheduling a migrating task that is finished, blocked, or out of budget |
487 | tsk_rt(t)->edfsc_params.can_release = 0; | 483 | * - Be scheduling a fixed task |
488 | c_release(t); | 484 | * - Be scheduling nothing |
489 | if (get_rt_utilization(t) == to_fp(1)) | 485 | * If there's a migrating task being scheduled, we can't unconditionally |
490 | manage_idle_enforcement_timer(t); | 486 | * requeue it. Often, we may actually have to call g_job_completion() on |
487 | * that migrating task. If we finish while running a fixed task, we just | ||
488 | * "freeze" it in the container - edfsc_cschedule() will take care of | ||
489 | * processing its state when the container is rescheduled. | ||
490 | * | ||
491 | * If the container is tardy, we process its scheduled task as in the non- | ||
492 | * tardy case, then just immediately call c_release() on the container. | ||
493 | */ | ||
494 | } else if (is_container(t)) { | ||
495 | struct task_struct** child = &tsk_rt(t)->edfsc_params.domain->scheduled; | ||
496 | // No need to handle fixed tasks, cschedule will do that when it runs next | ||
497 | if (*child && is_migrating(*child)) { | ||
498 | BUG_ON(is_queued(*child)); | ||
499 | // If migrating and done | ||
500 | if (is_completed(*child) || (budget_enforced(*child) && budget_exhausted(*child))) { | ||
501 | g_job_completion(*child, budget_enforced(*child) && budget_exhausted(*child)); | ||
502 | // If migrating and blocked | ||
503 | } else if (!is_current_running()) { | ||
504 | unlink(*child); | ||
505 | // Otherwise it can keep running globally | ||
506 | } else { | ||
507 | requeue(*child); | ||
508 | } | ||
509 | // Regardless, we never "freeze" a migrating task in a container | ||
510 | *child = NULL; | ||
511 | } | ||
512 | // When a container job finishes late, release it immediately | ||
513 | if (tsk_rt(t)->edfsc_params.can_release) { | ||
514 | tsk_rt(t)->edfsc_params.can_release = 0; | ||
515 | c_release(t); | ||
516 | if (get_rt_utilization(t) == to_fp(1)) | ||
517 | manage_idle_enforcement_timer(t); | ||
518 | } | ||
491 | } | 519 | } |
492 | } | 520 | } |
493 | 521 | ||
@@ -585,11 +613,6 @@ static void edfsc_cschedule(cont_domain_t* cedf, struct task_struct * prev) | |||
585 | */ | 613 | */ |
586 | resched = preempt; | 614 | resched = preempt; |
587 | 615 | ||
588 | /* If a task blocks we have no choice but to reschedule. | ||
589 | */ | ||
590 | if (blocks) | ||
591 | resched = 1; | ||
592 | |||
593 | /* Request a sys_exit_np() call if we would like to preempt but cannot. | 616 | /* Request a sys_exit_np() call if we would like to preempt but cannot. |
594 | * Multiple calls to request_exit_np() don't hurt. | 617 | * Multiple calls to request_exit_np() don't hurt. |
595 | */ | 618 | */ |
@@ -608,24 +631,24 @@ static void edfsc_cschedule(cont_domain_t* cedf, struct task_struct * prev) | |||
608 | resched = 1; | 631 | resched = 1; |
609 | } | 632 | } |
610 | 633 | ||
634 | // Deschedule any background jobs if a fixed task is ready | ||
635 | if (is_migrating(cedf->scheduled) && preempt) { | ||
636 | if (!sleep && !out_of_time && !blocks && !is_queued(cedf->scheduled)) | ||
637 | requeue(cedf->scheduled); | ||
638 | resched = 1; | ||
639 | } | ||
640 | |||
611 | /* The final scheduling decision. Do we need to switch for some reason? | 641 | /* The final scheduling decision. Do we need to switch for some reason? |
612 | * Switch if we are in RT mode and have no task or if we need to | 642 | * Switch if we are in RT mode and have no task or if we need to |
613 | * resched. | 643 | * resched. |
614 | */ | 644 | */ |
615 | next = NULL; | 645 | next = NULL; |
616 | if ((!np || blocks) && (resched || !exists)) { | 646 | if (blocks || !exists || (!np && resched)) { |
617 | /* When preempting a task that does not block, then | ||
618 | * re-insert it into either the ready queue or the | ||
619 | * release queue (if it completed). requeue() picks | ||
620 | * the appropriate queue. | ||
621 | */ | ||
622 | next = __take_ready(edf); | 647 | next = __take_ready(edf); |
623 | } else if (exists) { | 648 | } else if (exists) { |
624 | BUG_ON(!is_realtime(prev)); | 649 | // This is safe when background scheduling, as we can only get here if |
625 | /* Only override Linux scheduler if we have a real-time task | 650 | // there were no other fixed tasks ready to run. |
626 | * scheduled that needs to continue. | 651 | next = cedf->scheduled; |
627 | */ | ||
628 | next = prev; | ||
629 | } | 652 | } |
630 | 653 | ||
631 | if (next) { | 654 | if (next) { |
@@ -706,10 +729,10 @@ static struct task_struct *edfsc_gschedule(struct task_struct *prev) | |||
706 | 729 | ||
707 | 730 | ||
708 | /* If a task blocks we have no choice but to reschedule. | 731 | /* If a task blocks we have no choice but to reschedule. |
709 | * Note: containers never block | 732 | * Note: containers never block, so if blocks is true and we're background |
733 | * scheduling, we want to unlink `prev` NOT `entry->scheduled`. | ||
710 | */ | 734 | */ |
711 | if (blocks) | 735 | unlink(prev); |
712 | unlink(entry->scheduled); | ||
713 | 736 | ||
714 | /* Request a sys_exit_np() call if we would like to preempt but cannot. | 737 | /* Request a sys_exit_np() call if we would like to preempt but cannot. |
715 | * We need to make sure to update the link structure anyway in case | 738 | * We need to make sure to update the link structure anyway in case |
@@ -788,6 +811,10 @@ static struct task_struct *edfsc_gschedule(struct task_struct *prev) | |||
788 | 811 | ||
789 | // Tell LITMUS^RT that we choose a task and are done scheduling after return | 812 | // Tell LITMUS^RT that we choose a task and are done scheduling after return |
790 | sched_state_task_picked(); | 813 | sched_state_task_picked(); |
814 | // When we transition from doing background scheduling to doing normal | ||
815 | // scheduling, we may schedule the same task. Unfortunately, when this | ||
816 | // happens, g_finish_switch() will /not/ be called. Fix the state manually. | ||
817 | entry->scheduled = next; | ||
791 | 818 | ||
792 | // if no fixed tasks to be scheduled by the container, then container->scheduled | 819 | // if no fixed tasks to be scheduled by the container, then container->scheduled |
793 | // should be the previous non-rt task if any | 820 | // should be the previous non-rt task if any |
@@ -1091,8 +1118,14 @@ static void edfsc_task_exit(struct task_struct* t) | |||
1091 | BUG_ON(t != current); | 1118 | BUG_ON(t != current); |
1092 | list_del(&t->edfsc_qnode); | 1119 | list_del(&t->edfsc_qnode); |
1093 | entry = &per_cpu(edfsc_cpu_entries, task_cpu(t)); | 1120 | entry = &per_cpu(edfsc_cpu_entries, task_cpu(t)); |
1094 | BUG_ON(entry->scheduled != t); | 1121 | // Handle the case where we exit while being background scheduled |
1095 | entry->scheduled = NULL; | 1122 | if (is_container(entry->scheduled)) { |
1123 | BUG_ON(entry->scheduled->rt_param.edfsc_params.domain->scheduled != t); | ||
1124 | entry->scheduled->rt_param.edfsc_params.domain->scheduled = NULL; | ||
1125 | } else { | ||
1126 | BUG_ON(entry->scheduled != t); | ||
1127 | entry->scheduled = NULL; | ||
1128 | } | ||
1096 | } | 1129 | } |
1097 | 1130 | ||
1098 | /* To preserve EDF-sc scheduling invariants, we can only release a task's | 1131 | /* To preserve EDF-sc scheduling invariants, we can only release a task's |