Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
79caa3af307efecd0977a3dddc35ef87972d975a
[simgrid.git] / src / mc / mc_page_snapshot.cpp
1 #include <unistd.h> // pread, pwrite
2
3 #include "mc_page_store.h"
4 #include "mc_mmu.h"
5 #include "mc_private.h"
6 #include "mc_snapshot.h"
7
8 #include <xbt/mmalloc.h>
9
10 #define SOFT_DIRTY_BIT_NUMBER 55
11 #define SOFT_DIRTY (((uint64_t)1) << SOFT_DIRTY_BIT_NUMBER)
12
13 extern "C" {
14
15 // ***** Region management:
16
17 /** @brief Take a per-page snapshot of a region
18  *
19  *  @param data            The start of the region (must be at the beginning of a page)
20  *  @param pag_count       Number of pages of the region
21  *  @param pagemap         Linux kernel pagemap values fot this region (or NULL)
22  *  @param reference_pages Snapshot page numbers of the previous soft_dirty_reset (or NULL)
23  *  @return                Snapshot page numbers of this new snapshot
24  */
25 size_t* mc_take_page_snapshot_region(mc_process_t process,
26   void* data, size_t page_count, uint64_t* pagemap, size_t* reference_pages)
27 {
28   size_t* pagenos = (size_t*) malloc(page_count * sizeof(size_t));
29
30   const bool is_self = MC_process_is_self(process);
31
32   void* temp = NULL;
33   if (!is_self)
34     temp = malloc(xbt_pagebits);
35
36   for (size_t i=0; i!=page_count; ++i) {
37     bool softclean = pagemap && !(pagemap[i] & SOFT_DIRTY);
38     if (softclean && reference_pages) {
39       // The page is softclean, it is the same page as the reference page:
40       pagenos[i] = reference_pages[i];
41       mc_model_checker->pages->ref_page(reference_pages[i]);
42     } else {
43       // Otherwise, we need to store the page the hard way
44       // (by reading its content):
45       void* page = (char*) data + (i << xbt_pagebits);
46       xbt_assert(mc_page_offset(page)==0, "Not at the beginning of a page");
47       void* page_data;
48       if (is_self) {
49         page_data = page;
50       } else {
51         /* Adding another copy (and a syscall) will probably slow things a lot.
52            TODO, optimize this somehow (at least by grouping the syscalls)
53            if needed. Either:
54             - reduce the number of syscalls;
55             - let the application snapshot itself;
56             - move the segments in shared memory (this will break `fork` however).
57         */
58         page_data = temp;
59         MC_process_read(process, temp, page, xbt_pagesize);
60       }
61       pagenos[i] = mc_model_checker->pages->store_page(page_data);
62     }
63   }
64
65   free(temp);
66   return pagenos;
67 }
68
69 void mc_free_page_snapshot_region(size_t* pagenos, size_t page_count)
70 {
71   for (size_t i=0; i!=page_count; ++i) {
72     mc_model_checker->pages->unref_page(pagenos[i]);
73   }
74 }
75
76 /** @brief Restore a snapshot of a region
77  *
78  *  If possible, the restoration will be incremental
79  *  (the modified pages will not be touched).
80  *
81  *  @param start_addr
82  *  @param page_count       Number of pages of the region
83  *  @param pagenos
84  *  @param pagemap         Linux kernel pagemap values fot this region (or NULL)
85  *  @param reference_pages Snapshot page numbers of the previous soft_dirty_reset (or NULL)
86  */
87 void mc_restore_page_snapshot_region(mc_process_t process,
88   void* start_addr, size_t page_count, size_t* pagenos, uint64_t* pagemap, size_t* reference_pagenos)
89 {
90   for (size_t i=0; i!=page_count; ++i) {
91
92     bool softclean = pagemap && !(pagemap[i] & SOFT_DIRTY);
93     if (softclean && reference_pagenos && pagenos[i] == reference_pagenos[i]) {
94       // The page is softclean and is the same as the reference one:
95       // the page is already in the target state.
96       continue;
97     }
98
99     // Otherwise, copy the page:
100     void* target_page = mc_page_from_number(start_addr, i);
101     const void* source_page = mc_model_checker->pages->get_page(pagenos[i]);
102     MC_process_write(process, source_page, target_page, xbt_pagesize);
103   }
104 }
105
106 // ***** Soft dirty tracking
107
108 /** @brief Like pread() but without partial reads */
109 static size_t pread_whole(int fd, void* buf, size_t count, off_t offset) {
110   size_t res = 0;
111
112   char* data = (char*) buf;
113   while(count) {
114     ssize_t n = pread(fd, buf, count, offset);
115     // EOF
116     if (n==0)
117       return res;
118
119     // Error (or EINTR):
120     if (n==-1) {
121       if (errno == EINTR)
122         continue;
123       else
124         return -1;
125     }
126
127     // It might be a partial read:
128     count -= n;
129     data += n;
130     offset += n;
131     res += n;
132   }
133
134   return res;
135 }
136
137 static inline  __attribute__ ((always_inline))
138 void mc_ensure_fd(int* fd, const char* path, int flags) {
139   if (*fd != -1)
140     return;
141   *fd = open(path, flags);
142   if (*fd == -1) {
143     xbt_die("Could not open file %s", path);
144   }
145 }
146
147 /** @brief Reset the soft-dirty bits
148  *
149  *  This is done after checkpointing and after checkpoint restoration
150  *  (if per page checkpoiting is used) in order to know which pages were
151  *  modified.
152  *
153  *  See https://www.kernel.org/doc/Documentation/vm/soft-dirty.txt
154  * */
155 void mc_softdirty_reset() {
156   mc_ensure_fd(&mc_model_checker->fd_clear_refs, "/proc/self/clear_refs", O_WRONLY|O_CLOEXEC);
157   if( ::write(mc_model_checker->fd_clear_refs, "4\n", 2) != 2) {
158     xbt_die("Could not reset softdirty bits");
159   }
160 }
161
162 /** @brief Read memory page informations
163  *
164  *  For each virtual memory page of the process,
165  *  /proc/self/pagemap provides a 64 bit field of information.
166  *  We are interested in the soft-dirty bit: with this we can track which
167  *  pages were modified between snapshots/restorations and avoid
168  *  copying data which was not modified.
169  *
170  *  See https://www.kernel.org/doc/Documentation/vm/pagemap.txt
171  *
172  *  @param pagemap    Output buffer for pagemap informations
173  *  @param start_addr Address of the first page
174  *  @param page_count Number of pages
175  */
176 static void mc_read_pagemap(uint64_t* pagemap, size_t page_start, size_t page_count)
177 {
178   mc_ensure_fd(&mc_model_checker->fd_pagemap, "/proc/self/pagemap", O_RDONLY|O_CLOEXEC);
179   size_t bytesize = sizeof(uint64_t) * page_count;
180   off_t offset = sizeof(uint64_t) * page_start;
181   if (pread_whole(mc_model_checker->fd_pagemap, pagemap, bytesize, offset) != bytesize) {
182     xbt_die("Could not read pagemap");
183   }
184 }
185
186 // ***** High level API
187
188 mc_mem_region_t mc_region_new_sparse(mc_region_type_t region_type,
189   void *start_addr, void* permanent_addr, size_t size,
190   mc_mem_region_t ref_reg)
191 {
192   mc_process_t process = &mc_model_checker->process;
193
194   mc_mem_region_t region = xbt_new(s_mc_mem_region_t, 1);
195   region->region_type = region_type;
196   region->storage_type = MC_REGION_STORAGE_TYPE_CHUNKED;
197   region->start_addr = start_addr;
198   region->permanent_addr = permanent_addr;
199   region->size = size;
200
201   xbt_assert((((uintptr_t)start_addr) & (xbt_pagesize-1)) == 0,
202     "Not at the beginning of a page");
203   xbt_assert((((uintptr_t)permanent_addr) & (xbt_pagesize-1)) == 0,
204     "Not at the beginning of a page");
205   size_t page_count = mc_page_count(size);
206
207   uint64_t* pagemap = NULL;
208   if (_sg_mc_soft_dirty && mc_model_checker->parent_snapshot &&
209       MC_process_is_self(process)) {
210       pagemap = (uint64_t*) mmalloc_no_memset(mc_heap, sizeof(uint64_t) * page_count);
211       mc_read_pagemap(pagemap, mc_page_number(NULL, permanent_addr), page_count);
212   }
213
214   size_t* reg_page_numbers = NULL;
215   if (ref_reg!=NULL && ref_reg->storage_type == MC_REGION_STORAGE_TYPE_CHUNKED)
216     reg_page_numbers = ref_reg->chunked.page_numbers;
217
218   // Take incremental snapshot:
219   region->chunked.page_numbers = mc_take_page_snapshot_region(process,
220     permanent_addr, page_count, pagemap, reg_page_numbers);
221
222   if(pagemap) {
223     mfree(mc_heap, pagemap);
224   }
225   return region;
226 }
227
228 void mc_region_restore_sparse(mc_process_t process, mc_mem_region_t reg, mc_mem_region_t ref_reg)
229 {
230   xbt_assert((((uintptr_t)reg->permanent_addr) & (xbt_pagesize-1)) == 0,
231     "Not at the beginning of a page");
232   size_t page_count = mc_page_count(reg->size);
233
234   uint64_t* pagemap = NULL;
235
236   // Read soft-dirty bits if necessary in order to know which pages have changed:
237   if (_sg_mc_soft_dirty && mc_model_checker->parent_snapshot
238       && MC_process_is_self(process)) {
239     pagemap = (uint64_t*) mmalloc_no_memset(mc_heap, sizeof(uint64_t) * page_count);
240     mc_read_pagemap(pagemap, mc_page_number(NULL, reg->permanent_addr), page_count);
241   }
242
243   // Incremental per-page snapshot restoration:s
244   size_t* reg_page_numbers = NULL;
245   if (ref_reg && ref_reg->storage_type == MC_REGION_STORAGE_TYPE_CHUNKED)
246     reg_page_numbers = ref_reg->chunked.page_numbers;
247
248   mc_restore_page_snapshot_region(process,
249     reg->permanent_addr, page_count, reg->chunked.page_numbers,
250     pagemap, reg_page_numbers);
251
252   if(pagemap) {
253     free(pagemap);
254   }
255 }
256
257 }