| /* |
| * Copyright 2018 Google, Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer; |
| * redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution; |
| * neither the name of the copyright holders nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "base/fiber.hh" |
| |
| #if HAVE_VALGRIND |
| #include <valgrind/valgrind.h> |
| #endif |
| |
| // Mac OS requires _DARWIN_C_SOURCE if _POSIX_C_SOURCE is defined, |
| // otherwise it will mask the definition of MAP_ANONYMOUS. |
| // _POSIX_C_SOURCE is already defined by including <ucontext.h> in |
| // base/fiber.hh |
| #if defined(__APPLE__) && defined(__MACH__) |
| #define _DARWIN_C_SOURCE |
| #endif |
| |
| #include <sys/mman.h> |
| #include <unistd.h> |
| |
| #include <cerrno> |
| #include <cstdio> |
| #include <cstring> |
| |
| #include "base/logging.hh" |
| |
| using namespace std; |
| |
| namespace |
| { |
| |
| /* |
| * The PrimaryFiber class is a special case that attaches to the currently |
| * executing context. That makes handling the "primary" fiber, aka the one |
| * which most of gem5 is running under, no different than other Fibers. |
| */ |
| class PrimaryFiber : public Fiber |
| { |
| public: |
| PrimaryFiber() : Fiber(nullptr, 0) { setStarted(); } |
| void main() { panic("PrimaryFiber main executed.\n"); } |
| }; |
| |
| PrimaryFiber _primaryFiber; |
| |
| // A pointer to whatever the currently executing Fiber is. |
| Fiber *_currentFiber = &_primaryFiber; |
| |
| // A pointer to the Fiber which is currently being started/initialized. |
| Fiber *startingFiber = nullptr; |
| |
| } // anonymous namespace |
| |
| void |
| Fiber::entryTrampoline() |
| { |
| startingFiber->start(); |
| } |
| |
| Fiber::Fiber(size_t stack_size) : Fiber(primaryFiber(), stack_size) |
| {} |
| |
| Fiber::Fiber(Fiber *link, size_t stack_size) : |
| link(link), stack(nullptr), stackSize(stack_size), guardPage(nullptr), |
| guardPageSize(sysconf(_SC_PAGE_SIZE)), _started(false), _finished(false) |
| { |
| if (stack_size) { |
| guardPage = mmap(nullptr, guardPageSize + stack_size, |
| PROT_READ | PROT_WRITE, |
| MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); |
| if (guardPage == (void *)MAP_FAILED) { |
| perror("mmap"); |
| fatal("Could not mmap %d byte fiber stack.\n", stack_size); |
| } |
| stack = (void *)((uint8_t *)guardPage + guardPageSize); |
| if (mprotect(guardPage, guardPageSize, PROT_NONE)) { |
| perror("mprotect"); |
| fatal("Could not forbid access to fiber stack guard page."); |
| } |
| } |
| #if HAVE_VALGRIND |
| valgrindStackId = VALGRIND_STACK_REGISTER( |
| stack, (uint8_t *)stack + stack_size); |
| #endif |
| } |
| |
| Fiber::~Fiber() |
| { |
| panic_if(stack && _currentFiber == this, "Fiber stack is in use."); |
| #if HAVE_VALGRIND |
| VALGRIND_STACK_DEREGISTER(valgrindStackId); |
| #endif |
| if (guardPage) |
| munmap(guardPage, guardPageSize + stackSize); |
| } |
| |
| void |
| Fiber::createContext() |
| { |
| // Set up a context for the new fiber, starting it in the trampoline. |
| getcontext(&ctx); |
| ctx.uc_stack.ss_sp = stack; |
| ctx.uc_stack.ss_size = stackSize; |
| ctx.uc_link = nullptr; |
| makecontext(&ctx, &entryTrampoline, 0); |
| |
| // Swap to the new context so it can enter its start() function. It |
| // will then swap itself back out and return here. |
| startingFiber = this; |
| panic_if(!_currentFiber, "No active Fiber object."); |
| swapcontext(&_currentFiber->ctx, &ctx); |
| |
| // The new context is now ready and about to call main(). |
| } |
| |
| void |
| Fiber::start() |
| { |
| // Avoid a dangling pointer. |
| startingFiber = nullptr; |
| |
| setStarted(); |
| |
| // Swap back to the parent context which is still considered "current", |
| // now that we're ready to go. |
| int ret M5_VAR_USED = swapcontext(&ctx, &_currentFiber->ctx); |
| panic_if(ret == -1, strerror(errno)); |
| |
| // Call main() when we're been reactivated for the first time. |
| main(); |
| |
| // main has returned, so this Fiber has finished. Switch to the "link" |
| // Fiber. |
| _finished = true; |
| link->run(); |
| } |
| |
| void |
| Fiber::run() |
| { |
| panic_if(_finished, "Fiber has already run to completion."); |
| |
| // If we're already running this fiber, we're done. |
| if (_currentFiber == this) |
| return; |
| |
| if (!_started) |
| createContext(); |
| |
| // Switch out of the current Fiber's context and this one's in. |
| Fiber *prev = _currentFiber; |
| Fiber *next = this; |
| _currentFiber = next; |
| swapcontext(&prev->ctx, &next->ctx); |
| } |
| |
| Fiber *Fiber::currentFiber() { return _currentFiber; } |
| Fiber *Fiber::primaryFiber() { return &_primaryFiber; } |