Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
dcb908801374e07e99693e74b3d9e6a1b2a25315
[simgrid.git] / src / kernel / context / ContextUnix.cpp
1 /* Copyright (c) 2009-2018. 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 /* \file UContext.cpp Context switching with ucontexts from System V        */
7
8 #include "ContextUnix.hpp"
9
10 #include "mc/mc.h"
11 #include "src/mc/mc_ignore.hpp"
12 #include "src/simix/ActorImpl.hpp"
13
14 XBT_LOG_EXTERNAL_DEFAULT_CATEGORY(simix_context);
15
16 /** Many integers are needed to store a pointer
17  *
18  * Support up to two ints. */
19 constexpr int CTX_ADDR_LEN = 2;
20
21 static_assert(sizeof(simgrid::kernel::context::UContext*) <= CTX_ADDR_LEN * sizeof(int),
22               "Ucontexts are not supported on this arch yet");
23
24 namespace simgrid {
25 namespace kernel {
26 namespace context {
27
28 // UContextFactory
29
30 UContextFactory::UContextFactory() : ContextFactory("UContextFactory"), parallel_(SIMIX_context_is_parallel())
31 {
32   UContext::setMaestro(nullptr);
33   if (parallel_) {
34 #if HAVE_THREAD_CONTEXTS
35     ParallelUContext::initialize();
36 #else
37     xbt_die("No thread support for parallel context execution");
38 #endif
39   }
40 }
41
42 UContextFactory::~UContextFactory()
43 {
44 #if HAVE_THREAD_CONTEXTS
45   if (parallel_)
46     ParallelUContext::finalize();
47 #endif
48 }
49
50 Context* UContextFactory::create_context(std::function<void()> code, void_pfn_smxprocess_t cleanup, smx_actor_t process)
51 {
52 #if HAVE_THREAD_CONTEXTS
53   if (parallel_)
54     return new_context<ParallelUContext>(std::move(code), cleanup, process);
55   else
56 #endif
57     return new_context<SerialUContext>(std::move(code), cleanup, process);
58 }
59
60 /* This function is called by maestro at the beginning of a scheduling round to get all working threads executing some
61  * stuff It is much easier to understand what happens if you see the working threads as bodies that swap their soul for
62  * the ones of the simulated processes that must run.
63  */
64 void UContextFactory::run_all()
65 {
66 #if HAVE_THREAD_CONTEXTS
67   if (parallel_)
68     ParallelUContext::run_all();
69   else
70 #endif
71     SerialUContext::run_all();
72 }
73
74 // UContext
75
76 UContext* UContext::maestro_context_ = nullptr;
77
78 UContext::UContext(std::function<void()> code, void_pfn_smxprocess_t cleanup_func, smx_actor_t process)
79     : Context(std::move(code), cleanup_func, process)
80 {
81   /* if the user provided a function for the process then use it, otherwise it is the context for maestro */
82   if (has_code()) {
83     this->stack_ = SIMIX_context_stack_new();
84     getcontext(&this->uc_);
85     this->uc_.uc_link = nullptr;
86     this->uc_.uc_stack.ss_sp   = sg_makecontext_stack_addr(this->stack_);
87     this->uc_.uc_stack.ss_size = sg_makecontext_stack_size(smx_context_usable_stack_size);
88     UContext::make_ctx(&this->uc_, UContext::smx_ctx_sysv_wrapper, this);
89   } else {
90     if (process != nullptr && maestro_context_ == nullptr)
91       maestro_context_ = this;
92   }
93
94 #if SIMGRID_HAVE_MC
95   if (MC_is_active() && has_code()) {
96     MC_register_stack_area(this->stack_, process, &(this->uc_), smx_context_usable_stack_size);
97   }
98 #endif
99 }
100
101 UContext::~UContext()
102 {
103   SIMIX_context_stack_delete(this->stack_);
104 }
105
106 // The name of this function is currently hardcoded in the code (as string).
107 // Do not change it without fixing those references as well.
108 void UContext::smx_ctx_sysv_wrapper(int i1, int i2)
109 {
110   // Rebuild the Context* pointer from the integers:
111   int ctx_addr[CTX_ADDR_LEN] = {i1, i2};
112   simgrid::kernel::context::UContext* context;
113   memcpy(&context, ctx_addr, sizeof context);
114
115   try {
116     (*context)();
117     context->Context::stop();
118   } catch (simgrid::kernel::context::Context::StopRequest const&) {
119     XBT_DEBUG("Caught a StopRequest");
120   }
121   context->suspend();
122 }
123
124 /** A better makecontext
125  *
126  * Makecontext expects integer arguments, we the context variable is decomposed into a serie of integers and each
127  * integer is passed as argument to makecontext.
128  */
129 void UContext::make_ctx(ucontext_t* ucp, void (*func)(int, int), UContext* arg)
130 {
131   int ctx_addr[CTX_ADDR_LEN]{};
132   memcpy(ctx_addr, &arg, sizeof arg);
133   makecontext(ucp, (void (*)())func, 2, ctx_addr[0], ctx_addr[1]);
134 }
135
136 void UContext::stop()
137 {
138   Context::stop();
139   throw StopRequest();
140 }
141
142 // SerialUContext
143
144 unsigned long SerialUContext::process_index_; /* index of the next process to run in the list of runnable processes */
145
146 void SerialUContext::suspend()
147 {
148   /* determine the next context */
149   SerialUContext* next_context;
150   unsigned long int i = process_index_;
151   process_index_++;
152
153   if (i < simix_global->process_to_run.size()) {
154     /* execute the next process */
155     XBT_DEBUG("Run next process");
156     next_context = static_cast<SerialUContext*>(simix_global->process_to_run[i]->context);
157   } else {
158     /* all processes were run, return to maestro */
159     XBT_DEBUG("No more process to run");
160     next_context = static_cast<SerialUContext*>(UContext::getMaestro());
161   }
162   SIMIX_context_set_current(next_context);
163   UContext::swap(this, next_context);
164 }
165
166 void SerialUContext::resume()
167 {
168   SIMIX_context_set_current(this);
169   UContext::swap(UContext::getMaestro(), this);
170 }
171
172 void SerialUContext::run_all()
173 {
174   if (simix_global->process_to_run.empty())
175     return;
176   smx_actor_t first_process = simix_global->process_to_run.front();
177   process_index_            = 1;
178   static_cast<SerialUContext*>(first_process->context)->resume();
179 }
180
181 // ParallelUContext
182
183 #if HAVE_THREAD_CONTEXTS
184
185 simgrid::xbt::Parmap<smx_actor_t>* ParallelUContext::parmap_;
186 std::atomic<uintptr_t> ParallelUContext::threads_working_;         /* number of threads that have started their work */
187 xbt_os_thread_key_t ParallelUContext::worker_id_key_;              /* thread-specific storage for the thread id */
188 std::vector<ParallelUContext*> ParallelUContext::workers_context_; /* space to save the worker's context
189                                                                     * in each thread */
190
191 void ParallelUContext::initialize()
192 {
193   parmap_ = nullptr;
194   workers_context_.clear();
195   workers_context_.resize(SIMIX_context_get_nthreads(), nullptr);
196   xbt_os_thread_key_create(&worker_id_key_);
197 }
198
199 void ParallelUContext::finalize()
200 {
201   delete parmap_;
202   parmap_ = nullptr;
203   workers_context_.clear();
204   xbt_os_thread_key_destroy(worker_id_key_);
205 }
206
207 void ParallelUContext::run_all()
208 {
209   threads_working_ = 0;
210   // Parmap_apply ensures that every working thread get an index in the process_to_run array (through an atomic
211   // fetch_and_add), and runs the ParallelUContext::resume function on that index
212
213   // We lazily create the parmap because the parmap creates context with simix_global->context_factory (which might not
214   // be initialized when bootstrapping):
215   if (parmap_ == nullptr)
216     parmap_ = new simgrid::xbt::Parmap<smx_actor_t>(SIMIX_context_get_nthreads(), SIMIX_context_get_parallel_mode());
217   parmap_->apply(
218       [](smx_actor_t process) {
219         ParallelUContext* context = static_cast<ParallelUContext*>(process->context);
220         context->resume();
221       },
222       simix_global->process_to_run);
223 }
224
225 /** Yield
226  *
227  * This function is called when a simulated process wants to yield back to the maestro in a blocking simcall. This
228  * naturally occurs within SIMIX_context_suspend(self->context), called from SIMIX_process_yield() Actually, it does not
229  * really yield back to maestro, but into the next process that must be executed. If no one is to be executed, then it
230  * yields to the initial soul that was in this working thread (that was saved in resume_parallel).
231  */
232 void ParallelUContext::suspend()
233 {
234   /* determine the next context */
235   // Get the next soul to embody now:
236   boost::optional<smx_actor_t> next_work = parmap_->next();
237   ParallelUContext* next_context;
238   if (next_work) {
239     // There is a next soul to embody (ie, a next process to resume)
240     XBT_DEBUG("Run next process");
241     next_context = static_cast<ParallelUContext*>(next_work.get()->context);
242   } else {
243     // All processes were run, go to the barrier
244     XBT_DEBUG("No more processes to run");
245     // Get back the identity of my body that was stored when starting the scheduling round
246     uintptr_t worker_id = reinterpret_cast<uintptr_t>(xbt_os_thread_get_specific(worker_id_key_));
247     // Deduce the initial soul of that body
248     next_context = workers_context_[worker_id];
249     // When given that soul, the body will wait for the next scheduling round
250   }
251
252   SIMIX_context_set_current(next_context);
253   // Get the next soul to run, either simulated or initial minion's one:
254   UContext::swap(this, next_context);
255 }
256
257 /** Run one particular simulated process on the current thread. */
258 void ParallelUContext::resume()
259 {
260   // What is my containing body?
261   uintptr_t worker_id = threads_working_.fetch_add(1, std::memory_order_relaxed);
262   // Store the number of my containing body in os-thread-specific area :
263   xbt_os_thread_set_specific(worker_id_key_, reinterpret_cast<void*>(worker_id));
264   // Get my current soul:
265   ParallelUContext* worker_context = static_cast<ParallelUContext*>(SIMIX_context_self());
266   // Write down that this soul is hosted in that body (for now)
267   workers_context_[worker_id] = worker_context;
268   // Write in simix that I switched my soul
269   SIMIX_context_set_current(this);
270   // Actually do that using the relevant library call:
271   UContext::swap(worker_context, this);
272   // No body runs that soul anymore at this point.  Instead the current body took the soul of simulated process The
273   // simulated process wakes back after the call to "SIMIX_context_suspend(self->context);" within
274   // smx_process.c::SIMIX_process_yield()
275
276   // From now on, the simulated processes will change their soul with the next soul to execute (in suspend_parallel,
277   // below).  When nobody is to be executed in this scheduling round, the last simulated process will take back the
278   // initial soul of the current working thread
279 }
280
281 #endif
282
283 XBT_PRIVATE ContextFactory* sysv_factory()
284 {
285   XBT_VERB("Activating SYSV context factory");
286   return new UContextFactory();
287 }
288 }}} // namespace simgrid::kernel::context