bool Scheduler::execute() { static units::Nanoseconds time(0); DPRINTF("Begin executing the scheduler\n"); DPRINTF("Scheduler mutex addr is %p\n", &(m_sched_impl->sync)); /* If we let the period be zero, then the scheduler will always be * running. * TODO move this into an initialization routine called before * start(). */ if(0 == m_props.period) { EPRINTF("Invalid period\n"); return false; } /* The first time through we have to get the time from the RTC. * Thereafter, we just add the period. */ if(false == get_time(time)) { EPRINTF("Failed to get the time\n"); return false; } /* The scheduler tracks reference time. Reference time starts at zero * and gets incremented by the scheduler's period each time the * scheduler runs. */ Reference_time &rtimer = Reference_time::get_instance(); units::Nanoseconds ref_time(0); /* We have to have the mutex before calling wait. */ if(false == this->lock()) { EPRINTF("Failed to get mutex\n"); return false; } /* Get an reference to the data router. We'll be calling this to move * data for the pub/sub system. */ Data_router &router = Data_router::get_instance(); DPRINTF("Entering the scheduler loop\n"); /* Here's the meat of the show! This stuff is done in an infinite loop * while the task is running. */ Sched_list &list = m_sched_impl->rategroup; while(this->is_operational() || sched_unwind_tics) { /* If the scheduler has been asked to halt, start counting down the * unwind tics. The unwind tics make sure that all tasks have had * time to complete before the schduler exits and ceases scheduling * the tasks. */ if(!(this->is_operational())) { --sched_unwind_tics; DPRINTF("Unwinding, %u tics left\n", sched_unwind_tics); } /* The scheduler goes to sleep until its runtime period has elapsed. * Time of zero means wait forever on the sync point. This allows * the scheduler to be driven by an external time source. */ DPRINTF("Wait for next tic\n"); if(m_use_external_clock) { time = units::Nanoseconds(0); } else { time = units::Nanoseconds(m_props.period + time); } m_sched_impl->sync.condition_cleared(); if(false == m_sched_impl->sync.wait(time)) { EPRINTF("Scheduler wait failed\n"); this->unlock(); return false; } units::Nanoseconds start_time; get_time(start_time); /* Increment the reference timer by the period of the scheduler. * TODO This isn't really how I want to handle reference time. * Right now there is no lock around it so it could misbehave on * multicore. I think it should be sent via pub/sub message, but * I'm also using it as the predicate around the tasks wait * condition which makes some possibly undesirable dependencies. */ ref_time = rtimer.increment(m_props.period); DPRINTF("ref_time = %" PRId64 "\n", int64_t(ref_time)); /* Transfer all registered data that is scheduled to be moved this * period. */ router.do_transfers(ref_time); DPRINTF("Data routing complete\n"); /* For each rategroup, look to see if it is time to run. * TODO The rategroups should be in a sorted list so we can bail * early once we reach a period that is not modulo the reference * time. */ unsigned int item_num = 0; for(Sched_list::Node *n = list.head(); n; n = n->next()) { ++item_num; DPRINTF("Checking list item number %u\n", item_num); if(0 == (ref_time % n->data->period)) { if(n->data->finished) { n->data->rt_attr.last_runtime = n->data->end_time - n->data->start_time; if(n->data->rt_attr.last_runtime > n->data->rt_attr.max_runtime) { n->data->rt_attr.max_runtime = n->data->rt_attr.last_runtime; } } else { WPRINTF("OVERRUN Rategroup %" PRId64 "\n", int64_t(n->data->period)); ++(n->data->rt_attr.num_overruns); #ifdef DEBUG_OVERRUN_BUG fprintf(stderr, "num overruns = %u\n", n->data->rt_attr.num_overruns); fprintf(stderr, "last runtime = %ld\n", int64_t(n->data->rt_attr.last_runtime)); fprintf(stderr, "max runtime = %ld\n", int64_t(n->data->rt_attr.max_runtime)); #endif n->data->rt_attr_symbol->entry->write(n->data->rt_attr); /* If an overrun occurs in this rategroup, break out. * If we try to grab the rategroup lock we could cause * the scheduler to block for some non-deterministic * period of time. */ continue; } n->data->rt_attr_symbol->entry->write(n->data->rt_attr); DPRINTF("Signaling %" PRId64 " period tasks\n", int64_t(n->data->period)); if(false == n->data->sync.lock()) { EPRINTF("Attaining rategroup lock\n"); continue; } n->data->start_time = start_time; n->data->finished = false; n->data->sync.condition_satisfied(); if(false == n->data->sync.release()) { EPRINTF("Releasing rategroup\n"); } if(false == n->data->sync.unlock()) { EPRINTF("Releasing rategroup lock\n"); } } } DPRINTF("Scheduler is running!\n"); } /* Release the scheduler lock and exit the scheduler task. */ this->unlock(); return false; }
void *Task::run(void *arg) { Task *p = reinterpret_cast<Task *>(arg); struct sched_param sched_attr; int policy; pthread_t tid = pthread_self(); Scheduler &sched = Scheduler::get_instance(); m_thread_syncpoint.lock(); m_thread_syncpoint.condition_satisfied(); /* Set the scheduler parameters. Linux at least fails when the * parameters are set before creating the thread. The parameters must * be set BY the affected thread. Stupid little penguins!!! */ if(pthread_getschedparam(tid, &policy, &sched_attr) != 0) { m_thread_syncpoint.release(); m_thread_syncpoint.lock(); EPRINTF("Failed getting task schedule parameters\n"); return NULL; } policy = SCHED_FIFO; sched_attr.sched_priority = p->m_props.prio; if(pthread_setschedparam(tid, policy, &sched_attr) != 0) { m_thread_syncpoint.release(); m_thread_syncpoint.unlock(); EPRINTF("Failed setting task schedule parameters\n"); EPRINTF("Did you remember sudo?\n"); return NULL; } /* Make sure the thread is being scheduled at the right priority. Linux * requires that the application be run as root or permissions have * been set through PAM to allow use of real time thread priority * scheduling. Otherwise, the checks below will fail and we'll exit the * thread. */ if(pthread_getschedparam(tid, &policy, &sched_attr) != 0) { m_thread_syncpoint.release(); m_thread_syncpoint.unlock(); EPRINTF("Failed getting task schedule parameters\n"); return NULL; } if(SCHED_FIFO != policy) { m_thread_syncpoint.release(); m_thread_syncpoint.unlock(); EPRINTF("Failed to set real time scheduling policy\n"); return NULL; } if(sched_attr.sched_priority != static_cast<int>(p->m_props.prio)) { m_thread_syncpoint.release(); m_thread_syncpoint.unlock(); EPRINTF("Failed to set real time priority for task %s\n", p->m_name); return NULL; } DPRINTF("Successfully registered task %s at priority %d\n", p->m_name, sched_attr.sched_priority); units::Nanoseconds ref_time(0); p->m_operational = true; m_thread_syncpoint.release(); m_thread_syncpoint.unlock(); while(true) { units::Nanoseconds start_time(0); get_time(start_time); /* If this is a periodic tasks, wait to be scheduled. */ if(p->m_impl->rategroup_sync) { if(p->m_impl->first_pass) { /* We grab the lock and hold it while we're executing so no * other task jumps in. One task at a time per * rategroup! The lock gets released once we get into the * wait call. */ p->m_impl->rategroup_sync->lock(); /* Compute a wake up time to be used on the first pass. */ p->m_impl->expected_wake_time = get_reference_time(); p->m_impl->expected_wake_time -= p->m_impl->expected_wake_time % p->m_props.period; p->m_impl->first_pass = false; } /* We use reference time as a post condition for the wait. The * reference time when we wake up should be later than the time * at which we went to sleep. Otherwise, we've experienced a * spurious wakeup. */ p->m_impl->expected_wake_time += p->m_props.period; DPRINTF("%s: expected wake time = %" PRId64 "\n", p->m_name, int64_t(p->m_impl->expected_wake_time)); while((true == p->m_operational) && (ref_time = get_reference_time()) < p->m_impl->expected_wake_time) { DPRINTF("%s: Tref = %" PRId64 ", expected_wake_time = %" PRId64 "\n", p->m_name, int64_t(ref_time), int64_t(p->m_impl->expected_wake_time)); if(false == p->m_impl->rategroup_sync->wait()) { EPRINTF("%s: Error in periodic wait\n", p->m_name); p->m_impl->rategroup_sync->unlock(); return NULL; } DPRINTF("%s:Woke up\n", p->m_name); } DPRINTF("%s: Tref = %" PRId64 ", expected_wake_time = %" PRId64 "\n", p->m_name, int64_t(ref_time), int64_t(p->m_impl->expected_wake_time)); /* Wait for all of the tasks to be awoken and ready to run. * This relies on testing against an inverted wait condition * because we're not going to clear the condition until all the * tasks are awake. The end of frame task is allowed to begin * execution because it is the last task on the list (in * priority order). The end of frame task signals all of the * others that they may proceed. */ if(false == p->m_is_eof_task) { p->m_impl->rategroup_sync->inverse_wait(); } } if(false == p->m_operational) { DPRINTF("%s: No longer operational\n", p->m_name); if(p->m_impl->rategroup_sync) { p->m_impl->rategroup_sync->unlock(); } return NULL; } if(p->m_props.is_present_in_schedule(sched.get_schedule()) || (0 == p->m_props.period)) { DPRINTF("Executing task %s\n", p->m_name); if(false == p->execute()) { DPRINTF("Task %s exiting\n", p->m_name); p->terminate(); if(p->m_impl->rategroup_sync) { p->m_impl->rategroup_sync->unlock(); } return NULL; } units::Nanoseconds end_time(0); get_time(end_time); p->m_props.last_runtime = end_time - start_time; if(p->m_props.last_runtime > p->m_props.max_runtime) { p->m_props.max_runtime = p->m_props.last_runtime; } DPRINTF("%s last_runtime = %" PRId64 ", max_runtime = %" PRId64 "\n", p->m_name, int64_t(p->m_props.last_runtime), int64_t(p->m_props.max_runtime)); } } return NULL; }