| // 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); |
| } |