libfoedus-core
FOEDUS Core Library
foedus::memory::SharedMemory Class Referencefinal

Represents memory shared between processes. More...

Detailed Description

Represents memory shared between processes.

This class was initially a part of AlignedMemory, but we separated it out to reflect the special semantics of this type of memory.

Alloc/dealloc type
As this is a shared memory, there is only one choice unlike AlignedMemory. We always use shmget() with SHM_HUGETLB and shmat()/shmdt().
Pros/cons of System-V shmget()
We made this choice of share memory allocation API after trying all available options. The biggest pro of System-V shmget is that it does not need to mount a hugetlbfs for allocating hugepages. The cons are that it can't exceed kernel.shmmax and also we have to do a linux-dependent trick to reliably release the shared memory. For other choices, see the sections below.
Ownership
Every shared memory is allocated and owned by the master process. Forked/execed child processes, whether they are 'emulated' processes or not, do not have ownership thus should not release them at exit.
Moveable/Copiable
This object is NOT copiable. This object is moveable (the original object becomes null after the move).
Why not POSIX shm_open()
It can't use hugepages, period. shm_open is preferable because it's POSIX standard and also allows friendly naming to uniquefy the shared memory unlike shmget()'s integer ID. However, so far it doesn't allow hugepages and also we have to manually delete the file under /dev/shm.
Why not open() and mmap() with MAP_SHARED
It requires mounting hugetlbfs and also the user has to specify the path of the mounted file system in our configuration. This open/mmap is preferable because it is not affected by kernel.shmmax, which was an 'oversight' and might be corrected later, though. See the LWN article.
Why not mmap() with MAP_PRIVATE and MAP_SHARED
To allow sharing memory between arbitrary processes, we need non-private shared memory.
Debugging shared memories
ipcs -m ipcrm, etc etc.
Valgrind, shmat, and hugepages
Seems like valgrind has a bug on shmat with hugepages. I filed a bug report, not sure how it will be resolved. This causes all valgrind execution to fail on almost all testcases, crap. As a tentative work around, I check whether we are running on valgrind and, if so, get rid of SHM_HUGETLB from shmget call. Just for this reason, we have to include valgrind headers. gggrrrr.
See also
http://lwn.net/Articles/375096/
https://groups.google.com/forum/#!topic/fa.linux.kernel/OKplGDFf3EU
http://www.linuxquestions.org/questions/programming-9/shmctl-ipc_rmid-precludes-further-attachments-574636/
https://bugs.kde.org/show_bug.cgi?id=338995

Definition at line 92 of file shared_memory.hpp.

#include <shared_memory.hpp>

Public Member Functions

 SharedMemory () noexcept
 Empty constructor which allocates nothing. More...
 
 SharedMemory (const SharedMemory &other)=delete
 
SharedMemoryoperator= (const SharedMemory &other)=delete
 
 SharedMemory (SharedMemory &&other) noexcept
 Move constructor that steals the memory block from other. More...
 
SharedMemoryoperator= (SharedMemory &&other) noexcept
 Move assignment operator that steals the memory block from other. More...
 
 ~SharedMemory ()
 Automatically releases the memory. More...
 
ErrorStack alloc (const std::string &meta_path, uint64_t size, int numa_node, bool use_hugepages)
 Newly allocate a shared memory of given size on given NUMA node. More...
 
void attach (const std::string &meta_path, bool use_hugepages)
 Attach an already-allocated shared memory so that this object points to the memory. More...
 
const std::string & get_meta_path () const
 Returns the path of the meta file. More...
 
char * get_block () const
 Returns the memory block. More...
 
int get_shmid () const
 Returns the ID of this shared memory. More...
 
key_t get_shmkey () const
 Returns the key of this shared memory. More...
 
pid_t get_owner_pid () const
 If non-zero, it means the ID of the process that allocated the shared memory. More...
 
bool is_null () const
 Returns if this object doesn't hold a valid memory block. More...
 
bool is_owned () const
 Returns if this process owns this memory and is responsible to delete it. More...
 
uint64_t get_size () const
 Returns the byte size of the memory block. More...
 
int get_numa_node () const
 Where the physical memory is allocated. More...
 
void mark_for_release ()
 Marks the shared memory as being removed so that it will be reclaimed when all processes detach it. More...
 
void release_block ()
 Releases the memory block IF this process has an ownership. More...
 

Friends

