Logo AND Algorithmique Numérique Distribuée

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