Home | History | Annotate | Download | only in src
      1 // Copyright 2007 Google Inc. All Rights Reserved.
      2 
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 
      7 //      http://www.apache.org/licenses/LICENSE-2.0
      8 
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 // This is an interface to a simple thread safe container with fine-grain locks,
     16 // used to hold data blocks and patterns.
     17 
     18 // This file must work with autoconf on its public version,
     19 // so these includes are correct.
     20 #include "finelock_queue.h"
     21 #include "os.h"
     22 
     23 // Page entry queue implementation follows.
     24 // Push and Get functions are analogous to lock and unlock operations on a given
     25 // page entry, while preserving queue semantics.
     26 //
     27 // The actual 'queue' implementation is actually just an array. The entries are
     28 // never shuffled or re-ordered like that of a real queue. Instead, Get
     29 // functions return a random page entry of a given type and lock that particular
     30 // page entry until it is unlocked by corresponding Put functions.
     31 //
     32 // In this implementation, a free page is those page entries where pattern is
     33 // null (pe->pattern == 0)
     34 
     35 
     36 // Constructor: Allocates memory and initialize locks.
     37 FineLockPEQueue::FineLockPEQueue(
     38                  uint64 queuesize, int64 pagesize) {
     39   q_size_ = queuesize;
     40   pages_ = new struct page_entry[q_size_];
     41   pagelocks_ = new pthread_mutex_t[q_size_];
     42   page_size_ = pagesize;
     43 
     44   // What metric should we measure this run.
     45   queue_metric_ = kTouch;
     46 
     47   {  // Init all the page locks.
     48     for (uint64 i = 0; i < q_size_; i++) {
     49         pthread_mutex_init(&(pagelocks_[i]), NULL);
     50         // Pages start out owned (locked) by Sat::InitializePages.
     51         // A locked state indicates that the page state is unknown,
     52         // and the lock should not be aquired. As InitializePages creates
     53         // the page records, they will be inserted and unlocked, at which point
     54         // they are ready to be aquired and filled by worker threads.
     55         sat_assert(pthread_mutex_lock(&(pagelocks_[i])) == 0);
     56     }
     57   }
     58 
     59   {  // Init the random number generator.
     60     for (int i = 0; i < 4; i++) {
     61       rand_seed_[i] = i + 0xbeef;
     62       pthread_mutex_init(&(randlocks_[i]), NULL);
     63     }
     64   }
     65 
     66   // Try to make a linear congruential generator with our queue size.
     67   // We need this to deterministically search all the queue (being able to find
     68   // a single available element is a design requirement), but we don't want to
     69   // cause any page to be more likley chosen than another. The previous
     70   // sequential retry heavily biased pages at the beginning of a bunch, or
     71   // isolated pages surrounded by unqualified ones.
     72   int64 length = queuesize;
     73   int64 modlength = length;
     74   int64 a;
     75   int64 c;
     76 
     77   if (length < 3) {
     78     a = 1;
     79     c = 1;
     80   } else {
     81     // Search for a nontrivial generator.
     82     a = getA(length) % length;
     83     // If this queue size doesn't have a nontrivial generator (where the
     84     // multiplier is greater then one) we'll check increasing queue sizes,
     85     // and discard out of bounds results.
     86     while (a == 1) {
     87       modlength++;
     88       a = getA(modlength) % modlength;
     89     }
     90     c = getC(modlength);
     91   }
     92 
     93   // This is our final generator.
     94   a_ = a;
     95   c_ = c;
     96   modlength_ = modlength;
     97 }
     98 
     99 // Part of building a linear congruential generator n1 = (a * n0 + c) % m
    100 // Get 'a', where a - 1 must be divisible by all prime
    101 // factors of 'm', our queue size.
    102 int64 FineLockPEQueue::getA(int64 m) {
    103   int64 remaining = m;
    104   int64 a = 1;
    105   if ((((remaining / 4) * 4) == remaining)) {
    106     a = 2;
    107   }
    108   // For each number, let's see if it's divisible,
    109   // then divide it out.
    110   for (int64 i = 2; i <= m; i++) {
    111     if (((remaining / i) * i) == remaining) {
    112       remaining /= i;
    113       // Keep dividing it out until there's no more.
    114       while (((remaining / i) * i) == remaining)
    115         remaining /= i;
    116       a *= i;
    117     }
    118   }
    119 
    120   // Return 'a' as specified.
    121   return (a + 1) % m;
    122 }
    123 
    124 
    125 // Part of building a linear congruential generator n1 = (a * n0 + c) % m
    126 // Get a prime number approx 3/4 the size of our queue.
    127 int64 FineLockPEQueue::getC(int64 m) {
    128   // Start here at 3/4.
    129   int64 start = (3 * m) / 4 + 1;
    130   int64 possible_prime = start;
    131   // Keep trying until we find a prime.
    132   for (possible_prime = start; possible_prime > 1; possible_prime--) {
    133     bool failed = false;
    134     for (int64 i = 2; i < possible_prime; i++) {
    135       if (((possible_prime / i) * i) == possible_prime) {
    136         failed = true;
    137         break;
    138       }
    139     }
    140     if (!failed) {
    141       return possible_prime;
    142     }
    143   }
    144   // One is prime enough.
    145   return 1;
    146 }
    147 
    148 // Destructor: Clean-up allocated memory and destroy pthread locks.
    149 FineLockPEQueue::~FineLockPEQueue() {
    150   uint64 i;
    151   for (i = 0; i < q_size_; i++)
    152     pthread_mutex_destroy(&(pagelocks_[i]));
    153   delete[] pagelocks_;
    154   delete[] pages_;
    155   for (i = 0; i < 4; i++) {
    156     pthread_mutex_destroy(&(randlocks_[i]));
    157   }
    158 }
    159 
    160 
    161 bool FineLockPEQueue::QueueAnalysis() {
    162   const char *measurement = "Error";
    163   uint64 buckets[32];
    164 
    165   if (queue_metric_ == kTries)
    166     measurement = "Failed retrievals";
    167   else if (queue_metric_ == kTouch)
    168     measurement = "Reads per page";
    169 
    170   // Buckets for each log2 access counts.
    171   for (int b = 0; b < 32; b++) {
    172     buckets[b] = 0;
    173   }
    174 
    175   // Bucketize the page counts by highest bit set.
    176   for (uint64 i = 0; i < q_size_; i++) {
    177     uint32 readcount = pages_[i].touch;
    178     int b = 0;
    179     for (b = 0; b < 31; b++) {
    180       if (readcount < (1u << b))
    181         break;
    182     }
    183 
    184     buckets[b]++;
    185   }
    186 
    187   logprintf(12, "Log:  %s histogram\n", measurement);
    188   for (int b = 0; b < 32; b++) {
    189     if (buckets[b])
    190       logprintf(12, "Log:  %12d - %12d: %12d\n",
    191           ((1 << b) >> 1), 1 << b, buckets[b]);
    192   }
    193 
    194   return true;
    195 }
    196 
    197 namespace {
    198 // Callback mechanism for exporting last action.
    199 OsLayer *g_os;
    200 FineLockPEQueue *g_fpqueue = 0;
    201 
    202 // Global callback to hook into Os object.
    203 bool err_log_callback(uint64 paddr, string *buf) {
    204   if (g_fpqueue) {
    205     return g_fpqueue->ErrorLogCallback(paddr, buf);
    206   }
    207   return false;
    208 }
    209 }
    210 
    211 // Setup global state for exporting callback.
    212 void FineLockPEQueue::set_os(OsLayer *os) {
    213   g_os = os;
    214   g_fpqueue = this;
    215 }
    216 
    217 OsLayer::ErrCallback FineLockPEQueue::get_err_log_callback() {
    218   return err_log_callback;
    219 }
    220 
    221 // This call is used to export last transaction info on a particular physical
    222 // address.
    223 bool FineLockPEQueue::ErrorLogCallback(uint64 paddr, string *message) {
    224   struct page_entry pe;
    225   OsLayer *os = g_os;
    226   sat_assert(g_os);
    227   char buf[256];
    228 
    229   // Find the page of this paddr.
    230   int gotpage = GetPageFromPhysical(paddr, &pe);
    231   if (!gotpage) {
    232     return false;
    233   }
    234 
    235   // Find offset into the page.
    236   uint64 addr_diff = paddr - pe.paddr;
    237 
    238   // Find vaddr of this paddr. Make sure it matches,
    239   // as sometimes virtual memory is not contiguous.
    240   char *vaddr =
    241     reinterpret_cast<char*>(os->PrepareTestMem(pe.offset, page_size_));
    242   uint64 new_paddr = os->VirtualToPhysical(vaddr + addr_diff);
    243   os->ReleaseTestMem(vaddr, pe.offset, page_size_);
    244 
    245   // Is the physical address at this page offset the same as
    246   // the physical address we were given?
    247   if (new_paddr != paddr) {
    248     return false;
    249   }
    250 
    251   // Print all the info associated with this page.
    252   message->assign(" (Last Transaction:");
    253 
    254   if (pe.lastpattern) {
    255     int offset = addr_diff / 8;
    256     datacast_t data;
    257 
    258     data.l32.l = pe.lastpattern->pattern(offset << 1);
    259     data.l32.h = pe.lastpattern->pattern((offset << 1) + 1);
    260 
    261     snprintf(buf, sizeof(buf), " %s data=%#016llx",
    262                   pe.lastpattern->name(), data.l64);
    263     message->append(buf);
    264   }
    265   snprintf(buf, sizeof(buf), " tsc=%#llx)", pe.ts);
    266   message->append(buf);
    267   return true;
    268 }
    269 
    270 bool FineLockPEQueue::GetPageFromPhysical(uint64 paddr,
    271                                           struct page_entry *pe) {
    272   // Traverse through array until finding a page
    273   // that contains the address we want..
    274   for (uint64 i = 0; i < q_size_; i++) {
    275     uint64 page_addr = pages_[i].paddr;
    276     // This assumes linear vaddr.
    277     if ((page_addr <= paddr) && (page_addr + page_size_ > paddr)) {
    278       *pe = pages_[i];
    279       return true;
    280     }
    281   }
    282   return false;
    283 }
    284 
    285 
    286 // Get a random number from the slot we locked.
    287 uint64 FineLockPEQueue::GetRandom64FromSlot(int slot) {
    288   // 64 bit LCG numbers suggested on the internets by
    289   // http://nuclear.llnl.gov/CNP/rng/rngman/node4.html and others.
    290   uint64 result = 2862933555777941757ULL * rand_seed_[slot] + 3037000493ULL;
    291   rand_seed_[slot] = result;
    292   return result;
    293 }
    294 
    295 // Get a random number, we have 4 generators to choose from so hopefully we
    296 // won't be blocking on this.
    297 uint64 FineLockPEQueue::GetRandom64() {
    298   // Try each available slot.
    299   for (int i = 0; i < 4; i++) {
    300     if (pthread_mutex_trylock(&(randlocks_[i])) == 0) {
    301       uint64 result = GetRandom64FromSlot(i);
    302       pthread_mutex_unlock(&(randlocks_[i]));
    303       return result;
    304     }
    305   }
    306   // Forget it, just wait.
    307   int i = 0;
    308   if (pthread_mutex_lock(&(randlocks_[i])) == 0) {
    309     uint64 result = GetRandom64FromSlot(i);
    310     pthread_mutex_unlock(&(randlocks_[i]));
    311     return result;
    312   }
    313 
    314   logprintf(0, "Process Error: Could not acquire random lock.\n");
    315   sat_assert(0);
    316   return 0;
    317 }
    318 
    319 
    320 // Helper function to get a random page entry with given predicate,
    321 // ie, page_is_valid() or page_is_empty() as defined in finelock_queue.h.
    322 //
    323 // Setting tag to a value other than kDontCareTag (-1)
    324 // indicates that we need a tag match, otherwise any tag will do.
    325 //
    326 // Returns true on success, false on failure.
    327 bool FineLockPEQueue::GetRandomWithPredicateTag(struct page_entry *pe,
    328                       bool (*pred_func)(struct page_entry*),
    329                       int32 tag) {
    330   if (!pe || !q_size_)
    331     return false;
    332 
    333   // Randomly index into page entry array.
    334   uint64 first_try = GetRandom64() % q_size_;
    335   uint64 next_try = 1;
    336 
    337   // Traverse through array until finding a page meeting given predicate.
    338   for (uint64 i = 0; i < q_size_; i++) {
    339     uint64 index = (next_try + first_try) % q_size_;
    340     // Go through the loop linear conguentially. We are offsetting by
    341     // 'first_try' so this path will be a different sequence for every
    342     // initioal value chosen.
    343     next_try = (a_ * next_try + c_) % (modlength_);
    344     while (next_try >= q_size_) {
    345       // If we have chosen a modlength greater than the queue size,
    346       // discard out of bounds results.
    347       next_try = (a_ * next_try + c_) % (modlength_);
    348     }
    349 
    350     // If page does not meet predicate, don't trylock (expensive).
    351     if (!(pred_func)(&pages_[index]))
    352       continue;
    353 
    354     // If page does not meet tag predicate, don't trylock (expensive).
    355     if ((tag != kDontCareTag) && !(pages_[index].tag & tag))
    356       continue;
    357 
    358     if (pthread_mutex_trylock(&(pagelocks_[index])) == 0) {
    359       // If page property (valid/empty) changes before successfully locking,
    360       // release page and move on.
    361       if (!(pred_func)(&pages_[index])) {
    362         pthread_mutex_unlock(&(pagelocks_[index]));
    363         continue;
    364       } else {
    365         // A page entry with given predicate is locked, returns success.
    366         *pe = pages_[index];
    367 
    368         // Add metrics as necessary.
    369         if (pred_func == page_is_valid) {
    370           // Measure time to fetch valid page.
    371           if (queue_metric_ == kTries)
    372             pe->touch = i;
    373           // Measure number of times each page is read.
    374           if (queue_metric_ == kTouch)
    375             pe->touch++;
    376         }
    377 
    378         return true;
    379       }
    380     }
    381   }
    382 
    383   return false;
    384 }
    385 
    386 // Without tag hint.
    387 bool FineLockPEQueue::GetRandomWithPredicate(struct page_entry *pe,
    388                       bool (*pred_func)(struct page_entry*)) {
    389   return GetRandomWithPredicateTag(pe, pred_func, kDontCareTag);
    390 }
    391 
    392 
    393 // GetValid() randomly finds a valid page, locks it and returns page entry by
    394 // pointer.
    395 //
    396 // Returns true on success, false on failure.
    397 bool FineLockPEQueue::GetValid(struct page_entry *pe) {
    398   return GetRandomWithPredicate(pe, page_is_valid);
    399 }
    400 
    401 bool FineLockPEQueue::GetValid(struct page_entry *pe, int32 mask) {
    402   return GetRandomWithPredicateTag(pe, page_is_valid, mask);
    403 }
    404 
    405 // GetEmpty() randomly finds an empty page, locks it and returns page entry by
    406 // pointer.
    407 //
    408 // Returns true on success, false on failure.
    409 bool FineLockPEQueue::GetEmpty(struct page_entry *pe, int32 mask) {
    410   return GetRandomWithPredicateTag(pe, page_is_empty, mask);
    411 }
    412 bool FineLockPEQueue::GetEmpty(struct page_entry *pe) {
    413   return GetRandomWithPredicate(pe, page_is_empty);
    414 }
    415 
    416 // PutEmpty puts an empty page back into the queue, making it available by
    417 // releasing the per-page-entry lock.
    418 //
    419 // Returns true on success, false on failure.
    420 bool FineLockPEQueue::PutEmpty(struct page_entry *pe) {
    421   if (!pe || !q_size_)
    422     return false;
    423 
    424   int64 index = pe->offset / page_size_;
    425   if (!valid_index(index))
    426     return false;
    427 
    428   pages_[index] = *pe;
    429   // Enforce that page entry is indeed empty.
    430   pages_[index].pattern = 0;
    431   return (pthread_mutex_unlock(&(pagelocks_[index])) == 0);
    432 }
    433 
    434 // PutValid puts a valid page back into the queue, making it available by
    435 // releasing the per-page-entry lock.
    436 //
    437 // Returns true on success, false on failure.
    438 bool FineLockPEQueue::PutValid(struct page_entry *pe) {
    439   if (!pe || !page_is_valid(pe) || !q_size_)
    440     return false;
    441 
    442   int64 index = pe->offset / page_size_;
    443   if (!valid_index(index))
    444     return false;
    445 
    446   pages_[index] = *pe;
    447   return (pthread_mutex_unlock(&(pagelocks_[index])) == 0);
    448 }
    449