std::ostream & operator<< (std::ostream &o, const SharedMemory &v)
 

Constructor & Destructor Documentation

foedus::memory::SharedMemory::SharedMemory ( )
inlinenoexcept

Empty constructor which allocates nothing.

Definition at line 95 of file shared_memory.hpp.

96  : size_(0), numa_node_(0), shmid_(0), shmkey_(0), owner_pid_(0), block_(CXX11_NULLPTR) {}
#define CXX11_NULLPTR
Used in public headers in place of "nullptr" of C++11.
Definition: cxx11.hpp:132
foedus::memory::SharedMemory::SharedMemory ( const SharedMemory other)
delete
foedus::memory::SharedMemory::SharedMemory ( SharedMemory &&  other)
noexcept

Move constructor that steals the memory block from other.

Definition at line 47 of file shared_memory.cpp.

47  : block_(nullptr) {
48  *this = std::move(other);
49 }
foedus::memory::SharedMemory::~SharedMemory ( )
inline

Automatically releases the memory.

Definition at line 114 of file shared_memory.hpp.

References release_block().

114 { release_block(); }
void release_block()
Releases the memory block IF this process has an ownership.

Here is the call graph for this function:

Member Function Documentation

ErrorStack foedus::memory::SharedMemory::alloc ( const std::string &  meta_path,
uint64_t  size,
int  numa_node,
bool  use_hugepages 
)

Newly allocate a shared memory of given size on given NUMA node.

This method should be called only at the master process.

Parameters
[in]meta_pathThis method will create a temporary meta file of this path and use it to generate a unique ID via ftok() and also put the size of the memory as content. It must be unique, for example '/tmp/foedus_shm_[master-PID]_vpool_[node-ID]'.
[in]sizeByte size of the memory block. Actual allocation is at least of this size.
[in]numa_nodeWhere the physical memory is allocated. Use for binding via libnuma.
[in]use_hugepagesWhether to use hugepages.
Attention
When memory allocation fails for some reason (eg Out-Of-Memory), this method does NOT fail nor throws an exception. Instead, it sets the block_ NULL. So, the caller is responsible for checking it after construction.

Definition at line 67 of file shared_memory.cpp.

References ERROR_STACK_MSG, foedus::fs::exists(), foedus::debugging::get_rdtsc(), foedus::kErrorCodeSocShmAllocFailed, foedus::kRetOk, foedus::assorted::os_error(), and release_block().

Referenced by foedus::soc::SharedMemoryRepo::allocate_shared_memories().

