[LITMUS^RT] understanding PREEMPT_ACTIVE
Glenn Elliott
gelliott at cs.unc.edu
Fri Mar 7 19:45:47 CET 2014
Hi Everyone,
Can anyone explain more about the use of Linux's preempt_count flag PREEMPT_ACTIVE? This is the best resource that I have found, but it leaves much to be desired in terms of detail: http://lkml.iu.edu//hypermail/linux/kernel/0403.2/0784.html
As I understand it, when PREEMPT_ACTIVE is set in a task’s preempt_count field, the kernel is allowed to preempt the task, even if the task’s state is not TASK_RUNNING.
Why do I care about this flag? Here’s my situation.
On processor P0, I have a task T that is in a TASK_UNINTERRUPTIBLE state. P0 is about to call wake_up_process(T). Note, and this is important, that no scheduler locks are held—only interrupts are disabled on P0.* Here’s the general structure of the code:
local_irq_save(…);
…
get_scheduler_lock_and_other_locks();
enqueue T for waking
free_scheduler_lock_and_other_locks();
…
wake_up_process(T); // the scheduler lock will be re-aquired within this function call
local_irq_restore(…);
On processor P1, the budget of task T has been detected as exhausted (in my scheduler, a task’s budget can drain even when the task is not scheduled/linked). From within a budget timer interrupt handler, I use the scheduler’s job_completion() function to refresh the budget of, and set a new deadline for, T. It is safe to change T’s deadline since P1 holds the appropriate scheduler lock.
At the end of my scheduler’s job_completion(), it executes code that looks like this (https://github.com/LITMUS-RT/litmus-rt/blob/master/litmus/sched_gsn_edf.c#L364):
if(is_running(t)) { job_arrival(t); }
This makes sense. We want to make the task eligible to run if it is runnable. Is my task T running? This macro is_running() says it is, even though T’s state is TASK_UNINTERRUPTIBLE. This is because the macro is_running() expands to:
((t)->state == TASK_RUNNING || task_thread_info(t)->preempt_count & PREEMPT_ACTIVE)
So what happens to my task T? Ultimately, P0 and P1 both link T to different CPUs. These operations are serialized by the scheduler lock, but clearly, this is still wrong. (Thankfully, the bug is caught by link_task_to_cpu(). Example: https://github.com/LITMUS-RT/litmus-rt/blob/master/litmus/sched_gsn_edf.c#L181)
So what’s the fix? I think that I’d like P1 to still refresh T’s budget and set its new deadline. I believe this is safe since P1 holds the scheduler lock. However, I’d like P0 to be the processor to wake up T and link it to a processor. I could add a new flag (protected by the scheduler lock) to tell P1 that T is queued up for waking on a remote processor, but this seems messy. Would it be safe to replace
if(is_running(t)) { job_arrival(t); }
with
if(t->state == TASK_RUNNING) { job_arrival(t); }
?
Under vanilla Litmus, I believe job_completion() is always called by the task itself within schedule(), but only if the task is not self-suspending. This would mean that t->state would always equal TASK_RUNNING, correct? Thus, my proposed change would not affect normal/pre-existing code paths.
Thanks,
Glenn
* This behavior on P0 is to deal with a nasty lock dependency problem. Without getting into the details, I hoist wake_up_process() out of these lock critical sections. I hate this, but it’s how my code works at the moment. Implementing nested inheritance, with dynamic group locks (especially with priority-ordered lock queues), with unusual budget enforcement mechanisms = coding nightmare. It would take a significant effort to design and implement a cleaner solution, but (1) I don’t have the time, and (2) it wouldn’t be very fruitful without proper container/server support in Litmus. Thus, I am hoping to find a technically correct solution to my above problem, even if it is a kludge.
More information about the litmus-dev
mailing list