gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/membarrier.cc (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include <errno.h> 16 #include <signal.h> 17 #include <sys/syscall.h> 18 #include <sys/types.h> 19 #include <unistd.h> 20 21 #include <atomic> 22 23 #include "absl/time/clock.h" 24 #include "absl/time/time.h" 25 #include "test/util/cleanup.h" 26 #include "test/util/logging.h" 27 #include "test/util/memory_util.h" 28 #include "test/util/posix_error.h" 29 #include "test/util/test_util.h" 30 #include "test/util/thread_util.h" 31 32 namespace gvisor { 33 namespace testing { 34 35 namespace { 36 37 // This is the classic test case for memory fences on architectures with total 38 // store ordering; see e.g. Intel SDM Vol. 3A Sec. 8.2.3.4 "Loads May Be 39 // Reordered with Earlier Stores to Different Locations". In each iteration of 40 // the test, given two variables X and Y initially set to 0 41 // (MembarrierTestSharedState::local_var and remote_var in the code), two 42 // threads execute as follows: 43 // 44 // T1 T2 45 // -- -- 46 // 47 // X = 1 Y = 1 48 // T1fence() T2fence() 49 // read Y read X 50 // 51 // On architectures where memory writes may be locally buffered by each CPU 52 // (essentially all architectures), if T1fence() and T2fence() are omitted or 53 // ineffective, it is possible for both T1 and T2 to read 0 because the memory 54 // write from the other CPU is not yet visible outside that CPU. T1fence() and 55 // T2fence() are expected to perform the necessary synchronization to restore 56 // sequential consistency: both threads agree on a order of memory accesses that 57 // is consistent with program order in each thread, such that at least one 58 // thread reads 1. 59 // 60 // In the NoMembarrier test, T1fence() and T2fence() are both ordinary memory 61 // fences establishing ordering between memory accesses before and after the 62 // fence (std::atomic_thread_fence). In all other test cases, T1fence() is not a 63 // memory fence at all, but only prevents compiler reordering of memory accesses 64 // (std::atomic_signal_fence); T2fence() is an invocation of the membarrier() 65 // syscall, which establishes ordering of memory accesses before and after the 66 // syscall on both threads. 67 68 template <typename F> 69 int DoMembarrierTestSide(std::atomic<int>* our_var, 70 std::atomic<int> const& their_var, 71 F const& test_fence) { 72 our_var->store(1, std::memory_order_relaxed); 73 test_fence(); 74 return their_var.load(std::memory_order_relaxed); 75 } 76 77 struct MembarrierTestSharedState { 78 std::atomic<int64_t> remote_iter_cur; 79 std::atomic<int64_t> remote_iter_done; 80 std::atomic<int> local_var; 81 std::atomic<int> remote_var; 82 int remote_obs_of_local_var; 83 84 void Init() { 85 remote_iter_cur.store(-1, std::memory_order_relaxed); 86 remote_iter_done.store(-1, std::memory_order_relaxed); 87 } 88 }; 89 90 // Special value for MembarrierTestSharedState::remote_iter_cur indicating that 91 // the remote thread should terminate. 92 constexpr int64_t kRemoteIterStop = -2; 93 94 // Must be async-signal-safe. 95 template <typename F> 96 void RunMembarrierTestRemoteSide(MembarrierTestSharedState* state, 97 F const& test_fence) { 98 int64_t i = 0; 99 int64_t cur; 100 while (true) { 101 while ((cur = state->remote_iter_cur.load(std::memory_order_acquire)) < i) { 102 if (cur == kRemoteIterStop) { 103 return; 104 } 105 // spin 106 } 107 state->remote_obs_of_local_var = 108 DoMembarrierTestSide(&state->remote_var, state->local_var, test_fence); 109 state->remote_iter_done.store(i, std::memory_order_release); 110 i++; 111 } 112 } 113 114 template <typename F> 115 void RunMembarrierTestLocalSide(MembarrierTestSharedState* state, 116 F const& test_fence) { 117 // On test completion, instruct the remote thread to terminate. 118 Cleanup cleanup_remote([&] { 119 state->remote_iter_cur.store(kRemoteIterStop, std::memory_order_relaxed); 120 }); 121 122 int64_t i = 0; 123 absl::Time end = absl::Now() + absl::Seconds(5); // arbitrary test duration 124 while (absl::Now() < end) { 125 // Reset both vars to 0. 126 state->local_var.store(0, std::memory_order_relaxed); 127 state->remote_var.store(0, std::memory_order_relaxed); 128 // Instruct the remote thread to begin this iteration. 129 state->remote_iter_cur.store(i, std::memory_order_release); 130 // Perform our side of the test. 131 auto local_obs_of_remote_var = 132 DoMembarrierTestSide(&state->local_var, state->remote_var, test_fence); 133 // Wait for the remote thread to finish this iteration. 134 while (state->remote_iter_done.load(std::memory_order_acquire) < i) { 135 // spin 136 } 137 ASSERT_TRUE(local_obs_of_remote_var != 0 || 138 state->remote_obs_of_local_var != 0); 139 i++; 140 } 141 } 142 143 TEST(MembarrierTest, NoMembarrier) { 144 MembarrierTestSharedState state; 145 state.Init(); 146 147 ScopedThread remote_thread([&] { 148 RunMembarrierTestRemoteSide( 149 &state, [] { std::atomic_thread_fence(std::memory_order_seq_cst); }); 150 }); 151 RunMembarrierTestLocalSide( 152 &state, [] { std::atomic_thread_fence(std::memory_order_seq_cst); }); 153 } 154 155 enum membarrier_cmd { 156 MEMBARRIER_CMD_QUERY = 0, 157 MEMBARRIER_CMD_GLOBAL = (1 << 0), 158 MEMBARRIER_CMD_GLOBAL_EXPEDITED = (1 << 1), 159 MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED = (1 << 2), 160 MEMBARRIER_CMD_PRIVATE_EXPEDITED = (1 << 3), 161 MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED = (1 << 4), 162 }; 163 164 int membarrier(membarrier_cmd cmd, int flags) { 165 return syscall(SYS_membarrier, cmd, flags); 166 } 167 168 PosixErrorOr<int> SupportedMembarrierCommands() { 169 int cmds = membarrier(MEMBARRIER_CMD_QUERY, 0); 170 if (cmds < 0) { 171 if (errno == ENOSYS) { 172 // No commands are supported. 173 return 0; 174 } 175 return PosixError(errno, "membarrier(MEMBARRIER_CMD_QUERY) failed"); 176 } 177 return cmds; 178 } 179 180 TEST(MembarrierTest, Global) { 181 SKIP_IF((ASSERT_NO_ERRNO_AND_VALUE(SupportedMembarrierCommands()) & 182 MEMBARRIER_CMD_GLOBAL) == 0); 183 184 Mapping m = ASSERT_NO_ERRNO_AND_VALUE( 185 MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED)); 186 auto state = static_cast<MembarrierTestSharedState*>(m.ptr()); 187 state->Init(); 188 189 pid_t const child_pid = fork(); 190 if (child_pid == 0) { 191 // In child process. 192 RunMembarrierTestRemoteSide( 193 state, [] { TEST_PCHECK(membarrier(MEMBARRIER_CMD_GLOBAL, 0) == 0); }); 194 _exit(0); 195 } 196 // In parent process. 197 ASSERT_THAT(child_pid, SyscallSucceeds()); 198 Cleanup cleanup_child([&] { 199 int status; 200 ASSERT_THAT(waitpid(child_pid, &status, 0), 201 SyscallSucceedsWithValue(child_pid)); 202 EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) 203 << " status " << status; 204 }); 205 RunMembarrierTestLocalSide( 206 state, [] { std::atomic_signal_fence(std::memory_order_seq_cst); }); 207 } 208 209 TEST(MembarrierTest, GlobalExpedited) { 210 constexpr int kRequiredCommands = MEMBARRIER_CMD_GLOBAL_EXPEDITED | 211 MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED; 212 SKIP_IF((ASSERT_NO_ERRNO_AND_VALUE(SupportedMembarrierCommands()) & 213 kRequiredCommands) != kRequiredCommands); 214 215 ASSERT_THAT(membarrier(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED, 0), 216 SyscallSucceeds()); 217 218 Mapping m = ASSERT_NO_ERRNO_AND_VALUE( 219 MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED)); 220 auto state = static_cast<MembarrierTestSharedState*>(m.ptr()); 221 state->Init(); 222 223 pid_t const child_pid = fork(); 224 if (child_pid == 0) { 225 // In child process. 226 RunMembarrierTestRemoteSide(state, [] { 227 TEST_PCHECK(membarrier(MEMBARRIER_CMD_GLOBAL_EXPEDITED, 0) == 0); 228 }); 229 _exit(0); 230 } 231 // In parent process. 232 ASSERT_THAT(child_pid, SyscallSucceeds()); 233 Cleanup cleanup_child([&] { 234 int status; 235 ASSERT_THAT(waitpid(child_pid, &status, 0), 236 SyscallSucceedsWithValue(child_pid)); 237 EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) 238 << " status " << status; 239 }); 240 RunMembarrierTestLocalSide( 241 state, [] { std::atomic_signal_fence(std::memory_order_seq_cst); }); 242 } 243 244 TEST(MembarrierTest, PrivateExpedited) { 245 constexpr int kRequiredCommands = MEMBARRIER_CMD_PRIVATE_EXPEDITED | 246 MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED; 247 SKIP_IF((ASSERT_NO_ERRNO_AND_VALUE(SupportedMembarrierCommands()) & 248 kRequiredCommands) != kRequiredCommands); 249 250 ASSERT_THAT(membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, 0), 251 SyscallSucceeds()); 252 253 MembarrierTestSharedState state; 254 state.Init(); 255 256 ScopedThread remote_thread([&] { 257 RunMembarrierTestRemoteSide(&state, [] { 258 TEST_PCHECK(membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0) == 0); 259 }); 260 }); 261 RunMembarrierTestLocalSide( 262 &state, [] { std::atomic_signal_fence(std::memory_order_seq_cst); }); 263 } 264 265 } // namespace 266 267 } // namespace testing 268 } // namespace gvisor