71  {
72  release_block();
73 
74  if (size % (1ULL << 21) != 0) {
75  size = ((size >> 21) + 1ULL) << 21;
76  }
77 
78  // create a meta file. we must first create it then generate key.
79  // shmkey will change whenever we modify the file.
80  if (fs::exists(fs::Path(meta_path))) {
81  std::string msg = std::string("Shared memory meta file already exists:") + meta_path;
82  return ERROR_STACK_MSG(kErrorCodeSocShmAllocFailed, msg.c_str());
83  }
84  std::ofstream file(meta_path, std::ofstream::binary);
85  if (!file.is_open()) {
86  std::string msg = std::string("Failed to create shared memory meta file:") + meta_path;
87  return ERROR_STACK_MSG(kErrorCodeSocShmAllocFailed, msg.c_str());
88  }
89 
90  // randomly generate shmkey. We initially used ftok(), but it occasionally gives lots of
91  // conflicts for some reason, esp on aarch64. we just need some random number, so here
92  // we use pid and CPU cycle.
93  pid_t the_pid = ::getpid();
94  uint64_t key64 = debugging::get_rdtsc() ^ the_pid;
95  key_t the_key = (key64 >> 32) ^ key64;
96 
97  if (the_key == 0) {
98  // rdtsc and getpid not working??
99  std::string msg = std::string("Dubious shmkey");
100  return ERROR_STACK_MSG(kErrorCodeSocShmAllocFailed, msg.c_str());
101  }
102 
103  // Write out the size/node/shmkey of the shared memory in the meta file
104  file.write(reinterpret_cast<char*>(&size), sizeof(size));
105  file.write(reinterpret_cast<char*>(&numa_node), sizeof(numa_node));
106  file.write(reinterpret_cast<char*>(&the_key), sizeof(key_t));
107  file.flush();
108  file.close();
109 
110  size_ = size;
111  numa_node_ = numa_node;
112  owner_pid_ = the_pid;
113  meta_path_ = meta_path;
114  shmkey_ = the_key;
115 
116  // if this is running under valgrind, we have to avoid using hugepages due to a bug in valgrind.
117  // When we are running on valgrind, we don't care performance anyway. So shouldn't matter.
118  if (RUNNING_ON_VALGRIND) {
119  use_hugepages = false;
120  }
121  // see https://bugs.kde.org/show_bug.cgi?id=338995
122 
123  // Use libnuma's numa_set_preferred to initialize the NUMA node of the memory.
124  // This is the only way to control numa allocation for shared memory.
125  // mbind does nothing for shared memory.
126  ScopedNumaPreferred numa_scope(numa_node, true);
127 
128  shmid_ = ::shmget(
129  shmkey_,
130  size_,
131  IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR | (use_hugepages ? SHM_HUGETLB : 0));
132  if (shmid_ == -1) {
133  std::string msg = std::string("shmget() failed! size=") + std::to_string(size_)
134  + std::string(", os_error=") + assorted::os_error() + std::string(", meta_path=") + meta_path;
135  return ERROR_STACK_MSG(kErrorCodeSocShmAllocFailed, msg.c_str());
136  }
137 
138  block_ = reinterpret_cast<char*>(::shmat(shmid_, nullptr, 0));
139 
140  if (block_ == reinterpret_cast<void*>(-1)) {
141  ::shmctl(shmid_, IPC_RMID, nullptr); // first thing. release it! before everything else.
142  block_ = nullptr;
143  std::stringstream msg;
144  msg << "shmat alloc failed!" << *this << ", error=" << assorted::os_error();
145  release_block();
146  std::string str = msg.str();
147  return ERROR_STACK_MSG(kErrorCodeSocShmAllocFailed, str.c_str());
148  }
149 
150  std::memset(block_, 0, size_); // see class comment for why we do this immediately
151  // This memset takes a very long time due to the issue in linux kernel:
152  // https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=8382d914ebf72092aa15cdc2a5dcedb2daa0209d
153  // In linux 3.15 and later, this problem gets resolved and highly parallelizable.
154  return kRetOk;
155 }
uint64_t get_rdtsc()
Returns the current CPU cycle via x86 RDTSC.
Definition: rdtsc.hpp:35
bool exists(const Path &p)
Returns if the file exists.
Definition: filesystem.hpp:128
std::string os_error()
Thread-safe strerror(errno).
const ErrorStack kRetOk
Normal return value for no-error case.
#define ERROR_STACK_MSG(e, m)
Overload of ERROR_STACK(e) to receive a custom error message.
0x0C01 : "SOC : Failed to allocate a shared memory. This is usually caused by a misconfigured envi...
Definition: error_code.hpp:220
void release_block()
Releases the memory block IF this process has an ownership.

Here is the call graph for this function:

Here is the caller graph for this function:

void foedus::memory::SharedMemory::attach ( const std::string &  meta_path,
bool  use_hugepages 
)

Attach an already-allocated shared memory so that this object points to the memory.

Parameters
[in]meta_pathPath of the temporary meta file that contains memory size and shared memory ID.
[in]use_hugepagesWhether to use hugepages.
Attention
If binding fails for some reason, this method does NOT fail nor throws an exception. Instead, it sets the block_ NULL. So, the caller is responsible for checking it after construction.

Definition at line 157 of file shared_memory.cpp.

References foedus::fs::exists(), foedus::assorted::os_error(), and release_block().

Referenced by foedus::soc::SharedMemoryRepo::attach_shared_memories().

