]> git.hungrycats.org Git - linux/commitdiff
More subtle SMP bugs in prepare_to_wait()/finish_wait().
authorLinus Torvalds <torvalds@home.osdl.org>
Sat, 13 Dec 2003 13:36:30 +0000 (05:36 -0800)
committerLinus Torvalds <torvalds@home.osdl.org>
Sat, 13 Dec 2003 13:36:30 +0000 (05:36 -0800)
This time we have a SMP memory ordering issue in prepare_to_wait(),
where we really need to make sure that subsequent tests for the
event we are waiting for can not migrate up to before the wait
queue has been set up.

kernel/fork.c

index 6fa0ce76acc58d3ca0b7731310110a151224f391..381b80b510ba68f05a3fccf9a1d72c8c2c41da70 100644 (file)
@@ -125,15 +125,28 @@ void remove_wait_queue(wait_queue_head_t *q, wait_queue_t * wait)
 
 EXPORT_SYMBOL(remove_wait_queue);
 
+
+/*
+ * Note: we use "set_current_state()" _after_ the wait-queue add,
+ * because we need a memory barrier there on SMP, so that any
+ * wake-function that tests for the wait-queue being active
+ * will be guaranteed to see waitqueue addition _or_ subsequent
+ * tests in this thread will see the wakeup having taken place.
+ *
+ * The spin_unlock() itself is semi-permeable and only protects
+ * one way (it only protects stuff inside the critical region and
+ * stops them from bleeding out - it would still allow subsequent
+ * loads to move into the the critical region).
+ */
 void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
 {
        unsigned long flags;
 
-       __set_current_state(state);
        wait->flags &= ~WQ_FLAG_EXCLUSIVE;
        spin_lock_irqsave(&q->lock, flags);
        if (list_empty(&wait->task_list))
                __add_wait_queue(q, wait);
+       set_current_state(state);
        spin_unlock_irqrestore(&q->lock, flags);
 }
 
@@ -144,11 +157,11 @@ prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
 {
        unsigned long flags;
 
-       __set_current_state(state);
        wait->flags |= WQ_FLAG_EXCLUSIVE;
        spin_lock_irqsave(&q->lock, flags);
        if (list_empty(&wait->task_list))
                __add_wait_queue_tail(q, wait);
+       set_current_state(state);
        spin_unlock_irqrestore(&q->lock, flags);
 }