// Copyright 2007 Google Inc. All Rights Reserved.
|
|
// 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.
|
|
// This is an interface to a simple thread safe container with fine-grain locks,
|
// used to hold data blocks and patterns.
|
|
// This file must work with autoconf on its public version,
|
// so these includes are correct.
|
#include "finelock_queue.h"
|
#include "os.h"
|
|
// Page entry queue implementation follows.
|
// Push and Get functions are analogous to lock and unlock operations on a given
|
// page entry, while preserving queue semantics.
|
//
|
// The actual 'queue' implementation is actually just an array. The entries are
|
// never shuffled or re-ordered like that of a real queue. Instead, Get
|
// functions return a random page entry of a given type and lock that particular
|
// page entry until it is unlocked by corresponding Put functions.
|
//
|
// In this implementation, a free page is those page entries where pattern is
|
// null (pe->pattern == 0)
|
|
|
// Constructor: Allocates memory and initialize locks.
|
FineLockPEQueue::FineLockPEQueue(
|
uint64 queuesize, int64 pagesize) {
|
q_size_ = queuesize;
|
pages_ = new struct page_entry[q_size_];
|
pagelocks_ = new pthread_mutex_t[q_size_];
|
page_size_ = pagesize;
|
|
// What metric should we measure this run.
|
queue_metric_ = kTouch;
|
|
{ // Init all the page locks.
|
for (uint64 i = 0; i < q_size_; i++) {
|
pthread_mutex_init(&(pagelocks_[i]), NULL);
|
// Pages start out owned (locked) by Sat::InitializePages.
|
// A locked state indicates that the page state is unknown,
|
// and the lock should not be aquired. As InitializePages creates
|
// the page records, they will be inserted and unlocked, at which point
|
// they are ready to be aquired and filled by worker threads.
|
sat_assert(pthread_mutex_lock(&(pagelocks_[i])) == 0);
|
}
|
}
|
|
{ // Init the random number generator.
|
for (int i = 0; i < 4; i++) {
|
rand_seed_[i] = i + 0xbeef;
|
pthread_mutex_init(&(randlocks_[i]), NULL);
|
}
|
}
|
|
// Try to make a linear congruential generator with our queue size.
|
// We need this to deterministically search all the queue (being able to find
|
// a single available element is a design requirement), but we don't want to
|
// cause any page to be more likley chosen than another. The previous
|
// sequential retry heavily biased pages at the beginning of a bunch, or
|
// isolated pages surrounded by unqualified ones.
|
int64 length = queuesize;
|
int64 modlength = length;
|
int64 a;
|
int64 c;
|
|
if (length < 3) {
|
a = 1;
|
c = 1;
|
} else {
|
// Search for a nontrivial generator.
|
a = getA(length) % length;
|
// If this queue size doesn't have a nontrivial generator (where the
|
// multiplier is greater then one) we'll check increasing queue sizes,
|
// and discard out of bounds results.
|
while (a == 1) {
|
modlength++;
|
a = getA(modlength) % modlength;
|
}
|
c = getC(modlength);
|
}
|
|
// This is our final generator.
|
a_ = a;
|
c_ = c;
|
modlength_ = modlength;
|
}
|
|
// Part of building a linear congruential generator n1 = (a * n0 + c) % m
|
// Get 'a', where a - 1 must be divisible by all prime
|
// factors of 'm', our queue size.
|
int64 FineLockPEQueue::getA(int64 m) {
|
int64 remaining = m;
|
int64 a = 1;
|
if ((((remaining / 4) * 4) == remaining)) {
|
a = 2;
|
}
|
// For each number, let's see if it's divisible,
|
// then divide it out.
|
for (int64 i = 2; i <= m; i++) {
|
if (((remaining / i) * i) == remaining) {
|
remaining /= i;
|
// Keep dividing it out until there's no more.
|
while (((remaining / i) * i) == remaining)
|
remaining /= i;
|
a *= i;
|
}
|
}
|
|
// Return 'a' as specified.
|
return (a + 1) % m;
|
}
|
|
|
// Part of building a linear congruential generator n1 = (a * n0 + c) % m
|
// Get a prime number approx 3/4 the size of our queue.
|
int64 FineLockPEQueue::getC(int64 m) {
|
// Start here at 3/4.
|
int64 start = (3 * m) / 4 + 1;
|
int64 possible_prime = start;
|
// Keep trying until we find a prime.
|
for (possible_prime = start; possible_prime > 1; possible_prime--) {
|
bool failed = false;
|
for (int64 i = 2; i < possible_prime; i++) {
|
if (((possible_prime / i) * i) == possible_prime) {
|
failed = true;
|
break;
|
}
|
}
|
if (!failed) {
|
return possible_prime;
|
}
|
}
|
// One is prime enough.
|
return 1;
|
}
|
|
// Destructor: Clean-up allocated memory and destroy pthread locks.
|
FineLockPEQueue::~FineLockPEQueue() {
|
uint64 i;
|
for (i = 0; i < q_size_; i++)
|
pthread_mutex_destroy(&(pagelocks_[i]));
|
delete[] pagelocks_;
|
delete[] pages_;
|
for (i = 0; i < 4; i++) {
|
pthread_mutex_destroy(&(randlocks_[i]));
|
}
|
}
|
|
|
bool FineLockPEQueue::QueueAnalysis() {
|
const char *measurement = "Error";
|
uint64 buckets[32];
|
|
if (queue_metric_ == kTries)
|
measurement = "Failed retrievals";
|
else if (queue_metric_ == kTouch)
|
measurement = "Reads per page";
|
|
// Buckets for each log2 access counts.
|
for (int b = 0; b < 32; b++) {
|
buckets[b] = 0;
|
}
|
|
// Bucketize the page counts by highest bit set.
|
for (uint64 i = 0; i < q_size_; i++) {
|
uint32 readcount = pages_[i].touch;
|
int b = 0;
|
for (b = 0; b < 31; b++) {
|
if (readcount < (1u << b))
|
break;
|
}
|
|
buckets[b]++;
|
}
|
|
logprintf(12, "Log: %s histogram\n", measurement);
|
for (int b = 0; b < 32; b++) {
|
if (buckets[b])
|
logprintf(12, "Log: %12d - %12d: %12d\n",
|
((1 << b) >> 1), 1 << b, buckets[b]);
|
}
|
|
return true;
|
}
|
|
namespace {
|
// Callback mechanism for exporting last action.
|
OsLayer *g_os;
|
FineLockPEQueue *g_fpqueue = 0;
|
|
// Global callback to hook into Os object.
|
bool err_log_callback(uint64 paddr, string *buf) {
|
if (g_fpqueue) {
|
return g_fpqueue->ErrorLogCallback(paddr, buf);
|
}
|
return false;
|
}
|
}
|
|
// Setup global state for exporting callback.
|
void FineLockPEQueue::set_os(OsLayer *os) {
|
g_os = os;
|
g_fpqueue = this;
|
}
|
|
OsLayer::ErrCallback FineLockPEQueue::get_err_log_callback() {
|
return err_log_callback;
|
}
|
|
// This call is used to export last transaction info on a particular physical
|
// address.
|
bool FineLockPEQueue::ErrorLogCallback(uint64 paddr, string *message) {
|
struct page_entry pe;
|
OsLayer *os = g_os;
|
sat_assert(g_os);
|
char buf[256];
|
|
// Find the page of this paddr.
|
int gotpage = GetPageFromPhysical(paddr, &pe);
|
if (!gotpage) {
|
return false;
|
}
|
|
// Find offset into the page.
|
uint64 addr_diff = paddr - pe.paddr;
|
|
// Find vaddr of this paddr. Make sure it matches,
|
// as sometimes virtual memory is not contiguous.
|
char *vaddr =
|
reinterpret_cast<char*>(os->PrepareTestMem(pe.offset, page_size_));
|
uint64 new_paddr = os->VirtualToPhysical(vaddr + addr_diff);
|
os->ReleaseTestMem(vaddr, pe.offset, page_size_);
|
|
// Is the physical address at this page offset the same as
|
// the physical address we were given?
|
if (new_paddr != paddr) {
|
return false;
|
}
|
|
// Print all the info associated with this page.
|
message->assign(" (Last Transaction:");
|
|
if (pe.lastpattern) {
|
int offset = addr_diff / 8;
|
datacast_t data;
|
|
data.l32.l = pe.lastpattern->pattern(offset << 1);
|
data.l32.h = pe.lastpattern->pattern((offset << 1) + 1);
|
|
snprintf(buf, sizeof(buf), " %s data=%#016llx",
|
pe.lastpattern->name(), data.l64);
|
message->append(buf);
|
}
|
snprintf(buf, sizeof(buf), " tsc=%#llx)", pe.ts);
|
message->append(buf);
|
return true;
|
}
|
|
bool FineLockPEQueue::GetPageFromPhysical(uint64 paddr,
|
struct page_entry *pe) {
|
// Traverse through array until finding a page
|
// that contains the address we want..
|
for (uint64 i = 0; i < q_size_; i++) {
|
uint64 page_addr = pages_[i].paddr;
|
// This assumes linear vaddr.
|
if ((page_addr <= paddr) && (page_addr + page_size_ > paddr)) {
|
*pe = pages_[i];
|
return true;
|
}
|
}
|
return false;
|
}
|
|
|
// Get a random number from the slot we locked.
|
uint64 FineLockPEQueue::GetRandom64FromSlot(int slot) {
|
// 64 bit LCG numbers suggested on the internets by
|
// http://nuclear.llnl.gov/CNP/rng/rngman/node4.html and others.
|
uint64 result = 2862933555777941757ULL * rand_seed_[slot] + 3037000493ULL;
|
rand_seed_[slot] = result;
|
return result;
|
}
|
|
// Get a random number, we have 4 generators to choose from so hopefully we
|
// won't be blocking on this.
|
uint64 FineLockPEQueue::GetRandom64() {
|
// Try each available slot.
|
for (int i = 0; i < 4; i++) {
|
if (pthread_mutex_trylock(&(randlocks_[i])) == 0) {
|
uint64 result = GetRandom64FromSlot(i);
|
pthread_mutex_unlock(&(randlocks_[i]));
|
return result;
|
}
|
}
|
// Forget it, just wait.
|
int i = 0;
|
if (pthread_mutex_lock(&(randlocks_[i])) == 0) {
|
uint64 result = GetRandom64FromSlot(i);
|
pthread_mutex_unlock(&(randlocks_[i]));
|
return result;
|
}
|
|
logprintf(0, "Process Error: Could not acquire random lock.\n");
|
sat_assert(0);
|
return 0;
|
}
|
|
|
// Helper function to get a random page entry with given predicate,
|
// ie, page_is_valid() or page_is_empty() as defined in finelock_queue.h.
|
//
|
// Setting tag to a value other than kDontCareTag (-1)
|
// indicates that we need a tag match, otherwise any tag will do.
|
//
|
// Returns true on success, false on failure.
|
bool FineLockPEQueue::GetRandomWithPredicateTag(struct page_entry *pe,
|
bool (*pred_func)(struct page_entry*),
|
int32 tag) {
|
if (!pe || !q_size_)
|
return false;
|
|
// Randomly index into page entry array.
|
uint64 first_try = GetRandom64() % q_size_;
|
uint64 next_try = 1;
|
|
// Traverse through array until finding a page meeting given predicate.
|
for (uint64 i = 0; i < q_size_; i++) {
|
uint64 index = (next_try + first_try) % q_size_;
|
// Go through the loop linear conguentially. We are offsetting by
|
// 'first_try' so this path will be a different sequence for every
|
// initioal value chosen.
|
next_try = (a_ * next_try + c_) % (modlength_);
|
while (next_try >= q_size_) {
|
// If we have chosen a modlength greater than the queue size,
|
// discard out of bounds results.
|
next_try = (a_ * next_try + c_) % (modlength_);
|
}
|
|
// If page does not meet predicate, don't trylock (expensive).
|
if (!(pred_func)(&pages_[index]))
|
continue;
|
|
// If page does not meet tag predicate, don't trylock (expensive).
|
if ((tag != kDontCareTag) && !(pages_[index].tag & tag))
|
continue;
|
|
if (pthread_mutex_trylock(&(pagelocks_[index])) == 0) {
|
// If page property (valid/empty) changes before successfully locking,
|
// release page and move on.
|
if (!(pred_func)(&pages_[index])) {
|
pthread_mutex_unlock(&(pagelocks_[index]));
|
continue;
|
} else {
|
// A page entry with given predicate is locked, returns success.
|
*pe = pages_[index];
|
|
// Add metrics as necessary.
|
if (pred_func == page_is_valid) {
|
// Measure time to fetch valid page.
|
if (queue_metric_ == kTries)
|
pe->touch = i;
|
// Measure number of times each page is read.
|
if (queue_metric_ == kTouch)
|
pe->touch++;
|
}
|
|
return true;
|
}
|
}
|
}
|
|
return false;
|
}
|
|
// Without tag hint.
|
bool FineLockPEQueue::GetRandomWithPredicate(struct page_entry *pe,
|
bool (*pred_func)(struct page_entry*)) {
|
return GetRandomWithPredicateTag(pe, pred_func, kDontCareTag);
|
}
|
|
|
// GetValid() randomly finds a valid page, locks it and returns page entry by
|
// pointer.
|
//
|
// Returns true on success, false on failure.
|
bool FineLockPEQueue::GetValid(struct page_entry *pe) {
|
return GetRandomWithPredicate(pe, page_is_valid);
|
}
|
|
bool FineLockPEQueue::GetValid(struct page_entry *pe, int32 mask) {
|
return GetRandomWithPredicateTag(pe, page_is_valid, mask);
|
}
|
|
// GetEmpty() randomly finds an empty page, locks it and returns page entry by
|
// pointer.
|
//
|
// Returns true on success, false on failure.
|
bool FineLockPEQueue::GetEmpty(struct page_entry *pe, int32 mask) {
|
return GetRandomWithPredicateTag(pe, page_is_empty, mask);
|
}
|
bool FineLockPEQueue::GetEmpty(struct page_entry *pe) {
|
return GetRandomWithPredicate(pe, page_is_empty);
|
}
|
|
// PutEmpty puts an empty page back into the queue, making it available by
|
// releasing the per-page-entry lock.
|
//
|
// Returns true on success, false on failure.
|
bool FineLockPEQueue::PutEmpty(struct page_entry *pe) {
|
if (!pe || !q_size_)
|
return false;
|
|
int64 index = pe->offset / page_size_;
|
if (!valid_index(index))
|
return false;
|
|
pages_[index] = *pe;
|
// Enforce that page entry is indeed empty.
|
pages_[index].pattern = 0;
|
return (pthread_mutex_unlock(&(pagelocks_[index])) == 0);
|
}
|
|
// PutValid puts a valid page back into the queue, making it available by
|
// releasing the per-page-entry lock.
|
//
|
// Returns true on success, false on failure.
|
bool FineLockPEQueue::PutValid(struct page_entry *pe) {
|
if (!pe || !page_is_valid(pe) || !q_size_)
|
return false;
|
|
int64 index = pe->offset / page_size_;
|
if (!valid_index(index))
|
return false;
|
|
pages_[index] = *pe;
|
return (pthread_mutex_unlock(&(pagelocks_[index])) == 0);
|
}
|