The current code in function 
__mod_timer(https://github.com/torvalds/linux/blob/master/kernel/time/timer.c#L961):

994             base = lock_timer_base(timer, &flags);   
----------------------(1)
                forward_timer_base(base);

                if (timer_pending(timer) && (options & MOD_TIMER_REDUCE) &&
                    time_before_eq(timer->expires, expires)) {
                        ret = 1;
                        goto out_unlock;
                }

                clk = base->clk;
                idx = calc_wheel_index(expires, clk, &bucket_expiry);

                /*
                 * Retrieve and compare the array index of the pending
                 * timer. If it matches set the expiry to the new value so a
                 * subsequent call will exit in the expires check above.
                 */
                if (idx == timer_get_idx(timer)) {
                        if (!(options & MOD_TIMER_REDUCE))
                                timer->expires = expires;
                        else if (time_after(timer->expires, expires))
                                timer->expires = expires;
                        ret = 1;
                        goto out_unlock;
                }
        } else {
1021            base = lock_timer_base(timer, &flags); 
------------------------(2)
                forward_timer_base(base);
        }

        ret = detach_if_pending(timer, base, false);
        if (!ret && (options & MOD_TIMER_PENDING_ONLY))
                goto out_unlock;

        new_base = get_target_base(base, timer->flags);

        if (base != new_base) {
                /*
                 * We are trying to schedule the timer on the new base.
                 * However we can't change timer's base while it is running,
                 * otherwise del_timer_sync() can't detect that the timer's
                 * handler yet has not finished. This also guarantees that the
                 * timer is serialized wrt itself.
                 */
                if (likely(base->running_timer != timer)) {
                        /* See the comment in lock_timer_base() */
                        timer->flags |= TIMER_MIGRATING;

1042                    raw_spin_unlock(&base->lock); -----------------------(3)
                        base = new_base;
                        raw_spin_lock(&base->lock); -------------------------(4)
                        WRITE_ONCE(timer->flags,
                                   (timer->flags & ~TIMER_BASEMASK) | 
base->cpu);
                        forward_timer_base(base);
                }
        }

        debug_timer_activate(timer);

        timer->expires = expires;
        /*
         * If 'idx' was calculated above and the base time did not advance
         * between calculating 'idx' and possibly switching the base, only
         * enqueue_timer() is required. Otherwise we need to (re)calculate
         * the wheel index via internal_add_timer().
         */
        if (idx != UINT_MAX && clk == base->clk)
                enqueue_timer(base, timer, idx, bucket_expiry);
        else
                internal_add_timer(base, timer);

out_unlock:
1066    raw_spin_unlock_irqrestore(&base->lock, flags); ---------------------(5)

        return ret;
}

The code in (1)(2) lock the base with raw_spin_lock_irqsave(&base->lock, flag),
if base != new_base,  the code in (3) unlock the old base, the code in (4) lock 
the
new base. at the end of the function(5), use 
raw_spin_unlock_irqrestore(&base->lock, flags);
to unlock the new_base.

Consider the following situation:

        CPU0                                    CPU1
base = lock_timer_base(timer, &flags);                                          
                (1)(2)
raw_spin_unlock(&base->lock);                                                   
                (3)
base = new_base;
                                        raw_spin_lock(&base->lock);             
                (4)
                                        raw_spin_unlock_irqrestore(&base->lock, 
flags);         (5)

The flags save from CPU0, and restore to CPU1. Is this wrong?

we encountered a kernel panic, and we suspect that it is the problem. How about 
the following patch to fix.

Signed-off-by: Wang Long <w...@laoqinren.net>
---
 kernel/time/timer.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/kernel/time/timer.c b/kernel/time/timer.c
index a16764b..4153766 100644
--- a/kernel/time/timer.c
+++ b/kernel/time/timer.c
@@ -1039,9 +1039,9 @@ static struct timer_base *lock_timer_base(struct 
timer_list *timer,
                        /* See the comment in lock_timer_base() */
                        timer->flags |= TIMER_MIGRATING;
 
-                       raw_spin_unlock(&base->lock);
+                       raw_spin_unlock_irqrestore(&base->lock, flags);
                        base = new_base;
-                       raw_spin_lock(&base->lock);
+                       raw_spin_lock_irqsave(&base->lock, flags);
                        WRITE_ONCE(timer->flags,
                                   (timer->flags & ~TIMER_BASEMASK) | 
base->cpu);
                        forward_timer_base(base);
-- 
1.8.3.1




Reply via email to