Files
NE_YuR/openflow/include/click/task.hh

649 lines
16 KiB
C++

// -*- c-basic-offset: 4; related-file-name: "../../lib/task.cc" -*-
#ifndef CLICK_TASK_HH
#define CLICK_TASK_HH
#include <click/element.hh>
#include <click/sync.hh>
#if HAVE_MULTITHREAD
# include <click/atomic.hh>
# include <click/ewma.hh>
#endif
CLICK_DECLS
#if CLICK_BSDMODULE
# include <click/cxxprotect.h>
CLICK_CXX_PROTECT
# include <sys/lock.h>
# include <sys/mutex.h>
CLICK_CXX_UNPROTECT
# include <click/cxxunprotect.h>
#else
#define GIANT_REQUIRED
#endif
#define PASS_GT(a, b) ((int)(a - b) > 0)
typedef bool (*TaskCallback)(Task *, void *);
typedef TaskCallback TaskHook CLICK_DEPRECATED;
class RouterThread;
class TaskList;
class Master;
struct TaskLink {
#if !HAVE_TASK_HEAP
TaskLink *_prev;
TaskLink *_next;
#endif
#if HAVE_STRIDE_SCHED
unsigned _pass;
#endif
TaskLink() {
#if !HAVE_TASK_HEAP
_prev = _next = 0;
#endif
#if HAVE_STRIDE_SCHED
_pass = 0;
#endif
}
};
class Task : private TaskLink { public:
#if HAVE_STRIDE_SCHED
enum { STRIDE1 = 1U<<16, MAX_STRIDE = 1U<<31 };
enum { MAX_TICKETS = 1<<15, DEFAULT_TICKETS = 1<<10 };
#endif
#if HAVE_ADAPTIVE_SCHEDULER
enum { MAX_UTILIZATION = 1000 };
#endif
/** @brief Construct a task that calls @a f with @a user_data argument.
*
* @param f callback function
* @param user_data argument for callback function
*
* Constructs a task that, when fired, calls @a f like so:
*
* @code
* bool work_done = f(task, user_data);
* @endcode
*
* where @a task is a pointer to this task. @a f should return true if
* the task accomplished some meaningful work, and false if it did not.
* For example, a task that polls a network driver for packets should
* return true if it emits at least one packet, and false if no packets
* were available. */
inline Task(TaskCallback f, void *user_data);
/** @brief Construct a task that calls @a e ->@link Element::run_task(Task*) run_task()@endlink.
*
* @param e element to call
*
* Constructs a task that, when fired, calls the element @a e's @link
* Element::run_task(Task *) run_task()@endlink method, passing this Task
* as an argument.
*
* @sa Task(TaskCallback, void *) */
inline Task(Element *e);
/** @brief Destroy a task.
*
* Unschedules the task if necessary. */
~Task();
/** @brief Return the task's callback function.
*
* Returns null if the task was constructed with the Task(Element *)
* constructor. */
inline TaskCallback callback() const {
return _hook;
}
/** @brief Return the task callback function's user data. */
inline void *user_data() const {
return _thunk;
}
/** @brief Return the task's owning element. */
inline Element *element() const {
return _owner;
}
/** @brief Return true iff the task has been initialize()d. */
inline bool initialized() const;
/** @brief Return the task's home thread ID.
*
* This is the @link RouterThread::thread_id() thread_id()@endlink of the
* thread on which this Task would run if it were scheduled. This need
* not equal the ID of the current thread(), since changes in
* home_thread_id() aren't always implemented immediately (because of
* locking issues). */
inline int home_thread_id() const;
/** @brief Return the thread on which this task is currently scheduled,
* or would be scheduled.
*
* Usually, task->thread()->@link RouterThread::thread_id()
* thread_id()@endlink == task->home_thread_id(). They can differ,
* however, if move_thread() was called but the task hasn't yet been moved
* to the new thread. */
inline RouterThread *thread() const;
/** @brief Return the router to which this task belongs. */
inline Router *router() const {
return _owner->router();
}
/** @brief Return the master where this task will be scheduled. */
Master *master() const;
/** @brief Initialize the Task, and optionally schedule it.
* @param owner specifies the element owning the Task
* @param schedule if true, the Task will be scheduled immediately
*
* This function must be called on every Task before it is used. The
* corresponding router's ThreadSched, if any, is used to determine the
* task's initial thread assignment. The task initially has the default
* number of tickets, and is scheduled iff @a schedule is true.
*
* An assertion will fail if a Task is initialized twice.
*
* Most elements call ScheduleInfo::initialize_task() to initialize a Task
* object. The ScheduleInfo method additionally sets the task's
* scheduling parameters, such as ticket count and thread preference,
* based on a router's ScheduleInfo. ScheduleInfo::initialize_task()
* calls Task::initialize(). */
void initialize(Element *owner, bool schedule);
/** @brief Initialize the Task, and optionally schedule it.
* @param router specifies the router owning the Task
* @param schedule if true, the Task will be scheduled immediately
*
* This function is shorthand for @link Task::initialize(Element *, bool)
* Task::initialize@endlink(@a router ->@link Router::root_element
* root_element@endlink(), @a scheduled). However, it is better to
* explicitly associate tasks with real elements. */
void initialize(Router *router, bool schedule);
/** @brief Return true iff the task is currently scheduled to run.
*
* @note A scheduled task will usually run very soon, but not
* always; due to locking issues, the effects of some reschedule()
* requests may be delayed. Although a task unscheduled with
* strong_unschedule() may appear scheduled(), it will not run
* until strong_reschedule() is called. */
inline bool scheduled() const {
return _status.is_scheduled;
}
/** @brief Unschedule the task.
*
* After unschedule() returns, the task will not run until it is
* rescheduled with reschedule().
*
* @sa reschedule, strong_unschedule */
inline void unschedule() {
_status.is_scheduled = false;
}
/** @brief Reschedule the task.
*
* The task is rescheduled on its home thread. It will eventually run,
* unless its home thread is quiescent or it has been
* strong_unschedule()d.
*
* @sa unschedule, strong_reschedule */
inline void reschedule() {
_status.is_scheduled = true;
click_fence();
if (_pending_nextptr.x < 2)
complete_schedule(0);
}
/** @brief Reschedule a task from the task's callback function.
*
* @warning Only call @a task.fast_reschedule() while @a task is being
* fired, i.e., in its callback function. It is an error to call
* @task.fast_reschedule() at other times -- the task may not actually be
* rescheduled.
*
* Here's a typical, correct use of fast_reschedule():
*
* @code
* class MyElement : public Element {
* ... Task _task; ... bool run_task(Task *t); ...
* };
* bool MyElement::run_task(Task *) {
* do_some_work();
* _task.fast_reschedule();
* return true;
* }
* @endcode
*
* This assumes, however, that run_task() is only called directly by the
* driver. If you call run_task() from another context, _task may not
* actually be scheduled.
*
* @code
* void MyElement::run_timer(Timer *) {
* run_task(); // XXX might not reschedule _task!
* }
* @endcode
*/
inline void fast_reschedule() {
_status.is_scheduled = true;
}
/** @brief Unschedule the Task until strong_reschedule().
*
* Like unschedule(), but in addition, future reschedule() calls
* will not actually schedule the task. Only after strong_reschedule()
* will the task run again.
* @sa strong_reschedule, unschedule
*/
inline void strong_unschedule() {
_status.is_scheduled = false;
_status.is_strong_unscheduled = true;
}
/** @brief Reschedule the Task, undoing a prior strong_unschedule().
*
* This function undoes any previous strong_unschedule() and
* reschedules the task.
* @sa reschedule, strong_unschedule
*/
inline void strong_reschedule() {
_status.is_strong_unscheduled = false;
reschedule();
}
/** @brief Move the Task to a new home thread.
*
* The home thread ID is set to @a new_thread_id. The task, if it is
* currently scheduled, is rescheduled on thread @a new_thread_id
* (which generally takes some time to take effect). If @a new_thread_id
* is less than zero or greater than the number of threads on the router,
* the task is scheduled on a quiescent thread that never actually runs.
*/
void move_thread(int new_thread_id);
#if HAVE_STRIDE_SCHED
inline int tickets() const;
inline void set_tickets(int n);
inline void adjust_tickets(int delta);
#endif
inline bool fire();
#if HAVE_ADAPTIVE_SCHEDULER
inline unsigned runs() const;
inline unsigned work_done() const;
inline unsigned utilization() const;
inline void clear_runs();
#endif
#if HAVE_MULTITHREAD
inline int cycles() const;
inline unsigned cycle_runs() const;
inline void update_cycles(unsigned c);
#endif
/** @cond never */
inline TaskCallback hook() const CLICK_DEPRECATED;
inline void *thunk() const CLICK_DEPRECATED;
/** @endcond never */
private:
#if HAVE_TASK_HEAP
int _schedpos;
#endif
#if HAVE_STRIDE_SCHED
unsigned _stride;
int _tickets;
#endif
union Status {
struct {
int16_t home_thread_id;
uint8_t is_scheduled;
uint8_t is_strong_unscheduled;
};
uint32_t status;
} _status;
TaskCallback _hook;
void *_thunk;
#if HAVE_ADAPTIVE_SCHEDULER
unsigned _runs;
unsigned _work_done;
#endif
#if HAVE_MULTITHREAD
DirectEWMA _cycles;
unsigned _cycle_runs;
#endif
RouterThread *_thread;
Element *_owner;
union Pending {
Task *t;
uintptr_t x;
};
Pending _pending_nextptr;
Task(const Task &x);
Task &operator=(const Task &x);
void cleanup();
#if CLICK_DEBUG_SCHEDULING
public:
#endif
inline bool on_scheduled_list() const;
inline bool on_pending_list() const {
return _pending_nextptr.x != 0;
}
inline bool needs_cleanup() const;
#if CLICK_DEBUG_SCHEDULING
private:
#endif
void add_pending(bool always);
void process_pending(RouterThread* thread);
void complete_schedule(RouterThread* process_pending_thread);
inline void remove_from_scheduled_list();
static bool error_hook(Task *task, void *user_data);
friend class RouterThread;
friend class Master;
};
// need RouterThread's definition for inline functions
CLICK_ENDDECLS
#include <click/routerthread.hh>
CLICK_DECLS
inline
Task::Task(TaskCallback f, void *user_data)
:
#if HAVE_TASK_HEAP
_schedpos(-1),
#endif
#if HAVE_STRIDE_SCHED
_stride(0), _tickets(-1),
#endif
_hook(f), _thunk(user_data),
#if HAVE_ADAPTIVE_SCHEDULER
_runs(0), _work_done(0),
#endif
#if HAVE_MULTITHREAD
_cycle_runs(0),
#endif
_thread(0), _owner(0)
{
_status.home_thread_id = -2;
_status.is_scheduled = _status.is_strong_unscheduled = false;
_pending_nextptr.x = 0;
}
inline
Task::Task(Element* e)
:
#if HAVE_TASK_HEAP
_schedpos(-1),
#endif
#if HAVE_STRIDE_SCHED
_stride(0), _tickets(-1),
#endif
_hook(0), _thunk(e),
#if HAVE_ADAPTIVE_SCHEDULER
_runs(0), _work_done(0),
#endif
#if HAVE_MULTITHREAD
_cycle_runs(0),
#endif
_thread(0), _owner(0)
{
_status.home_thread_id = -2;
_status.is_scheduled = _status.is_strong_unscheduled = false;
_pending_nextptr.x = 0;
}
inline bool
Task::initialized() const
{
return _owner != 0;
}
inline bool
Task::on_scheduled_list() const
{
#if HAVE_TASK_HEAP
return _schedpos >= 0;
#else
return _prev != 0;
#endif
}
inline bool
Task::needs_cleanup() const
{
#if HAVE_TASK_HEAP
int a;
uintptr_t b;
do {
a = _schedpos;
b = _pending_nextptr.x;
click_fence();
} while (a != _schedpos || b != _pending_nextptr.x);
return a >= 0 || b != 0;
#else
TaskLink* a;
uintptr_t b;
do {
a = _prev;
b = _pending_nextptr.x;
click_fence();
} while (a != _prev || b != _pending_nextptr.x);
return a != 0 || b != 0;
#endif
}
/** @cond never */
/** @brief Return the task's callback function.
* @deprecated Use callback() instead. */
inline TaskCallback
Task::hook() const
{
return _hook;
}
/** @brief Return the task's callback data.
* @deprecated Use user_data() instead. */
inline void *
Task::thunk() const
{
return _thunk;
}
/** @endcond never */
inline int
Task::home_thread_id() const
{
return _status.home_thread_id;
}
inline RouterThread *
Task::thread() const
{
return _thread;
}
inline void
Task::remove_from_scheduled_list()
{
if (on_scheduled_list()) {
#if HAVE_TASK_HEAP
Task *back = _thread->_task_heap.back().t;
_thread->_task_heap.pop_back();
if (_thread->_task_heap.size() > 0)
_thread->task_reheapify_from(_schedpos, back);
click_fence();
_schedpos = -1;
#else
_next->_prev = _prev;
_prev->_next = _next;
_next = 0;
click_fence();
_prev = 0;
#endif
}
}
#if HAVE_STRIDE_SCHED
/** @brief Return the task's number of tickets.
*
* Tasks with larger numbers of tickets are scheduled more often. Tasks are
* initialized with tickets() == DEFAULT_TICKETS.
*
* @sa set_tickets, adjust_tickets
*/
inline int
Task::tickets() const
{
return _tickets;
}
/** @brief Set the task's ticket count.
* @param n the ticket count
*
* The ticket count @a n is pinned to the range [1, MAX_TICKETS].
*
* @sa tickets, adjust_tickets
*/
inline void
Task::set_tickets(int n)
{
if (n > MAX_TICKETS)
n = MAX_TICKETS;
else if (n < 1)
n = 1;
_tickets = n;
_stride = STRIDE1 / n;
assert(_stride < MAX_STRIDE);
}
/** @brief Add @a delta to the Task's ticket count.
* @param delta adjustment to the ticket count
*
* The ticket count cannot be adjusted below 1 or above MAX_TICKETS.
*
* @sa set_tickets
*/
inline void
Task::adjust_tickets(int delta)
{
set_tickets(_tickets + delta);
}
#endif /* HAVE_STRIDE_SCHED */
/** @brief Fire the task by calling its callback function.
*
* This function is generally called by the RouterThread implementation; there
* should be no need to call it yourself.
*/
inline bool
Task::fire()
{
#if CLICK_STATS >= 2
click_cycles_t start_cycles = click_get_cycles(),
start_child_cycles = _owner->_child_cycles;
#endif
#if HAVE_MULTITHREAD
_cycle_runs++;
#endif
bool work_done;
if (!_hook)
work_done = ((Element*)_thunk)->run_task(this);
else
work_done = _hook(this, _thunk);
#if HAVE_ADAPTIVE_SCHEDULER
++_runs;
_work_done += work_done;
#endif
#if CLICK_STATS >= 2
click_cycles_t all_delta = click_get_cycles() - start_cycles,
own_delta = all_delta - (_owner->_child_cycles - start_child_cycles);
_owner->_task_calls += 1;
_owner->_task_own_cycles += own_delta;
#endif
return work_done;
}
#if HAVE_ADAPTIVE_SCHEDULER
inline unsigned
Task::runs() const
{
return _runs;
}
inline unsigned
Task::work_done() const
{
return _work_done;
}
inline unsigned
Task::utilization() const
{
return (_runs ? (MAX_UTILIZATION * _work_done) / _runs : 0);
}
inline void
Task::clear_runs()
{
_runs = _work_done = 0;
}
#endif
#if HAVE_MULTITHREAD
inline int
Task::cycles() const
{
return _cycles.unscaled_average();
}
inline unsigned
Task::cycle_runs() const
{
return _cycle_runs;
}
inline void
Task::update_cycles(unsigned c)
{
_cycles.update(c);
_cycle_runs = 0;
}
#endif
CLICK_ENDDECLS
#endif