[LITMUS^RT] understanding PREEMPT_ACTIVE
Glenn Elliott
gelliott at cs.unc.edu
Fri Mar 7 20:18:36 CET 2014
On Mar 7, 2014, at 1:45 PM, Glenn Elliott <gelliott at cs.unc.edu> wrote:
> 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.
It looks like I was wrong about t->state not being TASK_RUNNING. This line of code changes the task state to TASK_RUNNING before the task is woken up:
https://github.com/LITMUS-RT/litmus-rt/blob/master/kernel/sched/litmus.c#L192
Thus, my proposed “fix” is not enough. Why do we change the task state prior to calling litmus->task_wake_up()? Could this be done after the scheduler lock has been acquired? The comments say that we need to change the task state, but they don’t say _why_.
Thanks,
Glenn
More information about the litmus-dev
mailing list