157  {
158  release_block();
159  if (!fs::exists(fs::Path(meta_path))) {
160  std::cerr << "Shared memory meta file does not exist:" << meta_path << std::endl;
161  return;
162  }
163  // the meta file contains the size of the shared memory
164  std::ifstream file(meta_path, std::ifstream::binary);
165  if (!file.is_open()) {
166  std::cerr << "Failed to open shared memory meta file:" << meta_path << std::endl;
167  return;
168  }
169  uint64_t shared_size = 0;
170  int numa_node = 0;
171  key_t the_key = 0;
172  file.read(reinterpret_cast<char*>(&shared_size), sizeof(shared_size));
173  file.read(reinterpret_cast<char*>(&numa_node), sizeof(numa_node));
174  file.read(reinterpret_cast<char*>(&the_key), sizeof(key_t));
175  file.close();
176 
177  // we always use hugepages, so it's at least 2MB
178  if (shared_size < (1ULL << 21)) {
179  std::cerr << "Failed to read size of shared memory from meta file:" << meta_path
180  << ". It looks like:" << shared_size << std::endl;
181  return;
182  }
183  if (the_key == 0) {
184  std::cerr << "Failed to read shmkey from meta file:" << meta_path << std::endl;
185  return;
186  }
187 
188  size_ = shared_size;
189  numa_node_ = numa_node;
190  meta_path_ = meta_path;
191  shmkey_ = the_key;
192  owner_pid_ = 0;
193 
194  if (RUNNING_ON_VALGRIND) {
195  use_hugepages = false;
196  }
197  shmid_ = ::shmget(shmkey_, size_, use_hugepages ? SHM_HUGETLB : 0);
198  if (shmid_ == -1) {
199  std::cerr << "shmget() attach failed! size=" << size_ << ", error=" << assorted::os_error()
200  << std::endl;
201  return;
202  }
203 
204  block_ = reinterpret_cast<char*>(::shmat(shmid_, nullptr, 0));
205  if (block_ == reinterpret_cast<void*>(-1)) {
206  block_ = nullptr;
207  std::cerr << "shmat attach failed!" << *this << ", error=" << assorted::os_error() << std::endl;
208  release_block();
209  return;
210  }
211 }
bool exists(const Path &p)
Returns if the file exists.
Definition: filesystem.hpp:128
std::string os_error()
Thread-safe strerror(errno).
void release_block()
Releases the memory block IF this process has an ownership.

Here is the call graph for this function:

Here is the caller graph for this function:

char* foedus::memory::SharedMemory::get_block ( ) const
inline
const std::string& foedus::memory::SharedMemory::get_meta_path ( ) const
inline

Returns the path of the meta file.

Definition at line 142 of file shared_memory.hpp.

Referenced by foedus::memory::operator<<().

142 { return meta_path_; }

Here is the caller graph for this function:

int foedus::memory::SharedMemory::get_numa_node ( ) const
inline

Where the physical memory is allocated.

Definition at line 158 of file shared_memory.hpp.

Referenced by foedus::memory::operator<<().

158 { return numa_node_; }

Here is the caller graph for this function:

pid_t foedus::memory::SharedMemory::get_owner_pid ( ) const
inline

If non-zero, it means the ID of the process that allocated the shared memory.

Definition at line 150 of file shared_memory.hpp.

Referenced by foedus::memory::operator<<().

150 { return owner_pid_; }

Here is the caller graph for this function:

int foedus::memory::SharedMemory::get_shmid ( ) const
inline

Returns the ID of this shared memory.

Definition at line 146 of file shared_memory.hpp.

Referenced by foedus::memory::operator<<().

146 { return shmid_; }

Here is the caller graph for this function:

key_t foedus::memory::SharedMemory::get_shmkey ( ) const
inline

Returns the key of this shared memory.

Definition at line 148 of file shared_memory.hpp.

Referenced by foedus::memory::operator<<().

148 { return shmkey_; }

Here is the caller graph for this function:

uint64_t foedus::memory::SharedMemory::get_size ( ) const
inline

Returns the byte size of the memory block.

Definition at line 156 of file shared_memory.hpp.

Referenced by foedus::memory::operator<<().

156 { return size_; }

Here is the caller graph for this function:

bool foedus::memory::SharedMemory::is_null ( ) const
inline

Returns if this object doesn't hold a valid memory block.

Definition at line 152 of file shared_memory.hpp.

References CXX11_NULLPTR.

Referenced by foedus::soc::SharedMemoryRepo::attach_shared_memories(), and foedus::soc::SharedMemoryRepo::deallocate_shared_memories().

152 { return block_ == CXX11_NULLPTR; }
#define CXX11_NULLPTR
Used in public headers in place of "nullptr" of C++11.
Definition: cxx11.hpp:132

Here is the caller graph for this function:

bool foedus::memory::SharedMemory::is_owned ( ) const

Returns if this process owns this memory and is responsible to delete it.

Definition at line 63 of file shared_memory.cpp.

Referenced by foedus::memory::operator<<(), and release_block().

63  {
64  return owner_pid_ != 0 && owner_pid_ == ::getpid();
65 }

