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