Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Merge branch 'master' of https://framagit.org/simgrid/simgrid
[simgrid.git] / src / smpi / internals / smpi_memory.cpp
1 /* Copyright (c) 2015-2023. The SimGrid Team. All rights reserved.          */
2
3 /* This program is free software; you can redistribute it and/or modify it
4  * under the terms of the license (GNU LGPL) which comes with this package. */
5
6 #include "private.hpp"
7 #include "src/internal_config.h"
8 #include "src/smpi/include/smpi_actor.hpp"
9 #include "src/xbt/memory_map.hpp"
10 #include "xbt/virtu.h"
11
12 #include <algorithm>
13 #include <cerrno>
14 #include <climits>
15 #include <cstdint>
16 #include <cstdio>
17 #include <cstdlib>
18 #include <cstring>
19 #include <deque>
20 #include <fcntl.h>
21 #include <sys/mman.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25 #include <vector>
26
27 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(smpi_memory, smpi, "Memory layout support for SMPI");
28
29 static char* smpi_data_exe_start = nullptr; // start of the data+bss segment of the executable
30 static size_t smpi_data_exe_size = 0;       // size of the data+bss segment of the executable
31 static SmpiPrivStrategies smpi_privatize_global_variables;
32 static void* smpi_data_exe_copy;
33
34 // Initialized by smpi_prepare_global_memory_segment().
35 static std::vector<simgrid::xbt::VmMap> initial_vm_map;
36
37 // We keep a copy of all the privatization regions: We can then delete everything easily by iterating over this
38 // collection and nothing can be leaked. We could also iterate over all actors but we would have to be diligent when two
39 // actors use the same privatization region (so, smart pointers would have to be used etc.)
40 // Use a std::deque so that pointers remain valid after push_back().
41 static std::deque<s_smpi_privatization_region_t> smpi_privatization_regions;
42
43 static constexpr int PROT_RWX = PROT_READ | PROT_WRITE | PROT_EXEC;
44 static constexpr int PROT_RW  = PROT_READ | PROT_WRITE;
45
46 /** Take a snapshot of the process' memory map.
47  */
48 void smpi_prepare_global_memory_segment()
49 {
50   initial_vm_map = simgrid::xbt::get_memory_map(getpid());
51 }
52
53 static void smpi_get_executable_global_size()
54 {
55   char* buffer = realpath(simgrid::xbt::binary_name.c_str(), nullptr);
56   xbt_assert(buffer != nullptr, "Could not resolve real path of binary file '%s'", simgrid::xbt::binary_name.c_str());
57   std::string full_name = buffer;
58   free(buffer);
59
60   std::vector<simgrid::xbt::VmMap> map = simgrid::xbt::get_memory_map(getpid());
61   for (auto i = map.begin(); i != map.end() ; ++i) {
62     // TODO, In practice, this implementation would not detect a completely
63     // anonymous data segment. This does not happen in practice, however.
64
65     // File backed RW entry:
66     if (i->pathname == full_name && (i->prot & PROT_RWX) == PROT_RW) {
67       smpi_data_exe_start = (char*)i->start_addr;
68       smpi_data_exe_size  = i->end_addr - i->start_addr;
69       /* Here we are making the assumption that a suitable empty region
70          following the rw- area is the end of the data segment. It would
71          be better to check with the size of the data segment. */
72       if (auto j = i + 1; j != map.end() && j->pathname.empty() && (j->prot & PROT_RWX) == PROT_RW &&
73                           (char*)j->start_addr == smpi_data_exe_start + smpi_data_exe_size) {
74         // Only count the portion of this region not present in the initial map.
75         auto found    = std::find_if(initial_vm_map.begin(), initial_vm_map.end(), [&j](const simgrid::xbt::VmMap& m) {
76           return j->start_addr <= m.start_addr && m.start_addr < j->end_addr;
77         });
78         auto end_addr = (found == initial_vm_map.end() ? j->end_addr : found->start_addr);
79         smpi_data_exe_size = (char*)end_addr - smpi_data_exe_start;
80       }
81       return;
82     }
83   }
84   xbt_die("Did not find my data segment.");
85 }
86
87 #if HAVE_SANITIZER_ADDRESS
88 #include <sanitizer/asan_interface.h>
89 static void* asan_safe_memcpy(void* dest, void* src, size_t n)
90 {
91   char* psrc  = static_cast<char*>(src);
92   char* pdest = static_cast<char*>(dest);
93   for (size_t i = 0; i < n;) {
94     while (i < n && __asan_address_is_poisoned(psrc + i))
95       ++i;
96     if (i < n) {
97       char* p  = static_cast<char*>(__asan_region_is_poisoned(psrc + i, n - i));
98       size_t j = p ? (p - psrc) : n;
99       memcpy(pdest + i, psrc + i, j - i);
100       i = j;
101     }
102   }
103   return dest;
104 }
105 #else
106 #define asan_safe_memcpy(dest, src, n) memcpy((dest), (src), (n))
107 #endif
108
109 /**
110  * @brief Uses shm_open to get a temporary shm, and returns its file descriptor.
111  */
112 int smpi_temp_shm_get()
113 {
114   constexpr unsigned INDEX_MASK = 0xffffffffUL;
115   static unsigned index         = INDEX_MASK;
116   char shmname[32]; // cannot be longer than PSHMNAMLEN = 31 on macOS (shm_open raises ENAMETOOLONG otherwise)
117   int fd;
118
119   unsigned limit = index;
120   do {
121     index = (index + 1) & INDEX_MASK;
122     snprintf(shmname, sizeof(shmname), "/smpi-buffer-%016x", index);
123     fd = shm_open(shmname, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
124   } while (fd == -1 && errno == EEXIST && index != limit);
125
126   if (fd < 0) {
127     if (errno == EMFILE) {
128       xbt_die("Impossible to create temporary file for memory mapping: %s\n\
129 The shm_open() system call failed with the EMFILE error code (too many files). \n\n\
130 This means that you reached the system limits concerning the amount of files per process. \
131 This is not a surprise if you are trying to virtualize many processes on top of SMPI. \
132 Don't panic -- you should simply increase your system limits and try again. \n\n\
133 First, check what your limits are:\n\
134   cat /proc/sys/fs/file-max # Gives you the system-wide limit\n\
135   ulimit -Hn                # Gives you the per process hard limit\n\
136   ulimit -Sn                # Gives you the per process soft limit\n\
137   cat /proc/self/limits     # Displays any per-process limitation (including the one given above)\n\n\
138 If one of these values is less than the amount of MPI processes that you try to run, then you got the explanation of this error. \
139 Ask the Internet about tutorials on how to increase the files limit such as: https://rtcamp.com/tutorials/linux/increase-open-files-limit/",
140               strerror(errno));
141     }
142     xbt_die("Impossible to create temporary file for memory mapping. shm_open: %s", strerror(errno));
143   }
144   XBT_DEBUG("Got temporary shm %s (fd = %d)", shmname, fd);
145   if (shm_unlink(shmname) < 0)
146     XBT_WARN("Could not early unlink %s. shm_unlink: %s", shmname, strerror(errno));
147   return fd;
148 }
149
150 /**
151  * @brief Mmap a region of size bytes from temporary shm with file descriptor fd.
152  */
153 void* smpi_temp_shm_mmap(int fd, size_t size)
154 {
155   struct stat st;
156   xbt_assert(fstat(fd, &st) == 0, "Could not stat fd %d: %s", fd, strerror(errno));
157   xbt_assert(static_cast<off_t>(size) <= st.st_size || ftruncate(fd, static_cast<off_t>(size)) == 0,
158              "Could not truncate fd %d to %zu: %s", fd, size, strerror(errno));
159   void* mem = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
160   xbt_assert(
161       mem != MAP_FAILED,
162       "Failed to map fd %d with size %zu: %s\n"
163       "If you are running a lot of ranks, you may be exceeding the amount of mappings allowed per process.\n"
164       "On Linux systems, change this value with sudo sysctl -w vm.max_map_count=newvalue (default value: 65536)\n"
165       "Please see https://simgrid.org/doc/latest/Configuring_SimGrid.html#configuring-the-user-code-virtualization for "
166       "more information.",
167       fd, size, strerror(errno));
168   return mem;
169 }
170
171 /** Map a given SMPI privatization segment (make an SMPI process active)
172  *
173  *  When doing a state restoration, the state of the restored variables  might not be consistent with the state of the
174  *  virtual memory. In this case, we have to change the data segment.
175  *
176  *  If 'addr' is not null, only switch if it's an address from the data segment.
177  *
178  *  Returns 'true' if the segment has to be switched (mmap privatization and 'addr' in data segment).
179  */
180 bool smpi_switch_data_segment(simgrid::s4u::ActorPtr actor, const void* addr)
181 {
182   if (smpi_cfg_privatization() != SmpiPrivStrategies::MMAP || smpi_data_exe_size == 0)
183     return false; // no need to switch
184
185   if (addr != nullptr &&
186       not(static_cast<const char*>(addr) >= smpi_data_exe_start &&
187           static_cast<const char*>(addr) < smpi_data_exe_start + smpi_data_exe_size))
188     return false; // no need to switch, addr is not concerned
189
190   static aid_t smpi_loaded_page = -1;
191   if (smpi_loaded_page == actor->get_pid()) // no need to switch, we've already loaded the one we want
192     return true;                            // return 'true' anyway
193
194 #if HAVE_PRIVATIZATION
195   // FIXME, cross-process support (mmap across process when necessary)
196   XBT_DEBUG("Switching data frame to the one of process %ld", actor->get_pid());
197   const simgrid::smpi::ActorExt* process = smpi_process_remote(actor);
198   int current                     = process->privatized_region()->file_descriptor;
199   xbt_assert(mmap(TOPAGE(smpi_data_exe_start), smpi_data_exe_size, PROT_RW, MAP_FIXED | MAP_SHARED, current, 0) ==
200                  TOPAGE(smpi_data_exe_start),
201              "Couldn't map the new region (errno %d): %s", errno, strerror(errno));
202   smpi_loaded_page = actor->get_pid();
203 #endif
204
205   return true;
206 }
207
208 /**
209  * @brief Makes a backup of the segment in memory that stores the global variables of a process.
210  *        This backup is then used to initialize the global variables for every single
211  *        process that is added, regardless of the progress of the simulation.
212  */
213 void smpi_backup_global_memory_segment()
214 {
215 #if HAVE_PRIVATIZATION
216   smpi_get_executable_global_size();
217   initial_vm_map.clear();
218   initial_vm_map.shrink_to_fit();
219
220   XBT_DEBUG("bss+data segment found : size %zu starting at %p", smpi_data_exe_size, smpi_data_exe_start);
221
222   if (smpi_data_exe_size == 0) { // no need to do anything as global variables don't exist
223     smpi_privatize_global_variables = SmpiPrivStrategies::NONE;
224     return;
225   }
226
227   smpi_data_exe_copy = ::operator new(smpi_data_exe_size);
228   // Make a copy of the data segment. This clean copy is retained over the whole runtime
229   // of the simulation and can be used to initialize a dynamically added, new process.
230   asan_safe_memcpy(smpi_data_exe_copy, TOPAGE(smpi_data_exe_start), smpi_data_exe_size);
231 #else /* ! HAVE_PRIVATIZATION */
232   xbt_die("You are trying to use privatization on a system that does not support it. Don't.");
233 #endif
234 }
235
236 // Initializes the memory mapping for a single process and returns the privatization region
237 smpi_privatization_region_t smpi_init_global_memory_segment_process()
238 {
239   int file_descriptor = smpi_temp_shm_get();
240
241   // ask for a free region
242   void* address = smpi_temp_shm_mmap(file_descriptor, smpi_data_exe_size);
243
244   // initialize the values
245   asan_safe_memcpy(address, smpi_data_exe_copy, smpi_data_exe_size);
246
247   // store the address of the mapping for further switches
248   smpi_privatization_regions.emplace_back(s_smpi_privatization_region_t{address, file_descriptor});
249
250   return &smpi_privatization_regions.back();
251 }
252
253 void smpi_destroy_global_memory_segments(){
254   if (smpi_data_exe_size == 0) // no need to switch
255     return;
256 #if HAVE_PRIVATIZATION
257   for (auto const& region : smpi_privatization_regions) {
258     if (munmap(region.address, smpi_data_exe_size) < 0)
259       XBT_WARN("Unmapping of fd %d failed: %s", region.file_descriptor, strerror(errno));
260     close(region.file_descriptor);
261   }
262   smpi_privatization_regions.clear();
263   ::operator delete(smpi_data_exe_copy);
264 #endif
265 }
266
267 static std::vector<unsigned char> sendbuffer;
268 static std::vector<unsigned char> recvbuffer;
269
270 //allocate a single buffer for all sends, growing it if needed
271 unsigned char* smpi_get_tmp_sendbuffer(size_t size)
272 {
273   if (not smpi_process()->replaying())
274     return new unsigned char[size];
275   // FIXME: a resize() may invalidate a previous pointer. Maybe we need to handle a queue of buffers with a reference
276   // counter. The same holds for smpi_get_tmp_recvbuffer.
277   if (sendbuffer.size() < size)
278     sendbuffer.resize(size);
279   return sendbuffer.data();
280 }
281
282 //allocate a single buffer for all recv
283 unsigned char* smpi_get_tmp_recvbuffer(size_t size)
284 {
285   if (not smpi_process()->replaying())
286     return new unsigned char[size];
287   if (recvbuffer.size() < size)
288     recvbuffer.resize(size);
289   return recvbuffer.data();
290 }
291
292 void smpi_free_tmp_buffer(const unsigned char* buf)
293 {
294   if (not smpi_process()->replaying())
295     delete[] buf;
296 }
297
298 void smpi_free_replay_tmp_buffers()
299 {
300   std::vector<unsigned char>().swap(sendbuffer);
301   std::vector<unsigned char>().swap(recvbuffer);
302 }