Here is the caller graph for this function:

void foedus::memory::SharedMemory::mark_for_release ( )

Marks the shared memory as being removed so that it will be reclaimed when all processes detach it.

This is part of deallocation of shared memory in master process. After calling this method, no process can attach this shared memory. So, do not call this too early. However, on the other hand, do not call this too late. If the master process dies for an unexpected reason, the shared memory remains until next reboot. Call it as soon as child processes ack-ed that they have attached the memory or that there are some issues the master process should exit. This method is idempotent, meaning you can safely call this many times.

Definition at line 213 of file shared_memory.cpp.

Referenced by foedus::soc::SharedMemoryRepo::mark_for_release(), and release_block().

213  {
214  if (block_ != nullptr && shmid_ != 0) {
215  // Some material says that Linux allows shmget even after shmctl(IPC_RMID), but it doesn't.
216  // It allows shmat() after shmctl(IPC_RMID), but not shmget().
217  // So we have to invoke IPC_RMID after all child processes acked.
218  ::shmctl(shmid_, IPC_RMID, nullptr);
219  }
220 }

Here is the caller graph for this function:

SharedMemory& foedus::memory::SharedMemory::operator= ( const SharedMemory other)
delete
SharedMemory & foedus::memory::SharedMemory::operator= ( SharedMemory &&  other)
noexcept

Move assignment operator that steals the memory block from other.

Definition at line 50 of file shared_memory.cpp.

References release_block().

50  {
51  release_block();
52  meta_path_ = other.meta_path_;
53  size_ = other.size_;
54  numa_node_ = other.numa_node_;
55  shmid_ = other.shmid_;
56  shmkey_ = other.shmkey_;
57  owner_pid_ = other.owner_pid_;
58  block_ = other.block_;
59  other.block_ = nullptr;
60  return *this;
61 }
void release_block()
Releases the memory block IF this process has an ownership.

Here is the call graph for this function:

void foedus::memory::SharedMemory::release_block ( )

Releases the memory block IF this process has an ownership.

Definition at line 222 of file shared_memory.cpp.

References is_owned(), mark_for_release(), foedus::assorted::os_error(), and foedus::fs::remove().

Referenced by alloc(), attach(), foedus::soc::SharedMemoryRepo::deallocate_shared_memories(), operator=(), and ~SharedMemory().

222  {
223  if (block_ != nullptr) {
224  // mark the memory to be reclaimed
225  if (is_owned()) {
227  }
228 
229  // Just detach it. as we already invoked shmctl(IPC_RMID) at beginning, linux will
230  // automatically release it once the reference count reaches zero.
231  int dt_ret = ::shmdt(block_);
232  if (dt_ret == -1) {
233  std::cerr << "shmdt() failed." << *this << ", error=" << assorted::os_error() << std::endl;
234  }
235 
236  block_ = nullptr;
237 
238  // clean up meta file.
239  if (is_owned()) {
240  std::remove(meta_path_.c_str());
241  }
242  }
243 }
bool remove(const Path &p)
Deletes a regular file or an empty directory.
Definition: filesystem.cpp:132
bool is_owned() const
Returns if this process owns this memory and is responsible to delete it.
std::string os_error()
Thread-safe strerror(errno).
void mark_for_release()
Marks the shared memory as being removed so that it will be reclaimed when all processes detach it...

Here is the call graph for this function:

Here is the caller graph for this function:

Friends And Related Function Documentation

std::ostream& operator<< ( std::ostream &  o,
const SharedMemory v 
)
friend

Definition at line 245 of file shared_memory.cpp.

245  {
246  o << "<SharedMemory>";
247  o << "<meta_path>" << v.get_meta_path() << "</meta_path>";
248  o << "<size>" << v.get_size() << "</size>";
249  o << "<owned>" << v.is_owned() << "</owned>";
250  o << "<owner_pid>" << v.get_owner_pid() << "</owner_pid>";
251  o << "<numa_node>" << v.get_numa_node() << "</numa_node>";
252  o << "<shmid>" << v.get_shmid() << "</shmid>";
253  o << "<shmkey>" << v.get_shmkey() << "</shmkey>";
254  o << "<address>" << reinterpret_cast<uintptr_t>(v.get_block()) << "</address>";
255  o << "</SharedMemory>";
256  return o;
257 }

The documentation for this class was generated from the following files: