Add support for epoll
[c11tester.git] / snapshot.cc
1 #include <inttypes.h>
2 #include <sys/mman.h>
3 #include <unistd.h>
4 #include <signal.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <errno.h>
8 #include <sys/wait.h>
9
10 #include "hashtable.h"
11 #include "snapshot.h"
12 #include "mymemory.h"
13 #include "common.h"
14 #include "context.h"
15 #include "model.h"
16
17
18 #define SHARED_MEMORY_DEFAULT  (200 * ((size_t)1 << 20))        // 100mb for the shared memory
19 #define STACK_SIZE_DEFAULT      (((size_t)1 << 20) * 20)        // 20 mb out of the above 100 mb for my stack
20
21 struct fork_snapshotter {
22         /** @brief Pointer to the shared (non-snapshot) memory heap base
23          * (NOTE: this has size SHARED_MEMORY_DEFAULT - sizeof(*fork_snap)) */
24         void *mSharedMemoryBase;
25
26         /** @brief Pointer to the shared (non-snapshot) stack region */
27         void *mStackBase;
28
29         /** @brief Size of the shared stack */
30         size_t mStackSize;
31
32         /**
33          * @brief Stores the ID that we are attempting to roll back to
34          *
35          * Used in inter-process communication so that each process can
36          * determine whether or not to take over execution (w/ matching ID) or
37          * exit (we're rolling back even further). Dubiously marked 'volatile'
38          * to prevent compiler optimizations from messing with the
39          * inter-process behavior.
40          */
41         volatile snapshot_id mIDToRollback;
42
43
44
45         /** @brief Inter-process tracking of the next snapshot ID */
46         snapshot_id currSnapShotID;
47 };
48
49 static struct fork_snapshotter *fork_snap = NULL;
50 ucontext_t shared_ctxt;
51
52 /** @statics
53  *   These variables are necessary because the stack is shared region and
54  *   there exists a race between all processes executing the same function.
55  *   To avoid the problem above, we require variables allocated in 'safe' regions.
56  *   The bug was actually observed with the forkID, these variables below are
57  *   used to indicate the various contexts to which to switch to.
58  *
59  *   @private_ctxt: the context which is internal to the current process. Used
60  *   for running the internal snapshot/rollback loop.
61  *   @exit_ctxt: a special context used just for exiting from a process (so we
62  *   can use swapcontext() instead of setcontext() + hacks)
63  *   @snapshotid: it is a running counter for the various forked processes
64  *   snapshotid. it is incremented and set in a persistently shared record
65  */
66 static ucontext_t private_ctxt;
67 static snapshot_id snapshotid = 0;
68
69 /**
70  * @brief Create a new context, with a given stack and entry function
71  * @param ctxt The context structure to fill
72  * @param stack The stack to run the new context in
73  * @param stacksize The size of the stack
74  * @param func The entry point function for the context
75  */
76 static void create_context(ucontext_t *ctxt, void *stack, size_t stacksize,
77                                                                                                          void (*func)(void))
78 {
79         getcontext(ctxt);
80         ctxt->uc_stack.ss_sp = stack;
81         ctxt->uc_stack.ss_size = stacksize;
82         ctxt->uc_link = NULL;
83         makecontext(ctxt, func, 0);
84 }
85
86 /** @brief An empty function, used for an "empty" context which just exits a
87  *  process */
88 static void fork_exit()
89 {
90         _Exit(EXIT_SUCCESS);
91 }
92
93 static void createSharedMemory()
94 {
95         //step 1. create shared memory.
96         void *memMapBase = mmap(0, SHARED_MEMORY_DEFAULT + STACK_SIZE_DEFAULT, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
97         if (memMapBase == MAP_FAILED) {
98                 perror("mmap");
99                 exit(EXIT_FAILURE);
100         }
101
102         //Setup snapshot record at top of free region
103         fork_snap = (struct fork_snapshotter *)memMapBase;
104         fork_snap->mSharedMemoryBase = (void *)((uintptr_t)memMapBase + sizeof(*fork_snap));
105         fork_snap->mStackBase = (void *)((uintptr_t)memMapBase + SHARED_MEMORY_DEFAULT);
106         fork_snap->mStackSize = STACK_SIZE_DEFAULT;
107         fork_snap->mIDToRollback = -1;
108         fork_snap->currSnapShotID = 0;
109         sStaticSpace = create_shared_mspace();
110 }
111
112 /**
113  * Create a new mspace pointer for the non-snapshotting (i.e., inter-process
114  * shared) memory region. Only for fork-based snapshotting.
115  *
116  * @return The shared memory mspace
117  */
118 mspace create_shared_mspace()
119 {
120         if (!fork_snap)
121                 createSharedMemory();
122         return create_mspace_with_base((void *)(fork_snap->mSharedMemoryBase), SHARED_MEMORY_DEFAULT - sizeof(*fork_snap), 1);
123 }
124
125 static void fork_snapshot_init(unsigned int numheappages)
126 {
127         if (!fork_snap)
128                 createSharedMemory();
129
130         model_snapshot_space = create_mspace(numheappages * PAGESIZE, 1);
131 }
132
133 volatile int modellock = 0;
134
135 static void fork_loop() {
136         /* switch back here when takesnapshot is called */
137         snapshotid = fork_snap->currSnapShotID;
138         if (model->params.nofork) {
139                 setcontext(&shared_ctxt);
140                 _Exit(EXIT_SUCCESS);
141         }
142
143         while (true) {
144                 pid_t forkedID;
145                 fork_snap->currSnapShotID = snapshotid + 1;
146
147                 modellock = 1;
148                 forkedID = fork();
149                 modellock = 0;
150
151                 if (0 == forkedID) {
152                         setcontext(&shared_ctxt);
153                 } else {
154                         DEBUG("parent PID: %d, child PID: %d, snapshot ID: %d\n",
155                                                 getpid(), forkedID, snapshotid);
156
157                         while (waitpid(forkedID, NULL, 0) < 0) {
158                                 /* waitpid() may be interrupted */
159                                 if (errno != EINTR) {
160                                         perror("waitpid");
161                                         exit(EXIT_FAILURE);
162                                 }
163                         }
164
165                         if (fork_snap->mIDToRollback != snapshotid)
166                                 _Exit(EXIT_SUCCESS);
167                 }
168         }
169 }
170
171 static void fork_startExecution() {
172         /* switch to a new entryPoint context, on a new stack */
173         create_context(&private_ctxt, snapshot_calloc(STACK_SIZE_DEFAULT, 1), STACK_SIZE_DEFAULT, fork_loop);
174 }
175
176 static snapshot_id fork_take_snapshot() {
177         model_swapcontext(&shared_ctxt, &private_ctxt);
178         DEBUG("TAKESNAPSHOT RETURN\n");
179         fork_snap->mIDToRollback = -1;
180         return snapshotid;
181 }
182
183 static void fork_roll_back(snapshot_id theID)
184 {
185         DEBUG("Rollback\n");
186         fork_snap->mIDToRollback = theID;
187         fork_exit();
188 }
189
190 /**
191  * @brief Initializes the snapshot system
192  * @param entryPoint the function that should run the program.
193  */
194 void snapshot_system_init(unsigned int numheappages)
195 {
196         fork_snapshot_init(numheappages);
197 }
198
199 void startExecution() {
200         fork_startExecution();
201 }
202
203 /** Takes a snapshot of memory.
204  * @return The snapshot identifier.
205  */
206 snapshot_id take_snapshot()
207 {
208         return fork_take_snapshot();
209 }
210
211 /** Rolls the memory state back to the given snapshot identifier.
212  *  @param theID is the snapshot identifier to rollback to.
213  */
214 void snapshot_roll_back(snapshot_id theID)
215 {
216         fork_roll_back(theID);
217 }