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.
Showing the changes to kernel (1 of 1 file). Show all
Unified · Split
| @@ -6,6 +6,134 @@ · 5 unmodified lines | ||
| 6 | 6 | */ |
| 7 | 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 | 138 | * Bitmask made from a "or" of all commands within enum membarrier_cmd, |
| 11 | 139 | * except MEMBARRIER_CMD_QUERY. |