Document membarrier ordering scenarios in membarrier.c. Thanks to Alan Stern for refreshing my memory. Now that I have those in mind, it seems appropriate to serialize them to comments for posterity.
Unified · Split
| @@ -6,6 +6,134 @@ · 5 unmodified lines | |||
| 6 | */ | 6 | */ |
| 7 | #include "sched.h" | 7 | #include "sched.h" |
| 8 | 8 | ||
| 9 | /* | ||
| 10 | * For documentation purposes, here are some membarrier ordering | ||
| 11 | * scenarios to keep in mind: | ||
| 12 | * | ||
| 13 | * A) Userspace thread execution after IPI vs membarrier's memory | ||
| 14 | * barrier before sending the IPI | ||
| 15 | * | ||
| 16 | * Userspace variables: | ||
| 17 | * | ||
| 18 | * int x = 0, y = 0; | ||
| 19 | * | ||
| 20 | * The memory barrier at the start of membarrier() on CPU0 is necessary in | ||
| 21 | * order to enforce the guarantee that any writes occurring on CPU0 before | ||
| 22 | * the membarrier() is executed will be visible to any code executing on | ||
| 23 | * CPU1 after the IPI-induced memory barrier: | ||
| 24 | * | ||
| 25 | * CPU0 CPU1 | ||
| 26 | * | ||
| 27 | * x = 1 | ||
| 28 | * membarrier(): | ||
| 29 | * a: smp_mb() | ||
| 30 | * b: send IPI IPI-induced mb | ||
| 31 | * c: smp_mb() | ||
| 32 | * r2 = y | ||
| 33 | * y = 1 | ||
| 34 | * barrier() | ||
| 35 | * r1 = x | ||
| 36 | * | ||
| 37 | * BUG_ON(r1 == 0 && r2 == 0) | ||
| 38 | * | ||
| 39 | * The write to y and load from x by CPU1 are unordered by the hardware, | ||
| 40 | * so it's possible to have "r1 = x" reordered before "y = 1" at any | ||
| 41 | * point after (b). If the memory barrier at (a) is omitted, then "x = 1" | ||
| 42 | * can be reordered after (a) (although not after (c)), so we get r1 == 0 | ||
| 43 | * and r2 == 0. This violates the guarantee that membarrier() is | ||
| 44 | * supposed by provide. | ||
| 45 | * | ||
| 46 | * The timing of the memory barrier at (a) has to ensure that it executes | ||
| 47 | * before the IPI-induced memory barrier on CPU1. | ||
| 48 | * | ||
| 49 | * B) Userspace thread execution before IPI vs membarrier's memory | ||
| 50 | * barrier after completing the IPI | ||
| 51 | * | ||
| 52 | * Userspace variables: | ||
| 53 | * | ||
| 54 | * int x = 0, y = 0; | ||
| 55 | * | ||
| 56 | * The memory barrier at the end of membarrier() on CPU0 is necessary in | ||
| 57 | * order to enforce the guarantee that any writes occurring on CPU1 before | ||
| 58 | * the membarrier() is executed will be visible to any code executing on | ||
| 59 | * CPU0 after the membarrier(): | ||
| 60 | * | ||
| 61 | * CPU0 CPU1 | ||
| 62 | * | ||
| 63 | * x = 1 | ||
| 64 | * barrier() | ||
| 65 | * y = 1 | ||
| 66 | * r2 = y | ||
| 67 | * membarrier(): | ||
| 68 | * a: smp_mb() | ||
| 69 | * b: send IPI IPI-induced mb | ||
| 70 | * c: smp_mb() | ||
| 71 | * r1 = x | ||
| 72 | * BUG_ON(r1 == 0 && r2 == 1) | ||
| 73 | * | ||
| 74 | * The writes to x and y are unordered by the hardware, so it's possible to | ||
| 75 | * have "r2 = 1" even though the write to x doesn't execute until (b). If | ||
| 76 | * the memory barrier at (c) is omitted then "r1 = x" can be reordered | ||
| 77 | * before (b) (although not before (a)), so we get "r1 = 0". This violates | ||
| 78 | * the guarantee that membarrier() is supposed to provide. | ||
| 79 | * | ||
| 80 | * The timing of the memory barrier at (c) has to ensure that it executes | ||
| 81 | * after the IPI-induced memory barrier on CPU1. | ||
| 82 | * | ||
| 83 | * C) Scheduling userspace thread -> kthread -> userspace thread vs membarrier | ||
| 84 | * | ||
| 85 | * CPU0 CPU1 | ||
| 86 | * | ||
| 87 | * membarrier(): | ||
| 88 | * a: smp_mb() | ||
| 89 | * d: switch to kthread (includes mb) | ||
| 90 | * b: read rq->curr->mm == NULL | ||
| 91 | * e: switch to user (includes mb) | ||
| 92 | * c: smp_mb() | ||
| 93 | * | ||
| 94 | * Using the scenario from (A), we can show that (a) needs to be paired | ||
| 95 | * with (e). Using the scenario from (B), we can show that (c) needs to | ||
| 96 | * be paired with (d). | ||
| 97 | * | ||
| 98 | * D) exit_mm vs membarrier | ||
| 99 | * | ||
| 100 | * Two thread groups are created, A and B. Thread group B is created by | ||
| 101 | * issuing clone from group A with flag CLONE_VM set, but not CLONE_THREAD. | ||
| 102 | * Let's assume we have a single thread within each thread group (Thread A | ||
| 103 | * and Thread B). Thread A runs on CPU0, Thread B runs on CPU1. | ||
| 104 | * | ||
| 105 | * CPU0 CPU1 | ||
| 106 | * | ||
| 107 | * membarrier(): | ||
| 108 | * a: smp_mb() | ||
| 109 | * exit_mm(): | ||
| 110 | * d: smp_mb() | ||
| 111 | * e: current->mm = NULL | ||
| 112 | * b: read rq->curr->mm == NULL | ||
| 113 | * c: smp_mb() | ||
| 114 | * | ||
| 115 | * Using scenario (B), we can show that (c) needs to be paired with (d). | ||
| 116 | * | ||
| 117 | * E) kthread_{use,unuse}_mm vs membarrier | ||
| 118 | * | ||
| 119 | * CPU0 CPU1 | ||
| 120 | * | ||
| 121 | * membarrier(): | ||
| 122 | * a: smp_mb() | ||
| 123 | * kthread_unuse_mm() | ||
| 124 | * d: smp_mb() | ||
| 125 | * e: current->mm = NULL | ||
| 126 | * b: read rq->curr->mm == NULL | ||
| 127 | * kthread_use_mm() | ||
| 128 | * f: current->mm = mm | ||
| 129 | * g: smp_mb() | ||
| 130 | * c: smp_mb() | ||
| 131 | * | ||
| 132 | * Using the scenario from (A), we can show that (a) needs to be paired | ||
| 133 | * with (g). Using the scenario from (B), we can show that (c) needs to | ||
| 134 | * be paired with (d). | ||
| 135 | */ | ||
| 136 | |||
| 9 | /* | 137 | /* |
| 10 | * Bitmask made from a "or" of all commands within enum membarrier_cmd, | 138 | * Bitmask made from a "or" of all commands within enum membarrier_cmd, |
| 11 | * except MEMBARRIER_CMD_QUERY. | 139 | * except MEMBARRIER_CMD_QUERY. |