C++程序  |  215行  |  7.06 KB

#pragma once

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Object that represents a region on the Host

#include <stdlib.h>
#include <sys/mman.h>
#include <atomic>
#include <cstdint>

#include <functional>
#include <map>
#include <thread>

#include "common/libs/fs/shared_fd.h"
#include "common/libs/glog/logging.h"
#include "common/vsoc/lib/lock_guard.h"
#include "common/vsoc/lib/region_control.h"
#include "common/vsoc/lib/region_signaling_interface.h"
#include "common/vsoc/shm/base.h"
#include "uapi/vsoc_shm.h"

namespace vsoc {

class RegionControl;
class RegionView;

/**
 * Represents a task that is tied to a RegionView.
 *
 * This is currently used for the task that forwards futexes across the
 * shared memory window.
 */
class RegionWorker {
 public:
  RegionWorker(RegionView* region, std::shared_ptr<RegionControl> control);
  ~RegionWorker();

  void start();

  void Work();

 protected:
  std::shared_ptr<RegionControl> control_;
  RegionView* region_;
  std::thread thread_;
  volatile bool stopping_;
};

/**
 * Base class to access a mapped region in VSoC shared memory.
 * This class holds the methods that depends on the region's memory having an
 * address. The RegionControl class holds the methods that can be invoked
 * without mapping the region.
 */
class RegionView : public RegionSignalingInterface {
 public:
  virtual ~RegionView();

#if defined(CUTTLEFISH_HOST)
  bool Open(const char* region_name, const char* domain);
#else
  bool Open(const char* region_name);
#endif

  // Returns a pointer to the table that will be scanned for signals
  const vsoc_signal_table_layout& incoming_signal_table();

  // Returns a pointer to the table that will be used to post signals
  const vsoc_signal_table_layout& outgoing_signal_table();

  // Returns true iff an interrupt is queued in the signal table
  bool HasIncomingInterrupt() {
    return *region_offset_to_pointer<std::atomic<uint32_t>>(
        incoming_signal_table().interrupt_signalled_offset);
  }

  // Wake any threads waiting for an interrupt. This is generally used during
  // shutdown.
  void InterruptSelf() { control_->InterruptSelf(); }

  // Interrupt our peer if an interrupt is not already on the way.
  // Returns true if the interrupt was sent, false if an interrupt was already
  // pending.
  bool MaybeInterruptPeer();

  // Scan in the incoming signal table, issuing futex calls for any posted
  // signals and then reposting them to the peer if they were round-trip
  // signals.
  //
  //   signal_handler: An action to perform on every offset signalled by our
  //   peer, usually a FUTEX_WAKE call, but can be customized for other
  //   purposes.
  void ProcessSignalsFromPeer(
      std::function<void(uint32_t)> signal_handler);

  // Post a signal to the guest, the host, or both.
  // See futex(2) FUTEX_WAKE for details.
  //
  //   sides_to_signal: controls where the signal is sent
  //
  //   signal_addr: the memory location to signal. Must be within the region.
  void SendSignal(layout::Sides sides_to_signal,
                  std::atomic<uint32_t>* signal_addr);

  // Post a signal to our peer for a specific memeory location.
  // See futex(2) FUTEX_WAKE for details.
  //
  //   signal_addr: the memory location to signal. Must be within the region.
  //
  //   round_trip: true if there may be waiters on both sides of the shared
  //               memory.
  void SendSignalToPeer(std::atomic<uint32_t>* signal_addr, bool round_trip);

  // Waits until an interrupt appears on this region, then clears the
  // interrupted flag and returns.
  void WaitForInterrupt();

  // This implements the following:
  // if (*signal_addr == last_observed_value)
  //   wait_for_signal_at(signal_addr);
  //
  // Note: the caller still needs to check the value at signal_addr because
  // this function may return early for reasons that are implementation-defined.
  // See futex(2) FUTEX_WAIT for details.
  //
  //   signal_addr: the memory that will be signaled. Must be within the region.
  //
  //   last_observed_value: the value that motivated the calling code to wait.
  //
  // The return value is -1 on error. On the guest positive values give the
  // number of false wakes.
  int WaitForSignal(std::atomic<uint32_t>* signal_addr,
                     uint32_t last_observed_value) override;

  // Starts the signal table scanner. This must be invoked by subclasses, which
  // MUST store the returned unique_ptr as a class member.
  __attribute__((warn_unused_result))
  std::unique_ptr<RegionWorker> StartWorker();

  // Returns a pointer to the start of region data that is cast to the given
  // type.  Initializers that run in the launcher use this to get a typed view
  // of the region. Most other cases should be handled via TypedRegionView.
  template <typename LayoutType>
  LayoutType* GetLayoutPointer() {
    return this->region_offset_to_pointer<LayoutType>(
        control_->region_desc().offset_of_region_data);
  }

  // Helper functions for lock guards. This approach has two advantages:
  //
  //   o Callers don't have to be refactored when lock types change
  //   o The region pointer can be provided automatically
  template <typename T>
  LockGuard<T> make_lock_guard(T* lock) {
    return LockGuard<T>(lock);
  }

  LockGuard<::vsoc::layout::GuestAndHostLock> make_lock_guard(
      ::vsoc::layout::GuestAndHostLock* l) {
    return LockGuard<::vsoc::layout::GuestAndHostLock>(l, this);
  }

 protected:
  template <typename T>
  T* region_offset_to_pointer(uint32_t offset) {
    return control_->region_offset_to_pointer<T>(offset);
  }

  template <typename T>
  const T& region_offset_to_reference(uint32_t offset) const {
    if (offset > control_->region_size()) {
      LOG(FATAL) << __FUNCTION__ << ": " << offset << " not in region @"
                 << region_base_;
    }
    return *reinterpret_cast<const T*>(
        reinterpret_cast<uintptr_t>(region_base_) + offset);
  }

  // Calculates an offset based on a pointer in the region. Crashes if the
  // pointer isn't in the region.
  // This is mostly for the RegionView's internal plumbing. Use TypedRegionView
  // and RegionLayout to avoid this in most cases.
  template <typename T>
  uint32_t pointer_to_region_offset(T* ptr) const {
    uint32_t rval = reinterpret_cast<uintptr_t>(ptr) -
                          reinterpret_cast<uintptr_t>(region_base_);
    if (rval > control_->region_size()) {
      LOG(FATAL) << __FUNCTION__ << ": " << ptr << " not in region @"
                 << region_base_;
    }
    return rval;
  }

  std::shared_ptr<RegionControl> control_;
  void* region_base_{};
};

}  // namespace vsoc