pstore2
shared_memory.hpp
Go to the documentation of this file.
1 //===- include/pstore/os/shared_memory.hpp ----------------*- mode: C++ -*-===//
2 //* _ _ *
3 //* ___| |__ __ _ _ __ ___ __| | _ __ ___ ___ _ __ ___ ___ _ __ _ _ *
4 //* / __| '_ \ / _` | '__/ _ \/ _` | | '_ ` _ \ / _ \ '_ ` _ \ / _ \| '__| | | | *
5 //* \__ \ | | | (_| | | | __/ (_| | | | | | | | __/ | | | | | (_) | | | |_| | *
6 //* |___/_| |_|\__,_|_| \___|\__,_| |_| |_| |_|\___|_| |_| |_|\___/|_| \__, | *
7 //* |___/ *
8 //===----------------------------------------------------------------------===//
9 //
10 // Part of the pstore project, under the Apache License v2.0 with LLVM Exceptions.
11 // See https://github.com/SNSystems/pstore/blob/master/LICENSE.txt for license
12 // information.
13 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
14 //
15 //===----------------------------------------------------------------------===//
17 
18 #ifndef PSTORE_OS_SHARED_MEMORY_HPP
19 #define PSTORE_OS_SHARED_MEMORY_HPP
20 
21 #include <atomic>
22 #include <cstring>
23 #include <mutex>
24 #include <sstream>
25 
26 #ifndef _WIN32
27 # include <cerrno>
28 # include <fcntl.h>
29 # include <sys/mman.h>
30 # include <sys/stat.h>
31 # include <unistd.h>
32 #endif // !_WIN32
33 
34 #include "pstore/os/uint64.hpp"
35 #include "pstore/support/error.hpp"
37 
38 namespace pstore {
39  namespace posix {
40 
41  // shm_name
42  // ~~~~~~~~~~
51  template <typename SpanType>
52  auto shm_name (std::string const & name, SpanType arr) -> ::pstore::gsl::zstring {
53  using difference_type =
54  std::iterator_traits<std::string::const_iterator>::difference_type;
55 #ifndef NDEBUG
56  using udifference_type = std::make_unsigned<difference_type>::type;
57 #endif
58  static_assert (SpanType::extent >= 2,
59  "The posix_mutex_name span must be fixed size and able to hold "
60  "at least 2 characters");
61  auto out = arr.begin ();
62  *(out++) = '/';
63 
64  auto name_begin = std::begin (name);
65  auto name_end = name_begin;
66  PSTORE_ASSERT (name.length () <= static_cast<udifference_type> (
67  std::numeric_limits<difference_type>::max ()));
68  auto const name_length = static_cast<difference_type> (name.length ());
69  std::advance (name_end, std::min (SpanType::extent - 2, name_length));
70  out = std::copy (name_begin, name_end, out);
71  *out = '\0';
72  return arr.data ();
73  }
74 
75  template <std::size_t N>
76  auto shm_name (std::string const & name, std::array<char, N> & arr)
77  -> ::pstore::gsl::zstring {
78 
79  static_assert (N <= std::numeric_limits<std::ptrdiff_t>::max (),
80  "array size must not exected ptrdiff_t max");
81  return shm_name (name, ::pstore::gsl::make_span (arr));
82  }
84 
85  } // namespace posix
86 } // namespace pstore
87 
88 
89 namespace pstore {
91  template <typename Ty>
92  class shared_memory {
93  public:
94  shared_memory ();
95  explicit shared_memory (std::string const & name);
96  shared_memory (shared_memory const &) = delete;
97  shared_memory (shared_memory && rhs) noexcept;
98 
99  ~shared_memory ();
100 
101  shared_memory & operator= (shared_memory const &) = delete;
102  shared_memory & operator= (shared_memory && rhs) noexcept;
103 
104  Ty & operator* () { return *get (); }
105  Ty const & operator* () const { return *get (); }
106  Ty * operator-> () { return get (); }
107  Ty const * operator-> () const { return get (); }
108 
109  Ty * get () {
110  auto p = ptr_.get ();
111  return (p != nullptr) ? &p->contents : nullptr;
112  }
113  Ty const * get () const {
114  auto p = ptr_.get ();
115  return (p != nullptr) ? &p->contents : nullptr;
116  }
117 
118  private:
120  class spin_lock {
121  public:
124  explicit spin_lock (gsl::not_null<std::atomic_flag *> const lock)
125  : lock_ (lock) {}
126 
127  // No copying or assignment.
128  spin_lock (spin_lock const &) = delete;
129  spin_lock & operator= (spin_lock const &) = delete;
130 
134  void lock () noexcept {
135  while (lock_->test_and_set (std::memory_order_acquire)) {
136  // Spin until acquired
137  }
138  }
139 
144  void unlock () noexcept {
145  lock_->clear (std::memory_order_release); // Release lock
146  }
147 
148  private:
149  std::atomic_flag * const lock_;
150  };
151 
152  struct value_type {
155  std::atomic_flag lock{false};
156 
160  std::atomic_flag init_done{false};
161 
162  Ty contents;
163  };
164  static_assert (std::is_standard_layout<value_type>::value,
165  "value_type must be StandardLayout");
166 
167 
168 #ifdef _WIN32
169  using os_file_handle = HANDLE;
170 #else
171  using os_file_handle = int;
172 #endif
173 
174  class file_mapping {
175  public:
176  explicit file_mapping (gsl::czstring const name)
177  : descriptor_ (open (name)) {}
178  ~file_mapping () noexcept;
179  void set_size ();
180  os_file_handle get () { return descriptor_; }
181 
182  private:
183  static os_file_handle open (gsl::czstring name);
184  os_file_handle descriptor_;
185  };
186 
187  using pointer_type = std::unique_ptr<value_type, void (*) (value_type *)>;
188  auto mmap (os_file_handle map_file) -> pointer_type;
190  static void unmap (value_type * p);
191 
192  struct shm_name {
193  public:
194  shm_name () = default;
195  explicit shm_name (std::string const & name);
196  shm_name (shm_name && rhs) noexcept = default;
197  shm_name (shm_name const & rhs) = delete;
198 
199  shm_name & operator= (shm_name const & rhs) = delete;
200  shm_name & operator= (shm_name && rhs) noexcept = default;
201 
202  gsl::czstring c_str () const noexcept { return name_.c_str (); }
203  bool empty () const noexcept { return name_.empty (); }
204 
205  private:
206  std::string name_;
207  };
208  shm_name name_;
209  pointer_type ptr_;
210  };
211 
213  std::size_t get_pshmnamlen () noexcept;
214 
215 //***************************
216 //* shared_memory::shm_name *
217 //***************************
218 // (ctor)
219 // ~~~~~~
220 #ifdef _WIN32
221  template <typename Ty>
223  : name_ (name) {
224  // Can't rely on being able to create objects in the global namespace. This requires
225  // "SE_CREATE_GLOBAL_NAME" privileges, which is disabled by default for administrators,
226  // services, and the local system account.
227  // std::string const object_name = "Global\\" + name;
228  std::replace (name_.begin (), name_.end (), '\\', '/');
229  }
230 #else
231  template <typename Ty>
232  shared_memory<Ty>::shared_memory::shm_name::shm_name (std::string const & name) {
233  std::size_t const len = get_pshmnamlen ();
234  name_.reserve (len);
235  name_ = '/';
236  std::copy_n (std::begin (name), std::min (name.length (), len - 1),
237  std::back_inserter (name_));
238  }
239 #endif
240 
241 
242  //*****************
243  //* shared_memory *
244  //*****************
245  // (ctor)
246  // ~~~~~~
247  template <typename Ty>
249  : name_ ()
250  , ptr_ (nullptr, &unmap) {}
251 
252  template <typename Ty>
253  shared_memory<Ty>::shared_memory (std::string const & name)
254  : name_ (name)
255  , ptr_ (nullptr, &unmap) {
256 
257  file_mapping mapping (name_.c_str ());
258  ptr_ = mmap (mapping.get ());
259 
260  // The initialization of 'contents' is guarded by a simple atomic spin-lock mutex. We MUST
261  // NOT crash whilst holding this
262  // mutex or we'll hang the next time through here.
263  spin_lock sl (&ptr_->lock);
264  std::lock_guard<spin_lock> lock (sl);
265 
266  if (!ptr_->init_done.test_and_set ()) {
267  static_assert (sizeof (ptr_->contents) == sizeof (Ty),
268  "placement new buffer was not the expected size");
269  new (&ptr_->contents) Ty;
270  }
271  }
272 
273  template <typename Ty>
274  shared_memory<Ty>::shared_memory (shared_memory && rhs) noexcept
275  : name_ (std::move (rhs.name_))
276  , ptr_ (std::move (rhs.ptr_)) {}
277 
278  // (dtor)
279  // ~~~~~~
280  template <typename Ty>
282 #ifndef _WIN32
283  if (!name_.empty ()) {
284  ::shm_unlink (name_.c_str ());
285  }
286 #endif
287  }
288 
289  // operator=
290  // ~~~~~~~~~
291  template <typename Ty>
292  auto shared_memory<Ty>::operator= (shared_memory && rhs) noexcept -> shared_memory & {
293  if (this != &rhs) {
294  name_ = std::move (rhs.name_);
295  ptr_ = std::move (rhs.ptr_);
296  }
297  return *this;
298  }
299 
300 #ifdef _WIN32
301 
302  // unmap
303  // ~~~~~
304  template <typename Ty>
305  void shared_memory<Ty>::unmap (value_type * const p) {
306  if (::UnmapViewOfFile (p) == 0) {
307  auto const error = ::GetLastError ();
308  raise (win32_erc (error), "UnmapViewOfFile");
309  }
310  }
311 
312  // mmap
313  // ~~~~
314  template <typename Ty>
315  auto shared_memory<Ty>::mmap (os_file_handle const map_file) -> pointer_type {
316  auto mapped_ptr = static_cast<value_type *> (::MapViewOfFile (map_file, FILE_MAP_ALL_ACCESS,
317  0, // file offset (high)
318  0, // file offset (low)
319  sizeof (Ty)));
320  if (mapped_ptr == nullptr) {
321  auto const error = ::GetLastError ();
322  raise (win32_erc (error), "MapViewOfFile");
323  }
324  return {mapped_ptr, &unmap};
325  }
326 
327 #else
328 
329  // unmap
330  // ~~~~~
331  template <typename Ty>
332  void shared_memory<Ty>::unmap (value_type * const p) {
333  if (::munmap (p, sizeof (Ty)) == -1) {
334  raise (errno_erc{errno}, "munmap");
335  }
336  }
337 
338  // mmap
339  // ~~~~
340  template <typename Ty>
341  auto shared_memory<Ty>::mmap (os_file_handle const fd) -> pointer_type {
342  auto ptr = static_cast<value_type *> (
343  ::mmap (nullptr, sizeof (Ty), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); // NOLINT
344  if (ptr == MAP_FAILED) { // NOLINT
345  raise (errno_erc{errno}, "mmap");
346  }
347  return {ptr, &unmap};
348  }
349 
350 #endif
351 
352 
353  //***********************************
354  //* shared_memory<Ty>::file_mapping *
355  //***********************************
356 
357 #ifdef _WIN32
358 
359  // (dtor)
360  // ~~~~~~
361  template <typename Ty>
363  ::CloseHandle (descriptor_);
364  descriptor_ = nullptr;
365  }
366 
367  // open
368  // ~~~~
369  template <typename Ty>
370  auto shared_memory<Ty>::file_mapping::open (gsl::czstring const name) -> os_file_handle {
371 
372  HANDLE map_file = ::CreateFileMappingW (
373  INVALID_HANDLE_VALUE, // use paging file
374  nullptr, // default security
375  PAGE_READWRITE, // read/write access
376  uint64_high4 (sizeof (Ty)), // maximum object size (high-order DWORD)
377  uint64_low4 (sizeof (Ty)), // maximum object size (low-order DWORD)
378  utf::win32::to16 (name).c_str ()); // name of mapping object
379  if (map_file == nullptr) {
380  std::ostringstream str;
381 char const * n = "hello";
382  str << "Couldn't create a file mapping for " << pstore::quoted (n);
383  raise (win32_erc (::GetLastError ()), str.str ());
384  }
385  return map_file;
386  }
387 
388 #else // !_WIN32
389 
390  // (dtor)
391  // ~~~~~~
392  template <typename Ty>
394  ::close (descriptor_);
395  descriptor_ = -1;
396  }
397 
398  // open
399  // ~~~~
400  template <typename Ty>
401  auto shared_memory<Ty>::file_mapping::open (gsl::czstring const name) -> os_file_handle {
402  int const fd = ::shm_open (name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); // NOLINT
403  if (fd == -1) {
404  int const error = errno;
405  if (error == ENAMETOOLONG) {
406  std::ostringstream str;
407  str << "shared memory object name (" << name << ") is too long";
408  raise (errno_erc{error}, str.str ());
409  } else {
410  raise (errno_erc{error}, "shm_open");
411  }
412  }
413 
414  // If the shared memory object doesn't have room for at least sizeof(Ty) bytes,
415  // then we need to grow it before the memory map operation.
416  struct stat st;
417  if (::fstat (fd, &st) == -1) {
418  raise (errno_erc{errno}, "fstat");
419  }
420  if (st.st_size < static_cast<off_t> (sizeof (Ty))) {
421  if (::ftruncate (fd, sizeof (Ty)) == -1) {
422  raise (errno_erc{errno}, "ftruncate");
423  }
424  }
425  return fd;
426  }
427 
428 #endif // !_WIN32
429 } // namespace pstore
430 #endif // PSTORE_OS_SHARED_MEMORY_HPP
auto shm_name(std::string const &name, SpanType arr) -> ::pstore::gsl::zstring
Definition: shared_memory.hpp:52
Opens a shared memory object containing type Ty with the given name.
Definition: shared_memory.hpp:92
std::size_t get_pshmnamlen() noexcept
A function which returns the maximum length of a shared memory object name.
Definition: shared_memory.cpp:47
Convenience functions for breaking up a uint64_t value for use with Win32 API functions.
Definition: gsl.hpp:589
This class is a tiny wrapper that allows an errno value to be passed to std::make_error_code().
Definition: error.hpp:99
auto quoted(std::string const &str)
Wraps quotation marks around a string for presentation to the user.
Definition: quoted.hpp:38
Definition: nonpod2.cpp:40
Wrapping quotation marks around strings for output to the user.
Provides an pstore-specific error codes and a suitable error category for them.