Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Merge branch 'master' of scm.gforge.inria.fr:/gitroot/simgrid/simgrid
[simgrid.git] / src / smpi / internals / smpi_memory.cpp
1 /* Copyright (c) 2015-2017. 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 <cerrno>
7 #include <climits>
8 #include <cstdint>
9 #include <cstdio>
10 #include <cstdlib>
11 #include <cstring>
12 #include <deque>
13 #include <fcntl.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
16 #include <vector>
17
18 #ifndef WIN32
19 #include <sys/mman.h>
20 #include <unistd.h>
21
22 #include "src/internal_config.h"
23 #include "src/xbt/memory_map.hpp"
24
25 #include "private.hpp"
26 #include "smpi_process.hpp"
27
28 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(smpi_memory, smpi, "Memory layout support for SMPI");
29
30 int smpi_loaded_page      = -1;
31 char* smpi_data_exe_start = nullptr;
32 int smpi_data_exe_size    = 0;
33 int smpi_privatize_global_variables;
34 static void* smpi_data_exe_copy;
35
36 // We keep a copy of all the privatization regions: We can then delete everything easily by iterating over this
37 // collection and nothing can be leaked. We could also iterate over all actors but we would have to be diligent when two
38 // actors use the same privatization region (so, smart pointers would have to be used etc.)
39 // Use a std::deque so that pointers remain valid after push_back().
40 static std::deque<s_smpi_privatization_region_t> smpi_privatization_regions;
41
42 static const int PROT_RWX = (PROT_READ | PROT_WRITE | PROT_EXEC);
43 static const int PROT_RW  = (PROT_READ | PROT_WRITE );
44 XBT_ATTRIB_UNUSED static const int PROT_RX  = (PROT_READ | PROT_EXEC );
45
46 void smpi_get_executable_global_size()
47 {
48   char buffer[PATH_MAX];
49   char* full_name = realpath(xbt_binary_name, buffer);
50   if (full_name == nullptr)
51     xbt_die("Could not resolve binary file name");
52
53   std::vector<simgrid::xbt::VmMap> map = simgrid::xbt::get_memory_map(getpid());
54   for (auto i = map.begin(); i != map.end() ; ++i) {
55     // TODO, In practice, this implementation would not detect a completely
56     // anonymous data segment. This does not happen in practice, however.
57
58     // File backed RW entry:
59     if (i->pathname == full_name && (i->prot & PROT_RWX) == PROT_RW) {
60       smpi_data_exe_start = (char*)i->start_addr;
61       smpi_data_exe_size  = i->end_addr - i->start_addr;
62       ++i;
63       /* Here we are making the assumption that a suitable empty region
64          following the rw- area is the end of the data segment. It would
65          be better to check with the size of the data segment. */
66       if (i != map.end() && i->pathname.empty() && (i->prot & PROT_RWX) == PROT_RW &&
67           (char*)i->start_addr == smpi_data_exe_start + smpi_data_exe_size) {
68         smpi_data_exe_size = (char*)i->end_addr - smpi_data_exe_start;
69       }
70       return;
71     }
72   }
73   xbt_die("Did not find my data segment.");
74 }
75 #endif
76
77 #if HAVE_SANITIZE_ADDRESS
78 #include <sanitizer/asan_interface.h>
79 static void* asan_safe_memcpy(void* dest, void* src, size_t n)
80 {
81   char* psrc  = static_cast<char*>(src);
82   char* pdest = static_cast<char*>(dest);
83   for (size_t i = 0; i < n;) {
84     while (i < n && __asan_address_is_poisoned(psrc + i))
85       ++i;
86     if (i < n) {
87       char* p  = static_cast<char*>(__asan_region_is_poisoned(psrc + i, n - i));
88       size_t j = p ? (p - psrc) : n;
89       memcpy(pdest + i, psrc + i, j - i);
90       i = j;
91     }
92   }
93   return dest;
94 }
95 #else
96 #define asan_safe_memcpy(dest, src, n) memcpy(dest, src, n)
97 #endif
98
99 /** Map a given SMPI privatization segment (make a SMPI process active) */
100 void smpi_switch_data_segment(int dest) {
101   if (smpi_loaded_page == dest)//no need to switch, we've already loaded the one we want
102     return;
103
104   // So the job:
105   smpi_really_switch_data_segment(dest);
106 }
107
108 /** Map a given SMPI privatization segment (make a SMPI process active)  even if SMPI thinks it is already active
109  *
110  *  When doing a state restoration, the state of the restored variables  might not be consistent with the state of the
111  *  virtual memory. In this case, we to change the data segment.
112  */
113 void smpi_really_switch_data_segment(int dest)
114 {
115   if (smpi_data_exe_size == 0) // no need to switch
116     return;
117
118 #if HAVE_PRIVATIZATION
119   // FIXME, cross-process support (mmap across process when necessary)
120   simgrid::smpi::Process* process = smpi_process_remote(simgrid::s4u::Actor::byPid(dest+1));
121   int current                     = process->privatized_region()->file_descriptor;
122   XBT_DEBUG("Switching data frame to the one of process %d", dest);
123   void* tmp =
124       mmap(TOPAGE(smpi_data_exe_start), smpi_data_exe_size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, current, 0);
125   if (tmp != TOPAGE(smpi_data_exe_start))
126     xbt_die("Couldn't map the new region (errno %d): %s", errno, strerror(errno));
127   smpi_loaded_page = dest;
128 #endif
129 }
130
131 int smpi_is_privatization_file(char* file)
132 {
133   const std::string buffer_path("/dev/shm/my-buffer-");
134   return buffer_path.compare(0, std::string::npos, file, buffer_path.length()) == 0;
135 }
136
137 /**
138  * @brief Makes a backup of the segment in memory that stores the global variables of a process.
139  *        This backup is then used to initialize the global variables for every single
140  *        process that is added, regardless of the progress of the simulation.
141  */
142 void smpi_backup_global_memory_segment()
143 {
144 #if HAVE_PRIVATIZATION
145   smpi_get_executable_global_size();
146
147   XBT_DEBUG("bss+data segment found : size %d starting at %p", smpi_data_exe_size, smpi_data_exe_start);
148
149   if (smpi_data_exe_size == 0) { // no need to do anything as global variables don't exist
150     smpi_privatize_global_variables=false;
151     return;
152   }
153
154   smpi_data_exe_copy = ::operator new(smpi_data_exe_size);
155   // Make a copy of the data segment. This clean copy is retained over the whole runtime
156   // of the simulation and can be used to initialize a dynamically added, new process.
157   asan_safe_memcpy(smpi_data_exe_copy, TOPAGE(smpi_data_exe_start), smpi_data_exe_size);
158 #else /* ! HAVE_PRIVATIZATION */
159   smpi_privatize_global_variables = false;
160   xbt_die("You are trying to use privatization on a system that does not support it. Don't.");
161   return;
162 #endif
163 }
164
165 // Initializes the memory mapping for a single process and returns the privatization region
166 smpi_privatization_region_t smpi_init_global_memory_segment_process()
167 {
168   int file_descriptor;
169   void* address = nullptr;
170   char path[24];
171   int status;
172
173   do {
174     snprintf(path, sizeof(path), "/smpi-buffer-%06x", rand() % 0xffffffU);
175     file_descriptor = shm_open(path, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
176   } while (file_descriptor == -1 && errno == EEXIST);
177   if (file_descriptor < 0) {
178     if (errno == EMFILE) {
179       xbt_die("Impossible to create temporary file for memory mapping: %s\n\
180 The open() system call failed with the EMFILE error code (too many files). \n\n\
181 This means that you reached the system limits concerning the amount of files per process. \
182 This is not a surprise if you are trying to virtualize many processes on top of SMPI. \
183 Don't panic -- you should simply increase your system limits and try again. \n\n\
184 First, check what your limits are:\n\
185   cat /proc/sys/fs/file-max # Gives you the system-wide limit\n\
186   ulimit -Hn                # Gives you the per process hard limit\n\
187   ulimit -Sn                # Gives you the per process soft limit\n\
188   cat /proc/self/limits     # Displays any per-process limitation (including the one given above)\n\n\
189 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. \
190 Ask the Internet about tutorials on how to increase the files limit such as: https://rtcamp.com/tutorials/linux/increase-open-files-limit/",
191               strerror(errno));
192     }
193     xbt_die("Impossible to create temporary file for memory mapping: %s", strerror(errno));
194   }
195
196   status = ftruncate(file_descriptor, smpi_data_exe_size);
197   if (status)
198     xbt_die("Impossible to set the size of the temporary file for memory mapping");
199
200   /* Ask for a free region */
201   address = mmap(nullptr, smpi_data_exe_size, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, 0);
202   if (address == MAP_FAILED)
203     xbt_die("Couldn't find a free region for memory mapping");
204
205   status = shm_unlink(path);
206   if (status)
207     xbt_die("Impossible to unlink temporary file for memory mapping");
208
209   // initialize the values
210   asan_safe_memcpy(address, smpi_data_exe_copy, smpi_data_exe_size);
211
212   // store the address of the mapping for further switches
213   smpi_privatization_regions.emplace_back(s_smpi_privatization_region_t{address, file_descriptor});
214
215   return &smpi_privatization_regions.back();
216 }
217
218 void smpi_destroy_global_memory_segments(){
219   if (smpi_data_exe_size == 0) // no need to switch
220     return;
221 #if HAVE_PRIVATIZATION
222   for (auto const& region : smpi_privatization_regions) {
223     if (munmap(region.address, smpi_data_exe_size) < 0)
224       XBT_WARN("Unmapping of fd %d failed: %s", region.file_descriptor, strerror(errno));
225     close(region.file_descriptor);
226   }
227   smpi_privatization_regions.clear();
228   ::operator delete(smpi_data_exe_copy);
229 #endif
230 }
231